neon 4.10.1 → 4.11.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.
package/README.md CHANGED
@@ -683,7 +683,7 @@ When a package cannot be bundled — a native addon with no esbuild loader, or a
683
683
 
684
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
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.
686
+ After scaffolding, an interactive terminal asks about dependency install, git, agent tooling (the Neon plugin, or skills and MCP separately — never both), and `neon link` before running those steps. Dependency install is last, except when the template has a `neon.ts` and you chose to link — then install runs first so link can pull env. `--default` / `-y` skips the template, install, git, and agent pickers, then installs agent tooling for project folders, else the host CLI agent. If none are found, it exits: pass `--agent <name>`, run from a supported agent, or omit `--default` / `-y` in a terminal to pick. `--agent` / `-a` names coding agents, skips agent selection, and is forwarded to `plugins`, or to `skills` and `mcp`, not both. `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.
687
687
 
688
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>`.
689
689
 
@@ -694,6 +694,9 @@ $ neon bootstrap my-app
694
694
  # Scaffold a specific template into the current directory (skips the template picker)
695
695
  $ neon bootstrap . --template hono
696
696
 
697
+ # Skip agent selection; install the plugin for those agents
698
+ $ neon bootstrap my-app --agent cursor --agent claude-code
699
+
697
700
  # List templates
698
701
  $ neon bootstrap --list-templates
699
702
 
@@ -707,7 +710,7 @@ The target directory must be empty unless you pass `--force` (a lone `.git` is i
707
710
 
708
711
  `neon init` sets up this directory for Neon.
709
712
 
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.
713
+ 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. Interactive bootstrap prints a NEON banner, asks every setup question, then runs the work — dependency install last, except when a `neon.ts` needs deps before `link`.
711
714
 
712
715
  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).
713
716
 
@@ -716,11 +719,16 @@ In an interactive terminal it offers one of: the Neon plugin (`neon plugins`), s
716
719
  ```bash
717
720
  $ neon init
718
721
  $ neon init -y
722
+ $ neon init --agent cursor --agent claude-code
719
723
  ```
720
724
 
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.
725
+ Without a TTY, pass `-y`. `--agent` skips agent selection but does not replace `-y` for link or templates.
726
+
727
+ `-y` skips the template picker and the agent-setup offer. Empty dir: `bootstrap --default`. Existing app: plugin when Cursor, Claude Code, or Codex is in project folders, else the host CLI agent; otherwise skills and MCP. If none are found, it exits: pass `--agent <name>`, run from a supported agent, or omit `-y` in a terminal to pick. VS Code, GitHub Copilot CLI, and Grok only take the plugin user-level (`neon plugins --global`), so `-y` uses skills and MCP for those.
728
+
729
+ `--agent` / `-a` (repeatable) names coding agents and skips agent selection, interactive or with `-y`. Init forwards those names to `plugins`, or to `skills` and `mcp`, not both.
722
730
 
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.
731
+ `-y` forwards `-y` to `plugins` or `skills`/`mcp`, `--default` to `bootstrap`, `--yes` to `link`, and `--services none` to `config init`. `--agent` is forwarded with them. `mcp -y` is the global install. `link --yes` only skips the "already linked" confirmation; it still asks for a project unless one is already linked.
724
732
 
725
733
  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
734
 
@@ -734,17 +742,18 @@ A failed step stops the rest. `--profile` and `--config-dir` are forwarded to ea
734
742
  # Interactive: global or project, then agents, then API key or OAuth, then confirm.
735
743
  $ neon mcp
736
744
 
737
- # Skip prompts. Global config, every globally detected agent, reuse or mint an API key.
745
+ # Skip prompts. Global config, installed apps else the host CLI agent, reuse or mint an API key.
738
746
  $ neon mcp -y
739
747
 
740
748
  # OAuth: no API key minted. The agent prompts for Neon sign-in on first use.
741
749
  $ neon mcp --oauth
742
750
 
751
+ # Named agents.
752
+ $ neon mcp --agent cursor --agent claude-code
753
+
743
754
  # Project-level config. A minted key is still account-wide unless a project is pinned.
744
755
  $ neon mcp --project
745
756
 
746
- $ neon mcp --agent cursor --agent claude-code
747
-
748
757
  # Hide write tools. Does not change the minted key.
749
758
  $ neon mcp --read-only
750
759
 
@@ -757,9 +766,9 @@ $ neon mcp --category querying --category schema
757
766
 
758
767
  On a TTY the command asks for config location (global is the default), then agents, then API key vs OAuth, then a summary to confirm before it writes. Detected agents start selected: globally installed agents or project-folder markers such as `.cursor` when the install is project.
759
768
 
760
- `-y` skips those questions. `neon mcp -y` writes `https://mcp.neon.tech/mcp` into global config for every globally detected agent, reuses an existing Neon MCP API key or mints an account-wide key, leaves write tools enabled, exposes every tool category, and does not pin a project (including from `.neon`). `--project`, `--oauth` and `--agent` skip the question they answer and still apply with `-y`. `--read-only` and `--category` are flags only and are never prompted. A linked project-folder install asks whether to pin MCP tools to that `.neon` project (`?projectId=`). If you pin and selected API-key auth, the minted key is limited to that project too. An unlinked project folder does not ask. Global installs never add that param unless you pass `--project-id`. Without a TTY, pass `-y` to mint into every detected agent, `--agent` to name the agents or `--oauth` to write the URL only. `neon mcp --help` lists the server URL, those `-y` defaults, the supported `--agent` names, and the `--category` values.
769
+ `-y` skips those questions. `neon mcp -y` writes `https://mcp.neon.tech/mcp` into global config for globally installed apps, else the host CLI agent, reuses an existing Neon MCP API key or mints an account-wide key, leaves write tools enabled, exposes every tool category, and does not pin a project (including from `.neon`). `--agent`, `--project`, `--oauth`, `--read-only`, `--project-id` and `--category` still apply with `-y`. `--read-only` and `--category` are flags only and are never prompted. A linked project-folder install asks whether to pin MCP tools to that `.neon` project (`?projectId=`). If you pin and selected API-key auth, the minted key is limited to that project too. An unlinked project folder does not ask. Global installs never add that param unless you pass `--project-id`. Without a TTY, pass `-y` to mint into every detected agent, `--agent <name>` to name them, or `--oauth` to write the URL only. If `-y` finds no agent, it exits: pass `--agent <name>`, run from a supported agent, or omit `-y` in a terminal to pick. `neon mcp --help` lists the server URL, those `-y` defaults, the supported agent names, and the `--category` values.
761
770
 
762
- `--agent` names: `antigravity`, `cline`, `cline-cli`, `claude-code`, `codex`, `cursor`, `gemini-cli`, `goose`, `github-copilot-cli`, `grok-build`, `mcporter`, `opencode`, `vscode`, `windsurf`, `zed`. Project installs drop `antigravity`, `cline`, `cline-cli`, `goose` and `windsurf`. `claude-desktop` is a known name that is then skipped.
771
+ Supported agents: `antigravity`, `cline`, `cline-cli`, `claude-code`, `codex`, `cursor`, `gemini-cli`, `goose`, `github-copilot-cli`, `grok-build`, `mcporter`, `opencode`, `vscode`, `windsurf`, `zed`. Project installs drop `antigravity`, `cline`, `cline-cli`, `goose` and `windsurf`. `claude-desktop` is a known name that is then skipped.
763
772
 
764
773
  The default mints an account-wide API key (or reuses the Bearer already configured for Neon at `https://mcp.neon.tech/mcp`) and writes it into each selected agent's config. That key reaches everything the account can, in every organization. Revoke it with `neon api-keys revoke <id>`. `--oauth` writes the URL with no `Authorization` header; the agent signs in on first use. `--project` writes into the project config (`.cursor/mcp.json` and similar). `--read-only` adds `?readonly=true`. `--project-id` adds `?projectId=` and, when a key is minted, limits that key to the named project. Accepting the linked-project pin does the same. Revoke a project-scoped key with `neon api-keys revoke <id> --org-id <org>`. A reused Bearer keeps the scope it already has. `--category` adds `?category=` (repeatable or comma-separated: `projects`, `branches`, `schema`, `querying`, `neon_auth`, `data_api`, `observability`, `docs`). `--read-only` and `--category` restrict which MCP tools the server exposes; they do not change what the minted key can do.
765
774
 
@@ -771,12 +780,14 @@ The default mints an account-wide API key (or reuses the Bearer already configur
771
780
  # Interactive: this directory, then agents, then skills, then confirm.
772
781
  $ neon skills
773
782
 
774
- # Skip prompts. This directory, every detected agent, the default skills.
783
+ # Skip prompts. This directory, detected agents (project folders, else the host CLI agent), the default skills.
775
784
  $ neon skills -y
776
785
 
777
- $ neon skills -s neon -s neon-ai-gateway --agent cursor
786
+ # Named skills into detected agents.
787
+ $ neon skills -y -s neon -s neon-ai-gateway
778
788
 
779
- $ neon skills --agent cursor --agent claude-code
789
+ # Named skills into a named agent.
790
+ $ neon skills -s neon -s neon-ai-gateway --agent cursor
780
791
 
781
792
  # User-level skills.
782
793
  $ neon skills --global
@@ -789,11 +800,11 @@ $ neon skills update --global -y
789
800
 
790
801
  On a TTY the command asks which agents and which skills, then shows a summary to confirm. Detected agents start selected from project-folder markers such as `.cursor`. Default skills start selected. `neon-postgres-agent-platforms` is offered and starts unselected.
791
802
 
792
- `-y` skips those questions and installs the default skills. `--skill` / `-s` names specific skills and skips the skill picker. `--agent` and `--global` still apply. Without a TTY, pass `-y` or `--skill`. `--agent` alone is not enough.
803
+ `-y` skips those questions and installs the default skills into detected agents: project-folder markers such as `.cursor`, else the agent driving the CLI. `--agent` / `-a` names coding agents and skips the agent picker. `--skill` / `-s` names specific skills and skips the skill picker; it does not select agents. `--global -y` uses installed apps, else the host CLI agent. Without a TTY, pass `-y`, or `--skill <name>` (add `--agent <name>` to name agents). If `-y` finds no agent, it exits: pass `--agent <name>`, run from a supported agent, or omit `-y` in a terminal to pick.
793
804
 
794
- `--skill` names by source repo: `neondatabase/agent-skills` (`claimable-postgres`, `neon`, `neon-ai-gateway`, `neon-functions`, `neon-object-storage`, `neon-postgres`, `neon-postgres-branches`, `neon-postgres-egress-optimizer`); `neondatabase/neon-for-agent-platforms` (`neon-postgres-agent-platforms`).
805
+ `--skill` names by source repo: `neondatabase/agent-skills` (`neon`, `neon-ai-gateway`, `neon-functions`, `neon-object-storage`, `neon-postgres`, `neon-postgres-branches`, `neon-postgres-egress-optimizer`); `neondatabase/neon-for-agent-platforms` (`neon-postgres-agent-platforms`).
795
806
 
796
- `--agent` names match `neon mcp`, minus agents that cannot install skills: `antigravity`, `cline`, `cline-cli`, `claude-code`, `claude-desktop`, `codex`, `cursor`, `gemini-cli`, `goose`, `github-copilot-cli`, `grok-build`, `opencode`, `vscode`, `windsurf`, `zed`. `mcporter` is a known MCP name that is then skipped. `neon skills --help` lists the same skill and agent values, and that `-y` leaves out `neon-postgres-agent-platforms`.
807
+ Supported agents match `neon mcp`, minus agents that cannot install skills: `antigravity`, `cline`, `cline-cli`, `claude-code`, `claude-desktop`, `codex`, `cursor`, `gemini-cli`, `goose`, `github-copilot-cli`, `grok-build`, `opencode`, `vscode`, `windsurf`, `zed`. `mcporter` is a known MCP name that is then skipped. `neon skills --help` lists the same skill and agent values, and that `-y` leaves out `neon-postgres-agent-platforms`.
797
808
 
798
809
  ## Install the Neon plugin (`plugins`)
799
810
 
@@ -803,27 +814,27 @@ On a TTY the command asks which agents and which skills, then shows a summary to
803
814
  # Interactive: agents, then confirm.
804
815
  $ neon plugins
805
816
 
806
- # Skip prompts. Every detected agent.
817
+ # Skip prompts. Detected agents (project folders, else the host CLI agent).
807
818
  $ neon plugins -y
808
819
 
820
+ # Named agents.
809
821
  $ neon plugins --agent cursor --agent claude-code
810
822
 
811
823
  # User-level install.
812
824
  $ neon plugins --global
813
- $ neon plugins --global --agent vscode
814
825
  ```
815
826
 
816
827
  On a TTY the command asks which agents, then shows a summary to confirm. Detected agents start selected from project-folder markers such as `.cursor`. There is one plugin (`neon-postgres`); there is no plugin picker and no `update` subcommand.
817
828
 
818
- `-y` skips those questions and installs into every detected agent. `--agent` names specific agents and skips the agent picker. Without a TTY, pass `-y` or `--agent`. `--agent` alone is enough because the plugin is fixed.
829
+ `-y` skips those questions and installs into detected agents: project-folder markers such as `.cursor`, else the agent driving the CLI. `--agent` / `-a` names coding agents and skips the agent picker. `--global -y` uses installed apps, else the host CLI agent. Without a TTY, pass `-y` or `--agent <name>`. If `-y` finds no agent, it exits: pass `--agent <name>`, run from a supported agent, or omit `-y` in a terminal to pick.
819
830
 
820
- Default scope is `project`. `--global` is `user`. On macOS and Linux, Cursor and Claude Code store the plugin cache under `~/.claude/plugins`; on Windows, Cursor installs into Cursor extensions. `project` vs `user` is the scope field the plugins CLI records, not a directory in the repo. VS Code, GitHub Copilot CLI, and Grok Build only install user-level: they are skipped at the default scope with a warning, and `--agent vscode` without `--global` fails if nothing else is selected.
831
+ Default scope is `project`. `--global` is `user`. On macOS and Linux, Cursor and Claude Code store the plugin cache under `~/.claude/plugins`; on Windows, Cursor installs into Cursor extensions. `project` vs `user` is the scope field the plugins CLI records, not a directory in the repo. VS Code, GitHub Copilot CLI, and Grok Build only install user-level: they are skipped at the default scope with a warning, and a VS Code-only project fails if nothing else is selected. Pass `--global` for those.
821
832
 
822
- `--agent` names with a plugins mapping: `claude-code`, `claude-desktop`, `codex`, `cursor`, `github-copilot-cli`, `grok-build`, `vscode`. `claude-desktop` installs as Claude Code; naming both produces one install and lists both names in the table. `mcporter` is a known MCP name that is then skipped.
833
+ Supported agents with a plugins mapping: `claude-code`, `claude-desktop`, `codex`, `cursor`, `github-copilot-cli`, `grok-build`, `vscode`. `claude-desktop` installs as Claude Code; detecting both produces one install and lists both names in the table. `mcporter` is a known MCP name that is then skipped.
823
834
 
824
- The plugins CLI installs every plugin it finds in the Neon plugin package. Today that is `neon-postgres` from `neondatabase/agent-skills`. It includes the Neon MCP server (`https://mcp.neon.tech/mcp`) and these skills: `neon`, `neon-ai-gateway`, `neon-functions`, `neon-object-storage`, `neon-postgres`, `neon-postgres-branches`, `neon-postgres-egress-optimizer`. It does not include `claimable-postgres` or `neon-postgres-agent-platforms`.
835
+ The plugins CLI installs every plugin it finds in the Neon plugin package. Today that is `neon-postgres` from `neondatabase/agent-skills`. It includes the Neon MCP server (`https://mcp.neon.tech/mcp`) and these skills: `neon`, `neon-ai-gateway`, `neon-functions`, `neon-object-storage`, `neon-postgres`, `neon-postgres-branches`, `neon-postgres-egress-optimizer`. It does not include `neon-postgres-agent-platforms`.
825
836
 
826
- `neon plugins --help` lists the plugin, those contents, and the supported `--agent` names.
837
+ `neon plugins --help` lists the plugin, those contents, and the supported agent names.
827
838
 
828
839
  ## Snapshots (`snapshots`)
829
840
 
@@ -6,8 +6,12 @@ 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
8
  import { spawnCliChild } from "../init/child.js";
9
- import { projectContextFile } from "../init/plan.js";
10
- import { runScaffoldFollowUp } from "../init/tooling.js";
9
+ import { agentSetupDoneLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
10
+ import { coerceAgentFlag } from "../utils/agent_flag.js";
11
+ import { assertNamedAgentTooling, chooseYesAgentTooling, initPluginAgents, initSkillsMcpAgents, postScaffoldActions, projectContextFile, resolveNamedAgents } from "../init/plan.js";
12
+ import { pickAgentSetupInteractively } from "../init/wizard.js";
13
+ import { runAgentTooling, runInitSteps } from "../init/tooling.js";
14
+ import { helpCsv, helpEpilogue } from "../utils/help_text.js";
11
15
  import { formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
12
16
  import { existsSync } from "node:fs";
13
17
  import { join, relative, resolve } from "node:path";
@@ -20,7 +24,6 @@ var bootstrap_exports = /* @__PURE__ */ __exportAll({
20
24
  describe: () => describe,
21
25
  handler: () => handler
22
26
  });
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\`.`;
24
27
  const command = "bootstrap [directory]";
25
28
  const describe = "Scaffold a new project from a Neon starter template, then install agent tooling and link a Neon project";
26
29
  const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").positional("directory", {
@@ -42,13 +45,9 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
42
45
  type: "boolean",
43
46
  default: false
44
47
  },
45
- agent: {
46
- hidden: true,
47
- type: "boolean"
48
- },
49
48
  default: {
50
49
  alias: "y",
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",
50
+ describe: "Quick start: scaffold the default template (or --template), then install, git, agent tooling (project folders, else the host CLI agent; if none, pass --agent or omit --default in a terminal), and link --yes. Skips those pickers; link --yes still asks for a project unless one is already linked",
52
51
  type: "boolean",
53
52
  default: false
54
53
  },
@@ -63,7 +62,7 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
63
62
  default: true
64
63
  },
65
64
  link: {
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.`,
65
+ describe: `Run \`${getCliName()} link\` after scaffolding. Templates with neon.ts link after install so env pull works; otherwise link runs before install. In interactive mode this is offered as a prompt; use --no-link to skip without being asked.`,
67
66
  type: "boolean",
68
67
  default: true
69
68
  },
@@ -71,11 +70,15 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
71
70
  type: "boolean",
72
71
  default: true,
73
72
  describe: "After scaffolding, install the Neon plugin or skills and MCP. Use --no-agent-setup to skip"
73
+ },
74
+ agent: {
75
+ alias: "a",
76
+ type: "array",
77
+ string: true,
78
+ describe: "Coding agent to install into (repeatable). Forwarded to plugins, or to skills and mcp. Skips agent selection. Values listed below",
79
+ coerce: coerceAgentFlag
74
80
  }
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) => {
76
- if (argv.agent === true) throw new Error(removedAgent());
77
- return true;
78
- }).strict();
81
+ }).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").example("$0 bootstrap my-app --agent cursor --agent claude-code", "Skip agent selection; install the plugin for those agents").epilogue(helpEpilogue("--agent / -a is forwarded to plugins, or to skills and mcp, not both. It skips agent selection, including with --default.", helpCsv("Plugin agents", initPluginAgents()), helpCsv("Skills and MCP agents", initSkillsMcpAgents()))).strict();
79
82
  const handler = async (props) => {
80
83
  if (props.listTemplates) {
81
84
  const templates = await fetchTemplates();
@@ -99,6 +102,12 @@ const handler = async (props) => {
99
102
  }
100
103
  return;
101
104
  }
105
+ if (shouldPrintInitBanner(props.default)) printInitBanner();
106
+ const named = resolveNamedAgents(props.agent ?? []);
107
+ if (props.agentSetup !== false) assertNamedAgentTooling(named, "bootstrap", {
108
+ ...props.directory !== void 0 && props.directory.length > 0 ? { directory: props.directory } : {},
109
+ ...props.default ? { yes: true } : {}
110
+ });
102
111
  const templates = await resolveTemplateList(props);
103
112
  const interactive = !props.default && Boolean(process.stdout.isTTY) && !isCi();
104
113
  const template = await resolveSelectedTemplate(props, interactive, templates);
@@ -106,7 +115,7 @@ const handler = async (props) => {
106
115
  ensureTargetUsable(targetDir, props.force);
107
116
  await scaffold(template, targetDir);
108
117
  printScaffolded(template, targetDir);
109
- await runPostScaffoldSteps(props, targetDir, interactive);
118
+ await runPostScaffoldSteps(props, targetDir, interactive, template, named);
110
119
  };
111
120
  /**
112
121
  * The template list to choose from. When --template is given we try the
@@ -179,83 +188,155 @@ const scaffold = async (template, targetDir) => {
179
188
  log.info("Scaffolded %d files into %s.", filesWritten, targetDir);
180
189
  return filesWritten;
181
190
  };
182
- const runPostScaffoldSteps = async (props, targetDir, interactive) => {
191
+ const runPostScaffoldSteps = async (props, targetDir, interactive, template, named) => {
183
192
  const inferred = inferPackageManager(targetDir);
184
193
  const defaultPm = resolvePackageManager(targetDir);
194
+ const neonConfig = hasNeonConfig(targetDir);
185
195
  if (props.default) {
186
- await runDefaultSteps(props, targetDir, defaultPm);
196
+ await runDefaultSteps(props, targetDir, defaultPm, neonConfig, template, named);
187
197
  return;
188
198
  }
189
199
  if (!interactive) {
190
- printNextSteps(targetDir, defaultPm, {
200
+ printDoneSummary({
201
+ heading: "Project scaffolded.",
202
+ template,
203
+ targetDir,
204
+ pm: defaultPm,
191
205
  installed: false,
192
- suggestLink: true
206
+ installFailed: false,
207
+ gitFailed: false,
208
+ git: false,
209
+ agentSetup: "skip",
210
+ linked: false,
211
+ skippedLinkForDeps: false,
212
+ suggestLink: true,
213
+ agentsRan: false
193
214
  });
194
215
  return;
195
216
  }
196
217
  let pm = defaultPm;
197
- let installed = false;
198
- if (props.install && await confirm(installPrompt(inferred))) {
199
- pm = inferred ?? await selectPackageManager();
200
- installed = await runCommand(pm, installArgs(pm), targetDir);
201
- }
202
- if (props.git && !isGitRepo(targetDir) && await confirm("Initialize a git repository?")) await initGitRepo(targetDir);
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,
218
+ const wantInstall = props.install && await confirm(installPrompt(inferred));
219
+ if (wantInstall) pm = inferred ?? await selectPackageManager();
220
+ const wantGit = props.git && !isGitRepo(targetDir) && await confirm("Initialize a git repository?");
221
+ const agentSetup = props.agentSetup === false ? "skip" : named.length > 0 ? chooseYesAgentTooling(named).setup : await (props.pickAgentSetup ?? pickAgentSetupInteractively)();
222
+ const canLink = props.link && !(neonConfig && !wantInstall);
223
+ if (props.link && !canLink) logSkippedLink(pm);
224
+ const wantLink = canLink && await confirm(`Link this project to a Neon project now? (runs ${getCliName()} link)`);
225
+ const outcome = await executePostScaffold(props, targetDir, {
208
226
  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 } : {}
227
+ lockAgentSetup: named.length === 0,
228
+ pm,
229
+ git: wantGit,
230
+ agentSetup,
231
+ install: wantInstall,
232
+ link: wantLink,
233
+ hasNeonConfig: neonConfig,
234
+ named
215
235
  });
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;
230
- }
231
- printNextSteps(targetDir, pm, {
232
- installed,
233
- suggestLink: true
236
+ finishPostScaffold({
237
+ heading: "Project scaffolded.",
238
+ template,
239
+ targetDir,
240
+ pm,
241
+ ...outcome,
242
+ suggestLink: !outcome.linked
234
243
  });
235
244
  };
236
245
  const installPrompt = (inferred) => inferred ? `Install dependencies with ${inferred}?` : "Install dependencies?";
237
246
  /** `link --yes` still asks for a project unless one is already linked. */
238
- const runDefaultSteps = async (props, targetDir, pm) => {
247
+ const runDefaultSteps = async (props, targetDir, pm, neonConfig, template, named) => {
239
248
  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.");
240
- let installed = false;
241
- if (props.install) installed = await runCommand(pm, installArgs(pm), targetDir);
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,
249
+ const wantGit = props.git && !isGitRepo(targetDir);
250
+ const agentSetup = props.agentSetup === false ? "skip" : named.length > 0 ? chooseYesAgentTooling(named).setup : "skills-mcp";
251
+ const outcome = await executePostScaffold(props, targetDir, {
247
252
  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 } : {}
253
+ lockAgentSetup: false,
254
+ pm,
255
+ git: wantGit,
256
+ agentSetup,
257
+ install: props.install,
258
+ link: props.link,
259
+ hasNeonConfig: neonConfig,
260
+ named
254
261
  });
255
- printNextSteps(targetDir, pm, {
256
- installed,
257
- suggestLink: !(props.link && !skipLink)
262
+ finishPostScaffold({
263
+ heading: "Project scaffolded.",
264
+ template,
265
+ targetDir,
266
+ pm,
267
+ ...outcome,
268
+ suggestLink: !outcome.linked
269
+ });
270
+ };
271
+ const executePostScaffold = async (props, targetDir, choices) => {
272
+ const kids = bootstrapChildren(props, targetDir);
273
+ let installed = false;
274
+ let installFailed = false;
275
+ let gitFailed = false;
276
+ let git = false;
277
+ let linked = false;
278
+ let skippedLinkForDeps = false;
279
+ let agentSetup = choices.agentSetup;
280
+ let agentsRan = false;
281
+ const actions = postScaffoldActions({
282
+ git: choices.git,
283
+ agentSetup: choices.agentSetup,
284
+ install: choices.install,
285
+ link: choices.link,
286
+ hasNeonConfig: choices.hasNeonConfig
258
287
  });
288
+ for (const action of actions) {
289
+ if (action === "git") {
290
+ git = await initGitRepo(targetDir);
291
+ if (!git) {
292
+ gitFailed = true;
293
+ break;
294
+ }
295
+ continue;
296
+ }
297
+ if (action === "agent") {
298
+ agentSetup = await runAgentTooling({
299
+ cwd: targetDir,
300
+ yes: choices.yes,
301
+ ...choices.lockAgentSetup ? { agentSetup: choices.agentSetup } : {},
302
+ ...kids,
303
+ ...choices.named.length > 0 ? { agents: choices.named } : {},
304
+ ...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
305
+ ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {},
306
+ ...props.detectProjectAgents ? { detectProjectAgents: props.detectProjectAgents } : {},
307
+ ...props.detectAgent ? { detectAgent: props.detectAgent } : {},
308
+ command: "bootstrap"
309
+ });
310
+ agentsRan = true;
311
+ continue;
312
+ }
313
+ if (action === "install") {
314
+ installed = await runCommand(choices.pm, installArgs(choices.pm), targetDir);
315
+ if (!installed) installFailed = true;
316
+ continue;
317
+ }
318
+ if (action !== "link") throw new Error(`Unhandled post-scaffold action: ${action}`);
319
+ if (choices.hasNeonConfig && !installed) {
320
+ skippedLinkForDeps = true;
321
+ logSkippedLink(choices.pm);
322
+ continue;
323
+ }
324
+ await runInitSteps([choices.yes ? ["link", "--yes"] : ["link"]], {
325
+ cwd: targetDir,
326
+ ...kids
327
+ });
328
+ linked = true;
329
+ }
330
+ return {
331
+ installed,
332
+ installFailed,
333
+ gitFailed,
334
+ git,
335
+ linked,
336
+ skippedLinkForDeps,
337
+ agentSetup,
338
+ agentsRan
339
+ };
259
340
  };
260
341
  const isGitRepo = (dir) => existsSync(join(dir, ".git"));
261
342
  const NEON_CONFIG_FILENAMES = [
@@ -265,7 +346,6 @@ const NEON_CONFIG_FILENAMES = [
265
346
  "neon.mjs"
266
347
  ];
267
348
  const hasNeonConfig = (dir) => NEON_CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)));
268
- const shouldSkipLinkForDeps = (dir, installed) => !installed && hasNeonConfig(dir);
269
349
  const logSkippedLink = (pm) => {
270
350
  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
351
  };
@@ -288,9 +368,7 @@ const bootstrapChildren = (props, targetDir) => {
288
368
  * deliberately don't auto-commit, both to avoid failing on a machine with no
289
369
  * git identity configured and to leave the first commit to the user.
290
370
  */
291
- const initGitRepo = async (dir) => {
292
- await runCommand("git", ["init"], dir);
293
- };
371
+ const initGitRepo = async (dir) => runCommand("git", ["init"], dir);
294
372
  const confirm = async (message) => {
295
373
  const { value } = await prompts({
296
374
  onState: onPromptState,
@@ -324,22 +402,56 @@ const selectPackageManager = async () => {
324
402
  return pm ?? "npm";
325
403
  };
326
404
  const printScaffolded = (template, targetDir) => {
327
- log.info("");
328
- log.info("Done. Scaffolded \"%s\" into %s.", template.title, isCurrentDir(targetDir) ? "the current directory" : displayDir(targetDir));
405
+ log.info("Scaffolded \"%s\" into %s.", template.title, isCurrentDir(targetDir) ? "the current directory" : displayDir(targetDir));
329
406
  };
330
- /**
331
- * The closing "Next steps" hint. Skips `cd` for the current directory, omits
332
- * the install line once deps are in, and only nudges `neon link` when linking
333
- * wasn't already offered/run — so the user never sees a step they just did.
334
- */
335
- const printNextSteps = (targetDir, pm, opts) => {
336
- log.info("");
337
- log.info("Next steps:");
338
- if (!isCurrentDir(targetDir)) log.info(" cd %s", displayDir(targetDir));
339
- if (!opts.installed) log.info(" %s", formatInstallCommand(pm));
340
- if (opts.suggestLink) log.info(` ${getCliName()} link`);
341
- log.info(" See the README to run it.");
342
- log.info("");
407
+ const printDoneSummary = (input) => {
408
+ const unfinished = input.installFailed || input.gitFailed;
409
+ const heading = unfinished ? "Setup did not finish." : input.heading;
410
+ const deps = input.installFailed ? "install failed" : input.installed ? `installed with ${input.pm}` : "skipped";
411
+ const git = input.gitFailed ? "init failed" : input.git ? "initialized" : "skipped";
412
+ const project = input.linked ? "linked" : input.skippedLinkForDeps ? "skipped (needs dependencies)" : "not linked";
413
+ const next = [];
414
+ if (!isCurrentDir(input.targetDir)) next.push(`cd ${displayDir(input.targetDir)}`);
415
+ if (!input.installed) next.push(formatInstallCommand(input.pm));
416
+ if (input.suggestLink) next.push(`${getCliName()} link`);
417
+ if (!unfinished) next.push("See the README to run it.");
418
+ printInitDone(formatInitDone({
419
+ heading,
420
+ rows: [
421
+ {
422
+ label: "Template",
423
+ value: input.template.title
424
+ },
425
+ {
426
+ label: "Directory",
427
+ value: displayDir(input.targetDir)
428
+ },
429
+ {
430
+ label: "Dependencies",
431
+ value: deps
432
+ },
433
+ {
434
+ label: "Git",
435
+ value: git
436
+ },
437
+ {
438
+ label: "Agents",
439
+ value: agentSetupDoneLabel({
440
+ setup: input.agentSetup,
441
+ ran: input.agentsRan
442
+ })
443
+ },
444
+ {
445
+ label: "Project",
446
+ value: project
447
+ }
448
+ ],
449
+ next
450
+ }));
451
+ };
452
+ const finishPostScaffold = (input) => {
453
+ printDoneSummary(input);
454
+ if (input.installFailed || input.gitFailed) throw new Error("Setup did not finish.");
343
455
  };
344
456
  const isCurrentDir = (targetDir) => relative(process.cwd(), targetDir) === "";
345
457
  /**
@@ -208,7 +208,6 @@ const resolveProjectId = async (props) => {
208
208
  if (!runLink) throw new Error("Aborted: no project selected. Re-run with --project-id or link a project first.");
209
209
  await handler$1({
210
210
  ...props,
211
- agent: false,
212
211
  yes: false,
213
212
  clear: false,
214
213
  checks: true