neon 4.7.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +49 -59
  2. package/dist/_chunks/{auth_selection-ktL6uoEe.js → auth_selection-pitMFzDe.js} +1 -1
  3. package/dist/_chunks/{credential_io-B1JxWELN.js → credential_io-YeAxg9Xn.js} +2 -2
  4. package/dist/_chunks/{paths-g3DRqJiD.js → paths-DMq0Lt7a.js} +1 -1
  5. package/dist/_chunks/{profiles-D95AeZ5v.js → profiles-CvnFEQyd.js} +1 -1
  6. package/dist/analytics.js +2 -2
  7. package/dist/auth_context.js +1 -1
  8. package/dist/commands/auth.js +12 -9
  9. package/dist/commands/bootstrap.js +72 -43
  10. package/dist/commands/branches.js +29 -14
  11. package/dist/commands/claim.js +1 -1
  12. package/dist/commands/init.js +80 -79
  13. package/dist/commands/link.js +78 -63
  14. package/dist/commands/mcp.js +1 -1
  15. package/dist/commands/plugins.js +4 -4
  16. package/dist/commands/profile.js +4 -4
  17. package/dist/commands/projects.js +17 -6
  18. package/dist/commands/skills.js +1 -1
  19. package/dist/config.js +1 -1
  20. package/dist/credential_io.js +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/init/child.js +36 -0
  23. package/dist/init/plan.js +47 -0
  24. package/dist/init/tooling.js +43 -0
  25. package/dist/init/wizard.js +42 -0
  26. package/dist/mcp/targets.js +1 -1
  27. package/dist/plugins/plan.js +1 -1
  28. package/dist/plugins/targets.js +5 -1
  29. package/dist/retire_credential.js +1 -1
  30. package/dist/utils/middlewares.js +1 -1
  31. package/dist/utils/package_manager.js +10 -5
  32. package/package.json +15 -16
  33. package/dist/init/auth.js +0 -53
  34. package/dist/init/build_config.js +0 -4
  35. package/dist/init/detect_agent.js +0 -63
  36. package/dist/init/editors.js +0 -19
  37. package/dist/init/enrich_output.js +0 -61
  38. package/dist/init/extension.js +0 -154
  39. package/dist/init/inspect.js +0 -249
  40. package/dist/init/interactive.js +0 -529
  41. package/dist/init/neonctl.js +0 -139
  42. package/dist/init/orchestrate.js +0 -189
  43. package/dist/init/phases/auth.js +0 -195
  44. package/dist/init/phases/cleanup.js +0 -27
  45. package/dist/init/phases/db.js +0 -257
  46. package/dist/init/phases/getting_started.js +0 -218
  47. package/dist/init/phases/mcp.js +0 -219
  48. package/dist/init/phases/migrations.js +0 -250
  49. package/dist/init/phases/neon_auth.js +0 -116
  50. package/dist/init/phases/setup.js +0 -582
  51. package/dist/init/phases/skills.js +0 -86
  52. package/dist/init/phases/status.js +0 -82
  53. package/dist/init/profile_cli.js +0 -56
  54. package/dist/init/resolve_context.js +0 -105
  55. package/dist/init/route_command.js +0 -93
  56. package/dist/init/skills.js +0 -206
  57. package/dist/init/types.js +0 -1
  58. package/dist/init/vsix.js +0 -89
package/README.md CHANGED
@@ -133,6 +133,18 @@ automation:
133
133
  neon branches create --project-id <project-id> --name production --protected
134
134
  ```
135
135
 
136
+ Project and branch creation include connection credentials in their output by
137
+ default for backward compatibility. When the output may be logged or passed to
138
+ an agent, use `--no-secrets` to return only the created resource metadata:
139
+
140
+ ```bash
141
+ neon projects create --name my-project --output json --no-secrets
142
+ neon branches create --project-id <project-id> --name preview --output json --no-secrets
143
+ ```
144
+
145
+ The flag omits the complete `connection_uris` block rather than redacting one
146
+ field. Retrieve a connection string separately when it is actually needed.
147
+
136
148
  ### Enable logical replication
137
149
 
138
150
  Enable logical replication for every endpoint in an existing project:
@@ -257,7 +269,7 @@ The Neon CLI supports autocompletion, which you can configure in a few easy step
257
269
 
258
270
  - **org** is inferred from the project (so `--project-id` alone is enough); it's omitted only when the project has no organization (personal account).
259
271
  - **project** is taken from `--project-id` (or chosen interactively).
260
- - **branch** is left to an explicit [`neon checkout <branch>`](#checkout) — `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).
272
+ - **branch** is taken from `--branch`, an existing pin for the same project, or the project's branch list: one branch is pinned automatically; several prompt in a TTY, pin the default with `-y`, or stay unpinned for [`neon checkout <branch>`](#checkout). A project with no branches is linked without a pin and says so.
261
273
 
262
274
  When a branch ends up pinned, `link` also runs [`env pull`](#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 `neon checkout`. Pass `--no-env-pull` to skip the pull (for example when injecting env at runtime with `neon-env run` or `neon dev`).
263
275
 
@@ -282,22 +294,34 @@ Linked .neon:
282
294
 
283
295
  When you link an **existing** project that has more than one branch, the interactive flow adds a
284
296
  final step to pick which branch to pin — the same `+ Create a new branch…` + list selector used by
285
- `neon checkout` (a single-branch project is pinned automatically, no prompt). Non-interactive
286
- `link --project-id …` does **not** prompt or default a branch; it links org + project and leaves
287
- branch selection to `neon checkout`:
297
+ `neon checkout` (a single-branch project is pinned automatically, no prompt):
288
298
 
289
299
  ```bash
300
+ $ neon link
290
301
  ? Which organization would you like to link? › Personal Org (org-abc123)
291
302
  ? Which project would you like to link? › my-app (polished-snowflake-12345678)
292
303
  ? Which branch would you like to link? › [default] main (br-main-branch-87654321)
293
304
  ```
294
305
 
306
+ `link --project-id …` skips org and project. One branch is pinned with no prompt. Several branches
307
+ in a TTY show the branch prompt; `-y` pins the default; no TTY leaves the pin empty for
308
+ `neon checkout`:
309
+
310
+ ```bash
311
+ $ neon link --project-id polished-snowflake-12345678
312
+ ? Which branch would you like to link? › [default] main (br-main-branch-87654321)
313
+ ```
314
+
295
315
  **Non-interactive (flags or `--params` JSON)** — for scripts and CI:
296
316
 
297
317
  ```bash
298
- # Link to an existing project (org is inferred from the project; no branch pinned)
318
+ # Link to an existing project (org is inferred). Pins the only branch;
319
+ # several branches prompt in a TTY, or stay unpinned without one.
299
320
  neon link --project-id polished-snowflake-12345678
300
321
 
322
+ # Same, pin the project's default branch when several exist
323
+ neon link --project-id polished-snowflake-12345678 -y
324
+
301
325
  # Same, but also pin a branch (name or id — resolved and stored as its name)
302
326
  neon link --project-id polished-snowflake-12345678 --branch main
303
327
 
@@ -327,7 +351,7 @@ Every supplied identifier is checked before anything is written, with actionable
327
351
  ```bash
328
352
  neon orgs list --output json
329
353
  neon projects list --org-id <org-id> --output json
330
- neon link --project-id <project-id>
354
+ neon link --project-id <project-id> [--branch <name> | -y]
331
355
  neon link --org-id <org-id> --project-name <name> --region-id aws-us-east-2
332
356
  ```
333
357
 
@@ -352,7 +376,7 @@ How today's `set-context` uses map onto `link`:
352
376
 
353
377
  | `set-context` (deprecated) | Recommended `link` equivalent |
354
378
  | --------------------------------------- | ----------------------------------------------------------------------------- |
355
- | `neon set-context --project-id <id>` | `neon link --project-id <id>` (infers org + verifies; branch via checkout) |
379
+ | `neon set-context --project-id <id>` | `neon link --project-id <id>` (infers org + verifies; pins the only branch) |
356
380
  | `neon set-context --org-id <id>` | `neon link --org-id <id>` |
357
381
  | `neon set-context --branch-id <id>` | `neon link --branch <name\|id>` |
358
382
  | `neon set-context` (clear) | `neon link --clear` |
@@ -657,7 +681,9 @@ When a package cannot be bundled — a native addon with no esbuild loader, or a
657
681
 
658
682
  ## Scaffold a project (`bootstrap`)
659
683
 
660
- `neon 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`](https://github.com/neondatabase/examples) repo. It requires no Neon login: it just downloads files from GitHub.
684
+ `neon 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`](https://github.com/neondatabase/examples) repo. The template copy needs no Neon login: it downloads files from GitHub.
685
+
686
+ After scaffolding, an interactive terminal offers agent tooling (the Neon plugin, or skills and MCP separately — never both) and then `neon link`. `--default` / `-y` skips the template, install, git, and agent pickers, then runs `link --yes`. `link --yes` still asks for a project unless one is already linked. `--no-agent-setup` and `--no-link` skip those. Non-interactive without `--default` prints next steps and does not install, set up agents, or link.
661
687
 
662
688
  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>`.
663
689
 
@@ -665,7 +691,7 @@ Pass a target directory (or `.` for the current one). In an interactive terminal
665
691
  # Pick a template interactively and scaffold it into ./my-app
666
692
  $ neon bootstrap my-app
667
693
 
668
- # Scaffold a specific template into the current directory (no prompts)
694
+ # Scaffold a specific template into the current directory (skips the template picker)
669
695
  $ neon bootstrap . --template hono
670
696
 
671
697
  # List templates
@@ -677,64 +703,28 @@ $ neon bootstrap --list-templates --output json
677
703
 
678
704
  The target directory must be empty unless you pass `--force` (a lone `.git` is ignored, so a freshly `git init`ed folder is fine). Symlinks and executable bits in the template are preserved.
679
705
 
680
- ## Set up a project for your coding agent (`init`)
706
+ ## Set up a project (`init`)
681
707
 
682
- `neon init` wires an existing project up to Neon: it signs you in, installs the Neon MCP server and agent skills into your editor, adds the Neon Local Connect extension for VS Code and Cursor, creates or picks a project, writes `DATABASE_URL` into `.env`, and offers to scaffold migrations.
708
+ `neon init` sets up this directory for Neon.
683
709
 
684
- ```bash
685
- $ neon init
686
- ```
687
-
688
- Run in a terminal it prompts you through those steps. This is what the retired `neon-init` package used to do; `npx neon init` replaces it.
710
+ An empty directory (nothing except `.git`) runs `neon bootstrap .` and stops. With `-y` that is `neon bootstrap . --default`. Bootstrap handles scaffolding, agent tooling, and linking.
689
711
 
690
- Two side effects worth knowing before you run it. It **installs or upgrades `neon` globally**, with whichever package manager invoked it — the flow drives Neon by shelling out to the CLI rather than calling the API in-process. And it **writes `.neon`** in the project directory, the same context file `neon link` and `neon checkout` use.
712
+ An existing app installs agent tooling, then `neon link` unless `.neon` already has a projectId, then `neon config init`. Interactive `config init` opens the services picker; `-y` uses `--services none` (starter policy).
691
713
 
692
- ### Agent mode
693
-
694
- `--agent` turns the same flow into a state machine an AI coding assistant drives. It prints **one JSON object on stdout** and nothing else: a phase response carrying a `status` and a `nextAction` telling the agent what to do next — usually another `neon init` invocation, spelled out as a `command`. The two read-only steps, `status` and `finalize`, return a snapshot instead.
714
+ In an interactive terminal it offers one of: the Neon plugin (`neon plugins`), skills and MCP separately (`neon skills`, then `neon mcp`), or skip agent setup. It never runs plugin and skills+MCP together.
695
715
 
696
716
  ```bash
697
- $ neon init --agent --data '{"step":"status"}'
698
- {
699
- "auth": { "authenticated": true },
700
- "tooling": { "mcpServer": { "configured": true, "scope": "global" }, "skills": { "installed": false, "scope": null } },
701
- "project": { "databaseUrl": false },
702
- "migrations": { "tool": "prisma", "hasMigrations": false },
703
- "recommendations": [
704
- { "priority": "high", "message": "No DATABASE_URL found in .env", "command": "neon init --agent --data '{\"step\":\"db\"}'" },
705
- { "priority": "medium", "message": "Neon agent skills not detected in this project", "command": "neon init --agent --data '{\"step\":\"skills\",\"install\":true}'" },
706
- { "priority": "medium", "message": "prisma detected but no migrations found", "command": "neon init --agent --data '{\"step\":\"migrations\"}'" }
707
- ]
708
- }
717
+ $ neon init
718
+ $ neon init -y
709
719
  ```
710
720
 
711
- `--agent` is implied when stdin is not a TTY and a known agent is detected from the environment (Claude Code, Codex, Cline, Cursor, VS Code, Windsurf).
712
-
713
- `--data` takes a JSON object whose `step` selects the phase: `auth`, `db`, `setup`, `getting-started`, `mcp`, `skills`, `migrations`, `neon-auth`, `status`, or `finalize`. Remaining keys are that phase's options. Without `--data`, the orchestrator picks the next phase itself. An unrecognised `step` is refused with the full list.
721
+ `-y` skips the template picker and the agent-setup offer. Empty dir: `bootstrap --default`. Existing app: plugin when a project-level plugin agent is detected (Cursor, Claude Code, Codex); otherwise skills and MCP. VS Code, GitHub Copilot CLI, and Grok only take the plugin user-level (`neon plugins --global`), so `-y` uses skills and MCP for those.
714
722
 
715
- **Failures are JSON too**, so an agent never has to distinguish "it broke" from "it returned nothing":
723
+ `-y` forwards `-y` to `plugins` or `skills`/`mcp`, `--default` to `bootstrap`, `--yes` to `link`, and `--services none` to `config init`. `link --yes` only skips the "already linked" confirmation; it still asks for a project unless one is already linked.
716
724
 
717
- ```bash
718
- $ neon init --agent --data '{not json'
719
- {
720
- "success": false,
721
- "error": "Invalid JSON in --data flag at position 1. Expected a JSON object."
722
- }
723
- $ echo $?
724
- 1
725
- ```
725
+ A failed step stops the rest. `--profile` and `--config-dir` are forwarded to each child. `--output json` and `--output yaml` are refused; the commands init runs print their own output.
726
726
 
727
- That message reports where parsing stopped and nothing more. `--data` carries whatever you put in it, and the JSON parser's own message quotes a window of the input, so echoing either would put a connection string or an API key on stdout.
728
-
729
- **One exception to "JSON on stdout".** Credentials are resolved before any command runs, so a failure in that step — an unknown `--profile` or `NEON_PROFILE`, `--api-key` and `--profile` together, a `credentials.json` that cannot be read, or an OS keyring item that cannot be read — prints `ERROR: …` on stderr, leaves stdout empty, and exits 1. Treat a non-zero exit with empty stdout as a credential problem and read stderr.
730
-
731
- | Option | |
732
- | --- | --- |
733
- | `--agent`, `-a` | Emit the JSON state machine instead of prompting |
734
- | `--data <json>` | Route to one phase, with that phase's options |
735
- | `--skip-migrations` | Leave the migrations phase out of the flow |
736
- | `--preview` | Enable preview features (scaffolding a project from a template) |
737
- | `--profile <name>` | Run as that stored account. See [Which credential an invocation uses](#which-credential-an-invocation-uses). |
727
+ `skills` needs Node.js 22.20 or newer. See [`bootstrap`](#scaffold-a-project-bootstrap), [`plugins`](#install-the-neon-plugin-plugins), [`skills`](#install-neon-agent-skills-skills), [`link`](#linking-a-project), and [`mcp`](#install-the-neon-mcp-server-mcp) for what those commands write.
738
728
 
739
729
  ## Install the Neon MCP server (`mcp`)
740
730
 
@@ -1116,7 +1106,7 @@ When both are only environment variables the key wins, which keeps a CI pipeline
1116
1106
 
1117
1107
  `neon auth` and the `profile` subcommands are outside all of this, because they read the same flags to mean something else: `neon auth --profile work` names where to write a credential, and `neon profile create work --api-key …` names one to store.
1118
1108
 
1119
- `neon init` follows the same profile selection: `--profile` and `NEON_PROFILE` pick the stored account. Every `npx neon` it runs, and every command it tells an agent to run (`npx neon …`, `neon init --agent …`), includes `--profile <name>` when a profile was named by `--profile` or `NEON_PROFILE`, and `--config-dir <path>` when you passed `--config-dir`; nothing is added when those were not set. It still ignores `--api-key` and `NEON_API_KEY` for its own credential read and runs as that stored profile (`DEFAULT` when a key was named with no profile). Those keys are also not written onto the emitted commands — putting a key in agent JSON would print it. An ambient `NEON_API_KEY` is inherited by the subprocesses.
1109
+ `neon init` forwards `--profile` and `--config-dir` to the commands it runs. An explicit `--api-key` is passed to those children through `NEON_API_KEY`, not argv.
1120
1110
 
1121
1111
  ## API keys (`api-keys`)
1122
1112
 
@@ -1213,8 +1203,8 @@ Id Name Project Created At Last Used At Last
1213
1203
  | open | | Open the linked project in Console |
1214
1204
  | config | `init`, `status`, `plan`, `apply` | Drive a branch from `neon.ts` |
1215
1205
  | deploy | | Alias for `config apply` |
1216
- | bootstrap | | Scaffold a project from a template |
1217
- | init | | Set up a project for a coding agent |
1206
+ | bootstrap | | Scaffold a template, then agent tooling and link |
1207
+ | init | | Empty dir: bootstrap. Existing: agents, link, neon.ts |
1218
1208
  | mcp | | Install the Neon MCP server |
1219
1209
  | plugins | | Install the Neon plugin |
1220
1210
  | skills | `update` | Install Neon agent skills |
@@ -1,4 +1,4 @@
1
- import "./profiles-D95AeZ5v.js";
1
+ import "./profiles-CvnFEQyd.js";
2
2
  //#region ../../internals/cli-core/dist/auth_selection.js
3
3
  /**
4
4
  * # Which credential an invocation authenticates with
@@ -1,6 +1,6 @@
1
1
  import { t as writeSecretFile } from "./secure_file-BucZj4yQ.js";
2
- import { o as isOwnedCredentialPath } from "./paths-g3DRqJiD.js";
3
- import { f as profilesFilePath, v as CRED_STORAGE_FILE, y as CRED_STORAGE_KEYRING } from "./profiles-D95AeZ5v.js";
2
+ import { a as isOwnedCredentialPath } from "./paths-DMq0Lt7a.js";
3
+ import { f as profilesFilePath, v as CRED_STORAGE_FILE, y as CRED_STORAGE_KEYRING } from "./profiles-CvnFEQyd.js";
4
4
  import { tryLoadKeyring } from "../keyring.js";
5
5
  import { existsSync, readFileSync, rmSync } from "node:fs";
6
6
  import { dirname } from "node:path";
@@ -148,4 +148,4 @@ const isOwnedCredentialPath = (configDirectory, file) => {
148
148
  return legacy !== void 0 && isInsideConfigDir(legacy, file);
149
149
  };
150
150
  //#endregion
151
- export { isInsideConfigDir as a, defaultDir as i, configDir as n, isOwnedCredentialPath as o, credentialsPath as r, resolveConfigFile as s, CREDENTIALS_FILE as t };
151
+ export { isOwnedCredentialPath as a, isInsideConfigDir as i, credentialsPath as n, resolveConfigFile as o, defaultDir as r, CREDENTIALS_FILE as t };
@@ -1,5 +1,5 @@
1
1
  import { t as writeSecretFile } from "./secure_file-BucZj4yQ.js";
2
- import { i as defaultDir, r as credentialsPath, s as resolveConfigFile } from "./paths-g3DRqJiD.js";
2
+ import { n as credentialsPath, o as resolveConfigFile, r as defaultDir } from "./paths-DMq0Lt7a.js";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { isAbsolute, relative, resolve } from "node:path";
5
5
  //#region ../../internals/cli-core/dist/cli_config.js
package/dist/analytics.js CHANGED
@@ -1,9 +1,9 @@
1
- import { a as OAUTH, t as storeFor } from "./_chunks/credential_io-B1JxWELN.js";
1
+ import { a as OAUTH, t as storeFor } from "./_chunks/credential_io-YeAxg9Xn.js";
2
2
  import { getCliAgent, getGithubEnvVars, isCi } from "./env.js";
3
3
  import { log } from "./log.js";
4
4
  import pkg_default from "./pkg.js";
5
5
  import { getApiClient, isNeonApiError } from "./api.js";
6
- import { r as credentialsPath } from "./_chunks/paths-g3DRqJiD.js";
6
+ import { n as credentialsPath } from "./_chunks/paths-DMq0Lt7a.js";
7
7
  import "./config.js";
8
8
  import { getAuthContext } from "./auth_context.js";
9
9
  import { isCurrentBranchProbe } from "./context.js";
@@ -1,4 +1,4 @@
1
- import { o as isOwnedCredentialPath } from "./_chunks/paths-g3DRqJiD.js";
1
+ import { a as isOwnedCredentialPath } from "./_chunks/paths-DMq0Lt7a.js";
2
2
  import "./config.js";
3
3
  //#region src/auth_context.ts
4
4
  const locationFromContext = (context) => {
@@ -1,15 +1,15 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
- import { a as OAUTH, i as API_KEY, l as interpretCredentials, s as credentialLabel, t as storeFor } from "../_chunks/credential_io-B1JxWELN.js";
2
+ import { a as OAUTH, i as API_KEY, l as interpretCredentials, s as credentialLabel, t as storeFor } from "../_chunks/credential_io-YeAxg9Xn.js";
3
3
  import { isCi } from "../env.js";
4
4
  import { log } from "../log.js";
5
5
  import { getApiClient } from "../api.js";
6
- import { o as isOwnedCredentialPath } from "../_chunks/paths-g3DRqJiD.js";
6
+ import { a as isOwnedCredentialPath } from "../_chunks/paths-DMq0Lt7a.js";
7
7
  import { setAuthContext } from "../auth_context.js";
8
8
  import { currentContextFile, isClaimCommand, isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, readContextFile } from "../context.js";
9
- import { _ as upsertProfile, d as newProfileLocation, g as selectProfileName, h as resolveProfile, i as assertValidProfileName, l as locationForName, m as readProfiles, n as KEYRING_CREDENTIALS, p as profilesUsingPath, r as assertProfilesUsable, s as isKeyringPointer, t as DEFAULT_PROFILE, u as locationOf } from "../_chunks/profiles-D95AeZ5v.js";
9
+ import { _ as upsertProfile, d as newProfileLocation, g as selectProfileName, h as resolveProfile, i as assertValidProfileName, l as locationForName, m as readProfiles, n as KEYRING_CREDENTIALS, p as profilesUsingPath, r as assertProfilesUsable, s as isKeyringPointer, t as DEFAULT_PROFILE, u as locationOf } from "../_chunks/profiles-CvnFEQyd.js";
10
10
  import { extendTokenSet } from "../utils/auth.js";
11
11
  import { auth, refreshToken } from "../auth.js";
12
- import { i as selectCredential, n as displacedProfileWarning, t as credentialInputs } from "../_chunks/auth_selection-ktL6uoEe.js";
12
+ import { i as selectCredential, n as displacedProfileWarning, t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
13
13
  import { ClaimableClient, ClaimableServiceError } from "../claimable/api.js";
14
14
  import { assertionHasExpired, claimableCredentialsPath, readClaimableCredentials, resolveClaimableContext, shouldUseClaimableCredentials } from "../claimable/state.js";
15
15
  import { readOutgoingCredential, retirePreviousCredential } from "../retire_credential.js";
@@ -220,10 +220,17 @@ const ensureAuth = async (props) => {
220
220
  if (isMcpOauth(props)) return;
221
221
  if (props._[0] === "open") return;
222
222
  if (isSkillsCommand(props) || isPluginsCommand(props)) return;
223
+ if (props._[0] === "init") {
224
+ selectCredential({
225
+ ...credentialInputs(),
226
+ profileFlag: props.profile
227
+ });
228
+ log.debug("init: skipping global auth; child commands authenticate");
229
+ return;
230
+ }
223
231
  if (isClaimCommand(props)) return;
224
232
  const isLocalDev = props._[0] === "dev";
225
233
  const isBootstrap = props._[0] === "bootstrap";
226
- const isInit = props._[0] === "init";
227
234
  const isMcp = isMcpCommand(props);
228
235
  if (props._[0] === "auth") {
229
236
  props.apiClient = getApiClient({
@@ -334,10 +341,6 @@ const ensureAuth = async (props) => {
334
341
  log.debug("bootstrap: no usable credentials; continuing without auth");
335
342
  return;
336
343
  }
337
- if (isInit) {
338
- log.debug("init: skipping global auth; init manages its own auth flow");
339
- return;
340
- }
341
344
  if (isMcp) {
342
345
  log.debug("mcp: no usable credentials; minting requires auth or --oauth");
343
346
  return;
@@ -2,9 +2,12 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { isCi } from "../env.js";
3
3
  import { log } from "../log.js";
4
4
  import { getCliName } from "../utils/cli_name.js";
5
- import { t as credentialInputs } from "../_chunks/auth_selection-ktL6uoEe.js";
5
+ import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
6
6
  import { writer } from "../writer.js";
7
7
  import { FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds } from "../init/bootstrap.js";
8
+ import { spawnCliChild } from "../init/child.js";
9
+ import { projectContextFile } from "../init/plan.js";
10
+ import { runScaffoldFollowUp } from "../init/tooling.js";
8
11
  import { formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
9
12
  import { existsSync } from "node:fs";
10
13
  import { join, relative, resolve } from "node:path";
@@ -19,7 +22,7 @@ var bootstrap_exports = /* @__PURE__ */ __exportAll({
19
22
  });
20
23
  const removedAgent = () => `\`${getCliName()} bootstrap --agent\` was removed. List templates with \`${getCliName()} bootstrap --list-templates --output json\`. Scaffold with \`${getCliName()} bootstrap <directory> --template <id>\` or \`${getCliName()} bootstrap <directory> --default\`.`;
21
24
  const command = "bootstrap [directory]";
22
- const describe = "Scaffold a new project from a Neon starter template";
25
+ const describe = "Scaffold a new project from a Neon starter template, then install agent tooling and link a Neon project";
23
26
  const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").positional("directory", {
24
27
  describe: "Directory to scaffold into. Use \".\" for the current directory. Omit to be prompted.",
25
28
  type: "string"
@@ -45,7 +48,7 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
45
48
  },
46
49
  default: {
47
50
  alias: "y",
48
- describe: "Quick start: scaffold the default template (or --template) and run the usual setup (install dependencies, git init) without prompting. Linking is left to you since it needs a project choice.",
51
+ describe: "Quick start: scaffold the default template (or --template), then install, git, agent tooling, and link --yes. Skips those pickers; link --yes still asks for a project unless one is already linked",
49
52
  type: "boolean",
50
53
  default: false
51
54
  },
@@ -63,8 +66,13 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
63
66
  describe: `Run \`${getCliName()} link\` in the scaffolded directory after installing. In interactive mode this is offered as a prompt; use --no-link to skip without being asked.`,
64
67
  type: "boolean",
65
68
  default: true
69
+ },
70
+ "agent-setup": {
71
+ type: "boolean",
72
+ default: true,
73
+ describe: "After scaffolding, install the Neon plugin or skills and MCP. Use --no-agent-setup to skip"
66
74
  }
67
- }).example("$0 bootstrap my-app", "Create ./my-app from an interactively chosen template").example("$0 bootstrap . --template hono", "Scaffold the Hono template into the current directory").example("$0 bootstrap my-app --default", "Quick start: scaffold the default template and run setup without prompting").example("$0 bootstrap --list-templates --output json", "Print the template catalog as JSON").check((argv) => {
75
+ }).example("$0 bootstrap my-app", "Create ./my-app from an interactively chosen template").example("$0 bootstrap . --template hono", "Scaffold the Hono template into the current directory").example("$0 bootstrap my-app --default", "Skip the pickers; link --yes still asks for a project unless one is already linked").example("$0 bootstrap --list-templates --output json", "Print the template catalog as JSON").check((argv) => {
68
76
  if (argv.agent === true) throw new Error(removedAgent());
69
77
  return true;
70
78
  }).strict();
@@ -163,12 +171,6 @@ const resolveTargetDir = async (props, interactive, template) => {
163
171
  return resolve(process.cwd(), dir === "." ? "" : dir);
164
172
  };
165
173
  const defaultDirName = (template) => template.source.subdir.split("/").pop() || template.id;
166
- /**
167
- * Download and materialize the template into `targetDir`. The actual
168
- * download/extract/write lives in `src/init/bootstrap.ts`, shared with `neon init`
169
- * (exec-bit and symlink fidelity, graceful symlink fallback); here we just
170
- * frame it with progress logging. Returns the number of files written.
171
- */
172
174
  const scaffold = async (template, targetDir) => {
173
175
  log.info("Fetching template \"%s\" from GitHub…", template.id);
174
176
  const filesWritten = await scaffoldTemplate(template, targetDir, { onWarn: (message) => {
@@ -198,16 +200,33 @@ const runPostScaffoldSteps = async (props, targetDir, interactive) => {
198
200
  installed = await runCommand(pm, installArgs(pm), targetDir);
199
201
  }
200
202
  if (props.git && !isGitRepo(targetDir) && await confirm("Initialize a git repository?")) await initGitRepo(targetDir);
201
- if (props.link) {
202
- if (!installed && hasNeonConfig(targetDir)) log.info(`Skipping the Neon link step: \`${getCliName()} link\` reads this project's neon.ts to pull env vars, which needs its dependencies. Run \`${formatInstallCommand(pm)}\`, then \`${getCliName()} link\`.`);
203
- else if (await confirm(`Link this project to a Neon project now? (runs ${getCliName()} link)`)) {
204
- await runNeonLink(props, targetDir);
205
- printNextSteps(targetDir, pm, {
206
- installed,
207
- suggestLink: false
208
- });
209
- return;
210
- }
203
+ const skipLink = shouldSkipLinkForDeps(targetDir, installed);
204
+ if (props.link && skipLink) logSkippedLink(pm);
205
+ const kids = bootstrapChildren(props, targetDir);
206
+ await runScaffoldFollowUp({
207
+ cwd: targetDir,
208
+ yes: false,
209
+ skipAgentSetup: props.agentSetup === false,
210
+ shouldLink: false,
211
+ linkYes: false,
212
+ ...kids,
213
+ ...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
214
+ ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {}
215
+ });
216
+ if (props.link && !skipLink && await confirm(`Link this project to a Neon project now? (runs ${getCliName()} link)`)) {
217
+ await runScaffoldFollowUp({
218
+ cwd: targetDir,
219
+ yes: false,
220
+ skipAgentSetup: true,
221
+ shouldLink: true,
222
+ linkYes: false,
223
+ ...kids
224
+ });
225
+ printNextSteps(targetDir, pm, {
226
+ installed,
227
+ suggestLink: false
228
+ });
229
+ return;
211
230
  }
212
231
  printNextSteps(targetDir, pm, {
213
232
  installed,
@@ -215,20 +234,27 @@ const runPostScaffoldSteps = async (props, targetDir, interactive) => {
215
234
  });
216
235
  };
217
236
  const installPrompt = (inferred) => inferred ? `Install dependencies with ${inferred}?` : "Install dependencies?";
218
- /**
219
- * `--default` quick start: run install + git init without prompting, honoring
220
- * --no-install / --no-git. Linking is intentionally skipped — it needs an
221
- * org/project choice we can't make non-interactively — so we point at it in the
222
- * closing hint instead.
223
- */
237
+ /** `link --yes` still asks for a project unless one is already linked. */
224
238
  const runDefaultSteps = async (props, targetDir, pm) => {
225
- log.info("Quick start (--default): running setup without prompting.");
239
+ log.info("Quick start (--default): skipping the template, install, git, and agent pickers. link --yes still asks for a project unless one is already linked.");
226
240
  let installed = false;
227
241
  if (props.install) installed = await runCommand(pm, installArgs(pm), targetDir);
228
242
  if (props.git && !isGitRepo(targetDir)) await initGitRepo(targetDir);
243
+ const skipLink = shouldSkipLinkForDeps(targetDir, installed);
244
+ if (props.link && skipLink) logSkippedLink(pm);
245
+ await runScaffoldFollowUp({
246
+ cwd: targetDir,
247
+ yes: true,
248
+ skipAgentSetup: props.agentSetup === false,
249
+ shouldLink: props.link && !skipLink,
250
+ linkYes: true,
251
+ ...bootstrapChildren(props, targetDir),
252
+ ...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
253
+ ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {}
254
+ });
229
255
  printNextSteps(targetDir, pm, {
230
256
  installed,
231
- suggestLink: true
257
+ suggestLink: !(props.link && !skipLink)
232
258
  });
233
259
  };
234
260
  const isGitRepo = (dir) => existsSync(join(dir, ".git"));
@@ -239,6 +265,24 @@ const NEON_CONFIG_FILENAMES = [
239
265
  "neon.mjs"
240
266
  ];
241
267
  const hasNeonConfig = (dir) => NEON_CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)));
268
+ const shouldSkipLinkForDeps = (dir, installed) => !installed && hasNeonConfig(dir);
269
+ const logSkippedLink = (pm) => {
270
+ log.info(`Skipping the Neon link step: \`${getCliName()} link\` reads this project's neon.ts to pull env vars, which needs its dependencies. Run \`${formatInstallCommand(pm)}\`, then \`${getCliName()} link\`.`);
271
+ };
272
+ const bootstrapChildren = (props, targetDir) => {
273
+ const explicitKey = props.profile ? "" : credentialInputs().apiKeyFlag;
274
+ return {
275
+ run: props.run ?? spawnCliChild,
276
+ forward: {
277
+ ...props.configDir ? { configDir: props.configDir } : {},
278
+ ...props.profile ? { profile: props.profile } : {},
279
+ apiHost: props.apiHost,
280
+ contextFile: projectContextFile(targetDir, props.contextFile),
281
+ ...props.analytics === false ? { analytics: false } : {}
282
+ },
283
+ ...explicitKey ? { authEnv: { NEON_API_KEY: explicitKey } } : {}
284
+ };
285
+ };
242
286
  /**
243
287
  * Initialize a git repository in the scaffolded directory. Just `git init` — we
244
288
  * deliberately don't auto-commit, both to avoid failing on a machine with no
@@ -279,21 +323,6 @@ const selectPackageManager = async () => {
279
323
  });
280
324
  return pm ?? "npm";
281
325
  };
282
- /**
283
- * Re-invoke this same CLI as `neon link` inside the scaffolded directory, so the
284
- * new project's `.neon` context (and pulled `.env`) land in the right place and
285
- * link's own interactive picker drives org/project/branch selection. Re-execing
286
- * (rather than calling the handler in-process) keeps link running with `cwd` set
287
- * to the target dir, which is where its env pull writes.
288
- */
289
- const runNeonLink = async (props, targetDir) => {
290
- const args = [process.argv[1], "link"];
291
- if (props.configDir) args.push("--config-dir", props.configDir);
292
- if (props.profile) args.push("--profile", props.profile);
293
- args.push("--api-host", props.apiHost, "--output", props.output);
294
- const explicitKey = props.profile ? "" : credentialInputs().apiKeyFlag;
295
- await runCommand(process.execPath, args, targetDir, explicitKey ? { NEON_API_KEY: explicitKey } : void 0);
296
- };
297
326
  const printScaffolded = (template, targetDir) => {
298
327
  log.info("");
299
328
  log.info("Done. Scaffolded \"%s\" into %s.", template.title, isCurrentDir(targetDir) ? "the current directory" : displayDir(targetDir));
@@ -44,6 +44,11 @@ const builder = (argv) => argv.usage("$0 branches <sub-command> [options]").opti
44
44
  } }).middleware(fillSingleProject).middleware((args) => {
45
45
  args.branchId ??= args.id;
46
46
  }).command("list", "List branches", (yargs) => yargs, (args) => list(args)).command("create", "Create a branch", (yargs) => yargs.options({
47
+ secrets: {
48
+ describe: "Include connection credentials in command output. Use --no-secrets to omit them",
49
+ type: "boolean",
50
+ default: true
51
+ },
47
52
  name: branchCreateRequest["branch.name"],
48
53
  parent: {
49
54
  describe: "Parent branch name or id or timestamp or LSN. Defaults to the default branch",
@@ -220,21 +225,31 @@ const create = async (props) => {
220
225
  }));
221
226
  if (branches.find((b) => b.id === data.branch.parent_id)?.protected) log.warning("The parent branch is protected; a unique role password has been generated for the new branch.");
222
227
  const out = writer(props);
223
- out.write(data.branch, {
224
- fields: BRANCH_FIELDS,
225
- title: "branch",
226
- emptyMessage: "No branches have been found."
227
- });
228
- if (data.endpoints?.length > 0) out.write(data.endpoints, {
229
- fields: ["id", "created_at"],
230
- title: "endpoints",
231
- emptyMessage: "No endpoints have been found."
232
- });
233
- if (data.connection_uris?.length) out.write(data.connection_uris, {
234
- fields: ["connection_uri"],
235
- title: "connection_uris",
236
- emptyMessage: "No connection uris have been found"
228
+ const endpoints = data.endpoints ?? [];
229
+ const connectionUris = data.connection_uris ?? [];
230
+ const writeEndpoints = endpoints.length > 0;
231
+ const writeUris = props.secrets && connectionUris.length > 0;
232
+ if (!props.secrets && (props.output === "json" || props.output === "yaml") && !writeEndpoints) out.write({ branch: data.branch }, {
233
+ fields: ["branch"],
234
+ title: "branch"
237
235
  });
236
+ else {
237
+ out.write(data.branch, {
238
+ fields: BRANCH_FIELDS,
239
+ title: "branch",
240
+ emptyMessage: "No branches have been found."
241
+ });
242
+ if (writeEndpoints) out.write(endpoints, {
243
+ fields: ["id", "created_at"],
244
+ title: "endpoints",
245
+ emptyMessage: "No endpoints have been found."
246
+ });
247
+ if (writeUris) out.write(connectionUris, {
248
+ fields: ["connection_uri"],
249
+ title: "connection_uris",
250
+ emptyMessage: "No connection uris have been found"
251
+ });
252
+ }
238
253
  out.end();
239
254
  if (props.psql) {
240
255
  if (!data.connection_uris?.length) throw new Error(`Branch ${data.branch.id} doesn't have a connection uri`);
@@ -5,7 +5,7 @@ import { applyContext, contextBranch, ensureGitignored, readContextFile } from "
5
5
  import { declaredNeonServices } from "../config_services.js";
6
6
  import { NEON_SERVICES, deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
7
7
  import { mergeEnvFile, resolveEnvFilePath } from "../env_file.js";
8
- import { t as credentialInputs } from "../_chunks/auth_selection-ktL6uoEe.js";
8
+ import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
9
9
  import { ClaimableClient, ClaimableServiceError } from "../claimable/api.js";
10
10
  import { assertionHasExpired, listClaimableCredentials, readClaimableCredentials, removeClaimableCredentials, resolveClaimableContext, writeClaimableCredentials } from "../claimable/state.js";
11
11
  import { writer } from "../writer.js";