Skip to content

Latest commit

 

History

History
569 lines (395 loc) · 36.7 KB

File metadata and controls

569 lines (395 loc) · 36.7 KB

The Neon CLI is a command-line interface that lets you manage Neon Serverless Postgres directly from the terminal. For the complete documentation, see Neon CLI.

Install the Neon CLI

npm

npm i -g neonctl

Requires Node.js 18.0 or higher.

Howebrew

brew install neonctl

Binary (macOS, Linux, Windows)

Download a binary file here.

Upgrade

npm

npm update -g neonctl

Requires Node.js 18.0 or higher.

Howebrew

brew upgrade neonctl

Binary (macOS, Linux, Windows)

To upgrade a binary version, download the latest binary file, as described above, and replace your old binary with the new one.

Connect

Run the following command to authenticate a connection to Neon:

neonctl auth

The auth command launches a browser window where you can authorize the Neon CLI to access your Neon account. Running a Neon CLI command without authenticating with neonctl auth automatically launches the browser authentication process.

Alternatively, you can authenticate a connection with a Neon API key using the --api-key option when running a Neon CLI command. For example, an API key is used with the following neonctl projects list command:

neonctl projects list --api-key <neon_api_key>

For information about obtaining an Neon API key, see Authentication, in the Neon API Reference.

Connect with psql

The psql command

neonctl psql [branch] opens a psql session against a branch. It builds the connection string for the branch and launches psql — a shortcut for neonctl connection-string --psql. See Neon CLI commands — psql for the full reference.

neonctl psql                                    # default branch
neonctl psql main                               # a specific branch
neonctl psql main@2024-01-01T00:00:00Z          # point-in-time (branch@timestamp or branch@lsn)
neonctl psql --pooled                           # use the pooled connection

Arguments after -- are forwarded to psql:

neonctl psql main -- -c "SELECT version()"
neonctl psql main -- -f script.sql --csv

Options: --project-id, --role-name, --database-name, --pooled, --endpoint-type (read_only | read_write), --ssl, plus the global options.

The --psql flag

Several other commands accept a --psql flag that opens a psql session against the resolved endpoint:

neonctl connection-string --psql --project-id <id>
neonctl projects create --psql
neonctl branches create --psql

Any arguments after -- are forwarded to psql, for example:

neonctl cs --psql --project-id <id> -- -c "SELECT version()"
neonctl cs --psql --project-id <id> -- -f script.sql --csv

Embedded psql fallback

If the system has psql installed on $PATH, --psql continues to spawn the native binary — there is no behavior change for existing users.

If psql is not found on $PATH, neonctl now falls back to an embedded TypeScript implementation. There is nothing to install or configure; it ships with neonctl. This removes the "no psql binary" trap on machines (and CI runners) that don't have PostgreSQL client tools installed.

Automatic fallback is the intended path — there is normally no flag to set. The embedded implementation can also be force-selected (primarily for tests and CI, e.g. to exercise it even when a native psql is present):

  • --fallback — force the embedded implementation on connection-string, projects create, and branches create. Intentionally hidden from --help: it's a test/CI knob, not a user-facing option (the automatic fallback above is the supported behavior).
  • NEONCTL_PSQL_FALLBACK=1 — environment variable with the same effect as --fallback. Convenient for scripts and CI.

The embedded implementation is verified against a conformance suite that diffs its behavior against real PostgreSQL (14–18) and the upstream psql regression + TAP tests.

What works

REPL & scripting

  • Interactive REPL with a hand-rolled VT100 line editor (no native bindings); vi and emacs edit modes (VI_MODE psql variable)
  • Persistent command history (~/.psql_history, libreadline format)
  • ~/.psqlrc autoload (including $PGSYSCONFDIR/psqlrc and version-suffixed variants)
  • Scripted modes: -c "SQL", -f script.sql, and stdin; --single-transaction, ON_ERROR_STOP, ECHO, --echo-all
  • SINGLELINE (-S), \timing, \watch (named flags c=/i=/m=, unbounded continuous mode)

Backslash commands

  • All output formats: aligned, unaligned, wrapped, csv, json, html, asciidoc, latex, latex-longtable, troff-ms (\a \H \t \x \pset \f \C …)
  • All \d* describe commands with full upstream parity (columns, indexes, foreign keys, triggers, view definitions, sequences, RLS, replica identity, partitions, tablespaces, access methods, inheritance, FDW, stats objects, publications, subscriptions, per-column FDW options, TOAST owner)
  • \copy to/from file, PROGRAM, STDIN, STDOUT (incl. the \. EOF marker); \g / \gx / \gset / \gdesc / \gexec and \g | program pipes
  • Extended query + pipeline mode (\bind, \bind_named, \startpipeline, \parse, \sendpipeline)
  • \crosstabview, \lo_* large objects, \e/\edit (external editor), \s (history), \?/\h help, \if/\elif/\else/\endif, \set/\unset, \connect, \encoding (live SET client_encoding), \!, \cd, \prompt (incl. no-echo -), \password
  • Tab completion (~88 rules incl. live pg_settings GUC lookup, deep ALTER sub-actions, JOIN clauses, window OVER)

Connection & authentication

  • libpq-equivalent lookup precedence: argv flags > URI > PG* env vars > ~/.pgpass > pg_service.conf > libpq defaults
  • SCRAM-SHA-256 / SCRAM-SHA-256-PLUS with tls-server-end-point channel binding (channel_binding); MD5 and cleartext; require_auth
  • Multi-host failover & load balancing: target_session_attrs (any / read-write / read-only / primary / standby / prefer-standby), load_balance_hosts, DNS fan-out, hostaddr
  • Unix-domain sockets (host beginning with /); TCP keepalives (keepalives, keepalives_idle)

TLS

  • sslmode disable → verify-full; client certs in PEM or DER via sslcert / sslkey (+ sslpassword for encrypted keys, with the libpq group/world-readable-key check)
  • Trust config: sslrootcert (incl. =system with SSL_CERT_FILE / SSL_CERT_DIR), default client-cert discovery (~/.postgresql/postgresql.{crt,key}), sslcertmode
  • CRL: sslcrl and sslcrldir; ssl_min_protocol_version / ssl_max_protocol_version; sslsni
  • Direct-SSL negotiation (sslnegotiation=direct, PostgreSQL 17+, via ALPN)

What's not supported

  • GSSAPI / SSPI (gssencmode, Kerberos/SSPI auth, requirepeer). GSS transport encryption needs a native Kerberos binding, which the embedded psql deliberately avoids (pure TypeScript, zero native dependencies — the same reason the line editor is hand-rolled). node-postgres doesn't support it either, and Neon doesn't use it. gssencmode=disable / prefer are accepted; gssencmode=require is rejected with a clear error. requirepeer is parsed but a Unix-socket connection that sets it is refused (Node exposes no peer-credential API — it is not silently ignored).
  • keepalives_interval / keepalives_count — Node's socket API exposes only keepalive enable + initial delay, so these are accepted but not applied.

Known limitations

  • TLS cipher is runtime-dependent. The negotiated TLS 1.3 ciphersuite is chosen by the host runtime's TLS library from an offer byte-identical to libpq's. Under Node (OpenSSL) that is TLS_AES_256_GCM_SHA384, matching vanilla psql; under Bun (BoringSSL) it is TLS_AES_128_GCM_SHA256. Both are TLS 1.3 AEAD suites with no practical security difference, and neither runtime exposes a client-side knob to steer the selection.

Configure autocompletion

The Neon CLI supports autocompletion, which you can configure in a few easy steps. See Neon CLI commands — completion for instructions.

Linking a project

neonctl link is a Vercel-style command that binds the current directory to a Neon project. It picks (or creates) an organization and a project and writes a .neon file ({ "orgId", "projectId", "branch" }) that subsequent commands run in this directory (or any sub-directory) pick up automatically.

link resolves what it can and verifies every identifier you pass before writing, so a .neon is never left half-written or pointing at something that doesn't exist:

  • org is inferred from the project (so --project-id alone is enough); it's omitted only when the project has no organization (personal account).
  • project is taken from --project-id (or chosen interactively / via --agent).
  • branch is left to an explicit neonctl checkout <branch>link never silently pins a project's default branch (that would make later commands quietly target, say, production). It only records a branch when you pass --branch, when one is already pinned for the same project (preserved), when you pick one in the interactive picker, or for a freshly created project (whose single branch is unambiguous).

When a branch ends up pinned, link also runs env pull so the branch's Neon env vars (DATABASE_URL, …) land in a local .env. With no branch pinned there is nothing to pull, so link instead nudges you to run neonctl checkout. Pass --no-env-pull to skip the pull (for example when injecting env at runtime with neon-env run or neonctl dev).

Migrating from set-context? set-context is deprecated in favor of link (see below). It still works exactly as before for now (a raw write), it just prints a deprecation warning. The .neon branchId field is also superseded by branch (which stores the branch name when known); old branchId files are still read and are upgraded to branch the next time link/checkout writes the context.

There are three modes:

Interactive (default) — guided prompts for humans:

$ neonctl link
? Which organization would you like to link? › Personal Org (org-abc123)
? Which project would you like to link? › + Create new project…
? Name for the new project: › my-app
? Which region should the new project run in? › AWS US East (Ohio) (aws-us-east-2)
Created project polished-snowflake-12345678 ("my-app") in aws-us-east-2.
Linked .neon:
  orgId:     org-abc123
  projectId: polished-snowflake-12345678
  branch:    main

When you link an existing project that has more than one branch, the interactive flow adds a final step to pick which branch to pin — the same + Create a new branch… + list selector used by neonctl checkout (a single-branch project is pinned automatically, no prompt). Non-interactive link --project-id … does not prompt or default a branch; it links org + project and leaves branch selection to neonctl checkout:

? Which organization would you like to link? › Personal Org (org-abc123)
? Which project would you like to link? › my-app (polished-snowflake-12345678)
? Which branch would you like to link? › [default] main (br-main-branch-87654321)

Non-interactive (flags or --params JSON) — for scripts and CI:

# Link to an existing project (org is inferred from the project; no branch pinned)
neonctl link --project-id polished-snowflake-12345678

# Same, but also pin a branch (name or id — resolved and stored as its name)
neonctl link --project-id polished-snowflake-12345678 --branch main

# Pin/switch the branch in the already-linked project
neonctl link --branch main          # alias: --branch-id

# Create a new project and link it (pins the new project's default branch)
neonctl link --org-id org-abc123 --project-name my-app --region-id aws-us-east-2

# Same payload, one JSON blob
neonctl link --params '{"orgId":"org-abc123","projectName":"my-app","regionId":"aws-us-east-2"}'

# Record just the default org (preserves any existing project/branch)
neonctl link --org-id org-abc123

# Forget the current context
neonctl link --clear

# Offline write — no API calls, no verification (see --no-checks below)
neonctl link --no-checks --org-id org-abc123 --project-id polished-snowflake-12345678

Every supplied identifier is checked before anything is written, with actionable errors — e.g. Project '…' not found, You don't have access to project '…', Organization '…' not found, or your API key doesn't have access to it, Project '…' belongs to organization 'A', not 'B', or Branch '…' not found in project '…'. Available branches: ….

Agent mode (--agent) — a JSON state machine designed for AI coding assistants. Each invocation returns a single JSON object with a status discriminator describing the next step, the available options, and the exact follow-up command to run.

$ neonctl link --agent
{
  "status": "needs_org",
  "instruction": "Ask the user which of these 2 organizations they want to link the current directory to. After they pick one, re-run the next_command_template with the chosen --org-id value.",
  "options": [
    { "id": "org-abc123", "name": "Personal Org" },
    { "id": "org-team",   "name": "Team Org" }
  ],
  "next_command_template": "neonctl link --agent --org-id <org_id>"
}

$ neonctl link --agent --org-id org-abc123
{
  "status": "needs_project",
  "instruction": "Ask the user whether to link to one of these 1 existing projects (use next_command_template with --project-id) or create a new project (use create_option.next_command_template).",
  "options": [
    { "id": "polished-snowflake-12345678", "name": "my-app" }
  ],
  "create_option": {
    "instruction": "To create a new project, ask the user for a project name. The region can be omitted to receive a follow-up needs_project_details response that lists available regions.",
    "next_command_template": "neonctl link --agent --org-id org-abc123 --project-name <name> --region-id <region_id>"
  },
  "next_command_template": "neonctl link --agent --org-id org-abc123 --project-id <project_id>"
}

$ neonctl link --agent --org-id org-abc123 --project-id polished-snowflake-12345678
{
  "status": "linked",
  "context_file": "/path/to/cwd/.neon",
  "context": {
    "orgId": "org-abc123",
    "projectId": "polished-snowflake-12345678"
  },
  "project": { "id": "polished-snowflake-12345678" },
  "message": "Linked /path/to/cwd/.neon to project polished-snowflake-12345678 (org org-abc123). No branch pinned — run `neonctl checkout <branch>` (omit the branch to list options) to pin one and pull its env vars."
}

The linked response omits branch unless one was pinned (via --branch, an existing pin, or project creation); pass --branch <name|id> to include it. The agent flow also handles project creation: if the agent sends --project-name without --region-id, the next response is needs_project_details with the list of supported regions.

Organization-scoped API keys (those created at the organization level rather than the user level) cannot list user organizations or call the regions endpoint. link handles this transparently:

  • If the API key is org-scoped and at least one project already exists in the org, the CLI auto-detects the org_id from the first project. In interactive mode it prints an informational message; in --agent mode it skips straight to needs_project.
  • If the API key is org-scoped and no projects exist yet, --agent returns a needs_org response with options: [] and an instruction telling the user to find their org ID in the Neon Console. Interactive mode prints an error pointing to --org-id.
  • When the regions endpoint is not allowed, link falls back to a built-in static region list.

Agent error contract: any unexpected failure in --agent mode is reported as JSON to stdout with exit code 1, so agents can always parse the response:

{
  "status": "error",
  "code": "CLIENT_ERROR",
  "message": "user has no access to projects"
}

Offline writes (--no-checks) — write the .neon with no API calls at all: no org inference, no existence/access verification, no env pull. Because nothing can be resolved offline, it requires both --org-id and --project-id (--branch optional, stored verbatim). Handy for scripted/CI setups or re-creating a .neon from values you already trust:

neonctl link --no-checks --org-id org-abc123 --project-id polished-snowflake-12345678 --branch main

set-context is deprecated

set-context is deprecated in favor of link and prints a deprecation warning (to stderr, so it never pollutes stdout or scripts). For backward compatibility its behavior is unchanged: it's still a raw, offline write of exactly the fields you pass (no org inference, no verification, no env pull), and bare set-context still clears the file. Nothing breaks today — but new work should use link, and set-context will be removed in a future major release.

How today's set-context uses map onto link:

set-context (deprecated) Recommended link equivalent
neonctl set-context --project-id <id> neonctl link --project-id <id> (infers org + verifies; branch via checkout)
neonctl set-context --org-id <id> neonctl link --org-id <id>
neonctl set-context --branch-id <id> neonctl link --branch <name|id>
neonctl set-context (clear) neonctl link --clear
a raw local write (no network) neonctl link --no-checks --org-id <id> --project-id <id>

The key difference: link resolves and verifies before writing (so you never get a half-written or stale .neon), whereas set-context writes whatever you give it verbatim. The closest like-for-like replacement for the old raw write is link --no-checks.

checkout

checkout [id|name] pins a branch in the local context so subsequent commands target it — it's the focused companion to link for the common "switch the branch I'm working on" case (link resolves org + project; checkout pins the branch). It resolves the branch (by name or id) against the project, then heals the .neon file: it always (re)writes projectId, branch, and orgId (when the project has one), so a .neon that was missing fields or drifted ends up complete and consistent. The branch is stored as its name when known (matching link). When orgId isn't already known (from --org-id or the existing .neon), it's looked up from the project itself.

The branch argument is optional: run neonctl checkout with no branch in an interactive terminal to fetch the project's branches and pick one from a list. In a non-interactive context (CI or no TTY), a branch must be passed explicitly.

Branch id vs name is detected automatically (a br-… value is treated as an id):

  • id — matched strictly by id. A non-existent id is a hard "not found" error (ids are server-assigned, so checkout never creates one).
  • name — matched by name. If the name doesn't exist, in an interactive terminal checkout offers to create it (equivalent to neonctl branch create --name <name>: branched from the project's default branch with a read-write compute), then checks it out. In a non-interactive context a missing name is the usual "not found" error.

The project is resolved through the standard neonctl chain, each entry winning over the next:

  1. --project-id <id> flag
  2. projectId from the closest .neon file (found by walking up from the current directory — see "Where .neon lives" below)
  3. If still unresolved and the API key maps to exactly one project, that project is auto-detected (same behaviour as branches and connection-string)

If none of those resolve a project, checkout prints a telling error explaining the chain above. In an interactive terminal it then offers to run neonctl link in the current folder so you can pick (or create) a project on the spot; once linked, it continues and pins the requested branch. In non-interactive contexts (CI or no TTY) it exits with a non-zero code and the same guidance instead of prompting.

The resolved branch is then written (by name) to the same .neon file link uses:

$ neonctl checkout main --project-id polished-snowflake-12345678
INFO: Checked out branch br-main-branch-87654321 on project polished-snowflake-12345678. Updated /path/to/cwd/.neon.

$ cat .neon
{
  "orgId": "org-abc123",
  "projectId": "polished-snowflake-12345678",
  "branch": "main"
}

After pinning the branch, checkout also runs env pull by default, so the branch's Neon env vars are written to your local .env and you can start building right away — the branch-first loop is just link + checkout. Pass --no-env-pull to skip it (for example when env is injected at runtime via neon-env run / neonctl dev, or to keep secrets out of the working tree). A pull failure never undoes the checkout: the branch stays pinned and the failure is surfaced as a warning pointing you at neonctl env pull (or neonctl deploy if a neon.ts-declared service is missing).

env pull

env pull writes the linked branch's Neon environment variables into a local dotenv file: an existing .env if you have one, otherwise .env.local (override with --file <path>). Only Neon-managed keys (DATABASE_URL, DATABASE_URL_UNPOOLED, and the Neon Auth / Data API URLs when those services are enabled) are written; any other lines in the file are preserved. The branch comes from the closest .neon file, so no --branch is needed (pass --branch <id|name> to target another branch).

link and checkout invoke env pull automatically (see above), so you usually only run it by hand to refresh vars or to pull a different branch into a specific file:

# Refresh the linked branch's vars in place
neonctl env pull

# Pull a specific branch into a specific file
neonctl env pull --branch preview --file .env.preview

If you'd rather not keep env vars on disk, inject them at runtime instead with neon-env run -- <your dev command> (from @neondatabase/env) or neonctl dev, and pass --no-env-pull to link / checkout.

Where .neon lives: link writes .neon into the current working directory by default. If an existing .neon is found in any parent directory, that file is reused — so commands run from a sub-directory of a linked project still pick up the project's context. To pin the location explicitly, pass --context-file <path>.

.gitignore scaffolding: when .neon is created for the first time, the CLI also makes sure a .gitignore sits alongside it listing .neon. If .gitignore doesn't exist it's created with a single .neon line; if it does exist, .neon is appended only when missing (no duplicates, your other entries are left alone). On subsequent updates to an existing .neon, .gitignore is left untouched — so if you deliberately un-ignore .neon (e.g. to commit shared context), the entry is not re-added on every command.

Config as code (config / deploy)

Describe a branch's desired state in a neon.ts policy and reconcile it from the CLI — the Neon equivalent of terraform status / plan / apply. A policy splits into a static existential set — top-level auth / dataApi toggles and the beta preview block (Functions, buckets, AI Gateway) that decide what exists — and a dynamic branch closure that tunes each branch (compute settings, TTL, protection, parent) based on the branch it's evaluated for (name, isDefault, …):

// neon.ts
import { defineConfig } from '@neondatabase/config/v1';

export default defineConfig({
  // Static: what exists on every branch (drives the typed env).
  auth: true,
  // Dynamic: per-branch tuning only — cannot add/remove services.
  branch: (branch) => {
    if (branch.isDefault) {
      return { protected: true };
    }
    return { parent: 'main', ttl: '7d' };
  },
});

Three sub-commands plus a top-level alias drive it:

# Inspect the branch's live Neon state (read-only — never mutates)
neon config status

# Dry-run diff: show exactly what `apply` would change
neon config plan

# Reconcile the policy against the branch
neon config apply

# `neon deploy` is an alias for `neon config apply`
neon deploy

Project & branch resolution follows the same chain as the rest of the CLI, each entry winning over the next:

  1. --project-id <id> flag
  2. projectId from the closest .neon file (found by walking up from the current directory — see "Where .neon lives" above)
  3. If still unresolved and the API key maps to exactly one project, that project is auto-detected

The branch is chosen with --branch <id|name>; without it the project's default branch is used. The policy itself is found by walking up from the current directory for a neon.ts, or pass --config <path> to point at one explicitly.

Apply-only flags (also available on deploy):

  • --update-existing — auto-confirm overriding existing remote settings on the branch. Without it, drift on settings already present remotely (compute, TTL, protected) is reported as a conflict and apply makes no changes until you resolve it or pass this flag.
  • --allow-protected — auto-confirm applying to a branch Neon marks as protected. Without it, apply refuses to touch a protected branch.

Output: status prints the project, branch, and reverse-engineered config; plan / apply print the planned/applied changes and any conflicts as tables. Pass --output json (or --output yaml) to emit the full machine-readable result (PushResult) for piping into other tools or CI.

# CI gate: fail the build if the branch has drifted from the policy
neon config plan --project-id polished-snowflake-12345678 --output json

# Reconcile a feature branch, overriding any manual tweaks made in the console
neon deploy --branch my-feature --update-existing

Function deploys declared under preview.functions are bundled by neonctl's own esbuild helper and uploaded as part of apply, so the policy stays declarative and the packaged CLI never has to embed esbuild's native binary.

Scaffold a project (bootstrap)

neonctl bootstrap copies a Neon starter template into a new (or current) directory — conceptually like degit, but it only pulls from a small set of templates we maintain in the public neondatabase/examples repo. It requires no Neon login: it just downloads files from GitHub.

Pass a target directory (or . for the current one). In an interactive terminal you pick the template from a list; in CI / non-interactive contexts pass --template <id>.

# Pick a template interactively and scaffold it into ./my-app
$ neonctl bootstrap my-app

# Scaffold a specific template into the current directory (no prompts)
$ neonctl bootstrap . --template hono

The target directory must be empty unless you pass --force (a lone .git is ignored, so a freshly git inited folder is fine). Symlinks and executable bits in the template are preserved.

Commands

Command Subcommands Description
auth Authenticate
projects list, create, update, delete, get Manage projects
ip-allow list, add, remove, reset Manage IP Allow
me Show current user
branches list, create, rename, add-compute, set-default, set-expiration, delete, get Manage branches
databases list, create, delete Manage databases
function deploy, list, get, delete Manage Neon Functions
roles list, create, delete Manage roles
operations list Manage operations
connection-string Get connection string
psql Connect to a database via psql
set-context Deprecated; use link
env pull Manage a branch's env vars
checkout Pin a branch in .neon
link Link a directory to a project
config status, plan, apply Drive a branch from neon.ts
deploy Alias for config apply
bootstrap Scaffold a project from a template
bucket create, list, delete, object list, object get, object put, object delete (incl. --recursive) Manage buckets and their objects
completion Generate a completion script

Global options

Global options are supported with any Neon CLI command.

Option Description Type Default
-o, --output Set the Neon CLI output format (json, yaml, or table) string table
--config-dir Path to the Neon CLI configuration directory string /home/<user>/.config/neonctl
--api-key Neon API key string ""
--analytics Manage analytics boolean true
-v, --version Show the Neon CLI version number boolean -
-h, --help Show the Neon CLI help boolean -
  • -o, --output

    Sets the output format. Supported options are json, yaml, and table. The default is table. Table output may be limited. The json and yaml output formats show all data.

    neonctl me --output json
  • --config-dir

    Specifies the path to the neonctl configuration directory. To view the default configuration directory containing you credentials.json file, run neonctl --help. The credentials file is created when you authenticate using the neonctl auth command. This option is only necessary if you move your neonctl configuration file to a location other than the default.

    neonctl projects list --config-dir /home/dtprice/.config/neonctl
  • --api-key

    Specifies your Neon API key. You can authenticate using a Neon API key when running a Neon CLI command instead of using neonctl auth. For information about obtaining an Neon API key, see Authentication, in the Neon API Reference.

    neonctl <command> --api-key <neon_api_key>
  • --analytics

    Analytics are enabled by default to gather information about the CLI commands and options that are used by our customers. This data collection assists in offering support, and allows for a better understanding of typical usage patterns so that we can improve user experience. Neon does not collect user-defined data, such as project IDs or command payloads. To opt-out of analytics data collection, specify --no-analytics or --analytics false.

  • -v, --version

    Shows the Neon CLI version number.

    $ neonctl --version
    1.15.0
  • -h, --help

    Shows the neonctl command-line help. You can view help for neonctl, a neonctl command, or a neonctl subcommand, as shown in the following examples:

    neonctl --help
    
    neonctl branches --help
    
    neonctl branches create --help

Contribute

This repo uses pnpm. The required version is pinned in .tool-versions and package.json's packageManager field. The simplest way to get the right version is mise: mise install reads .tool-versions and installs Node and pnpm. Alternatives: npm install -g pnpm@9.15.9, or Corepack (corepack enable pnpm).

To run the CLI locally, execute the build command after making changes:

pnpm install
pnpm run build

To develop continuously:

pnpm run watch

To run commands from the local build, replace the neonctl command with node dist; for example:

node dist branches --help

Embedded psql tests

The embedded TypeScript psql implementation has its own conformance test suite that runs the same scripts against the embedded psql and a reference psql binary, then diffs the output.

bun run test:conformance         # run against $PSQL_BINARY (defaults to the system psql)
bun run test:conformance:matrix  # run across PG 14/15/16/17/18 locally (requires Docker)

Releasing

Maintainers: see RELEASING.md for the two-stage publish flow.