neon 4.10.2 → 4.11.1

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 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 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
 
@@ -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
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
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
 
@@ -7,9 +7,11 @@ 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
9
  import { agentSetupDoneLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
10
- import { postScaffoldActions, projectContextFile } from "../init/plan.js";
10
+ import { coerceAgentFlag } from "../utils/agent_flag.js";
11
+ import { assertNamedAgentTooling, chooseYesAgentTooling, initPluginAgents, initSkillsMcpAgents, postScaffoldActions, projectContextFile, resolveNamedAgents } from "../init/plan.js";
11
12
  import { pickAgentSetupInteractively } from "../init/wizard.js";
12
13
  import { runAgentTooling, runInitSteps } from "../init/tooling.js";
14
+ import { helpCsv, helpEpilogue } from "../utils/help_text.js";
13
15
  import { formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
14
16
  import { existsSync } from "node:fs";
15
17
  import { join, relative, resolve } from "node:path";
@@ -22,7 +24,6 @@ var bootstrap_exports = /* @__PURE__ */ __exportAll({
22
24
  describe: () => describe,
23
25
  handler: () => handler
24
26
  });
25
- 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\`.`;
26
27
  const command = "bootstrap [directory]";
27
28
  const describe = "Scaffold a new project from a Neon starter template, then install agent tooling and link a Neon project";
28
29
  const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").positional("directory", {
@@ -44,13 +45,9 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
44
45
  type: "boolean",
45
46
  default: false
46
47
  },
47
- agent: {
48
- hidden: true,
49
- type: "boolean"
50
- },
51
48
  default: {
52
49
  alias: "y",
53
- 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",
54
51
  type: "boolean",
55
52
  default: false
56
53
  },
@@ -73,11 +70,15 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
73
70
  type: "boolean",
74
71
  default: true,
75
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
76
80
  }
77
- }).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) => {
78
- if (argv.agent === true) throw new Error(removedAgent());
79
- return true;
80
- }).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();
81
82
  const handler = async (props) => {
82
83
  if (props.listTemplates) {
83
84
  const templates = await fetchTemplates();
@@ -102,6 +103,11 @@ const handler = async (props) => {
102
103
  return;
103
104
  }
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
+ });
105
111
  const templates = await resolveTemplateList(props);
106
112
  const interactive = !props.default && Boolean(process.stdout.isTTY) && !isCi();
107
113
  const template = await resolveSelectedTemplate(props, interactive, templates);
@@ -109,7 +115,7 @@ const handler = async (props) => {
109
115
  ensureTargetUsable(targetDir, props.force);
110
116
  await scaffold(template, targetDir);
111
117
  printScaffolded(template, targetDir);
112
- await runPostScaffoldSteps(props, targetDir, interactive, template);
118
+ await runPostScaffoldSteps(props, targetDir, interactive, template, named);
113
119
  };
114
120
  /**
115
121
  * The template list to choose from. When --template is given we try the
@@ -182,12 +188,12 @@ const scaffold = async (template, targetDir) => {
182
188
  log.info("Scaffolded %d files into %s.", filesWritten, targetDir);
183
189
  return filesWritten;
184
190
  };
185
- const runPostScaffoldSteps = async (props, targetDir, interactive, template) => {
191
+ const runPostScaffoldSteps = async (props, targetDir, interactive, template, named) => {
186
192
  const inferred = inferPackageManager(targetDir);
187
193
  const defaultPm = resolvePackageManager(targetDir);
188
194
  const neonConfig = hasNeonConfig(targetDir);
189
195
  if (props.default) {
190
- await runDefaultSteps(props, targetDir, defaultPm, neonConfig, template);
196
+ await runDefaultSteps(props, targetDir, defaultPm, neonConfig, template, named);
191
197
  return;
192
198
  }
193
199
  if (!interactive) {
@@ -212,19 +218,20 @@ const runPostScaffoldSteps = async (props, targetDir, interactive, template) =>
212
218
  const wantInstall = props.install && await confirm(installPrompt(inferred));
213
219
  if (wantInstall) pm = inferred ?? await selectPackageManager();
214
220
  const wantGit = props.git && !isGitRepo(targetDir) && await confirm("Initialize a git repository?");
215
- const agentSetup = props.agentSetup === false ? "skip" : await (props.pickAgentSetup ?? pickAgentSetupInteractively)();
221
+ const agentSetup = props.agentSetup === false ? "skip" : named.length > 0 ? chooseYesAgentTooling(named).setup : await (props.pickAgentSetup ?? pickAgentSetupInteractively)();
216
222
  const canLink = props.link && !(neonConfig && !wantInstall);
217
223
  if (props.link && !canLink) logSkippedLink(pm);
218
224
  const wantLink = canLink && await confirm(`Link this project to a Neon project now? (runs ${getCliName()} link)`);
219
225
  const outcome = await executePostScaffold(props, targetDir, {
220
226
  yes: false,
221
- lockAgentSetup: true,
227
+ lockAgentSetup: named.length === 0,
222
228
  pm,
223
229
  git: wantGit,
224
230
  agentSetup,
225
231
  install: wantInstall,
226
232
  link: wantLink,
227
- hasNeonConfig: neonConfig
233
+ hasNeonConfig: neonConfig,
234
+ named
228
235
  });
229
236
  finishPostScaffold({
230
237
  heading: "Project scaffolded.",
@@ -237,10 +244,10 @@ const runPostScaffoldSteps = async (props, targetDir, interactive, template) =>
237
244
  };
238
245
  const installPrompt = (inferred) => inferred ? `Install dependencies with ${inferred}?` : "Install dependencies?";
239
246
  /** `link --yes` still asks for a project unless one is already linked. */
240
- const runDefaultSteps = async (props, targetDir, pm, neonConfig, template) => {
247
+ const runDefaultSteps = async (props, targetDir, pm, neonConfig, template, named) => {
241
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.");
242
249
  const wantGit = props.git && !isGitRepo(targetDir);
243
- const agentSetup = props.agentSetup === false ? "skip" : "skills-mcp";
250
+ const agentSetup = props.agentSetup === false ? "skip" : named.length > 0 ? chooseYesAgentTooling(named).setup : "skills-mcp";
244
251
  const outcome = await executePostScaffold(props, targetDir, {
245
252
  yes: true,
246
253
  lockAgentSetup: false,
@@ -249,7 +256,8 @@ const runDefaultSteps = async (props, targetDir, pm, neonConfig, template) => {
249
256
  agentSetup,
250
257
  install: props.install,
251
258
  link: props.link,
252
- hasNeonConfig: neonConfig
259
+ hasNeonConfig: neonConfig,
260
+ named
253
261
  });
254
262
  finishPostScaffold({
255
263
  heading: "Project scaffolded.",
@@ -292,8 +300,12 @@ const executePostScaffold = async (props, targetDir, choices) => {
292
300
  yes: choices.yes,
293
301
  ...choices.lockAgentSetup ? { agentSetup: choices.agentSetup } : {},
294
302
  ...kids,
303
+ ...choices.named.length > 0 ? { agents: choices.named } : {},
295
304
  ...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
296
- ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {}
305
+ ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {},
306
+ ...props.detectProjectAgents ? { detectProjectAgents: props.detectProjectAgents } : {},
307
+ ...props.detectAgent ? { detectAgent: props.detectAgent } : {},
308
+ command: "bootstrap"
297
309
  });
298
310
  agentsRan = true;
299
311
  continue;
@@ -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
@@ -4,9 +4,11 @@ import { getCliName } from "../utils/cli_name.js";
4
4
  import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
5
5
  import { initChildEnv, spawnCliChild } from "../init/child.js";
6
6
  import { agentSetupLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
7
- import { bootstrapInitStep, directoryIsEmpty, planExistingInit } from "../init/plan.js";
7
+ import { coerceAgentFlag } from "../utils/agent_flag.js";
8
+ import { INIT_NEEDS_YES_OR_TERMINAL, assertNamedAgentTooling, bootstrapInitStep, directoryIsEmpty, initPluginAgents, initSkillsMcpAgents, planExistingInit, resolveNamedAgents } from "../init/plan.js";
9
+ import { canPickAgentsInteractively } from "../utils/agent_picker.js";
8
10
  import { runAgentTooling, runInitSteps } from "../init/tooling.js";
9
- import { helpEpilogue } from "../utils/help_text.js";
11
+ import { helpCsv, helpEpilogue } from "../utils/help_text.js";
10
12
  import { existsSync, readdirSync } from "node:fs";
11
13
  import { resolve } from "node:path";
12
14
  //#region src/commands/init.ts
@@ -19,15 +21,18 @@ var init_exports = /* @__PURE__ */ __exportAll({
19
21
  });
20
22
  const command = "init";
21
23
  const describe = "Set up this directory for Neon: agent tooling, a linked project, and neon.ts. In an empty directory, scaffolds a template first. Skills needs Node.js 22.20 or newer.";
22
- const removedProtocol = () => `\`${getCliName()} init --agent\` and \`--data\` were removed. Run \`${getCliName()} init\` or \`${getCliName()} init -y\`.`;
24
+ const removedProtocol = () => `\`${getCliName()} init --data\` was removed. Run \`${getCliName()} init\` or \`${getCliName()} init -y\`.`;
23
25
  const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file", { hidden: true }).option("yes", {
24
26
  alias: "y",
25
27
  type: "boolean",
26
28
  default: false,
27
- describe: "Empty dir: bootstrap --default. Otherwise plugin when a project plugin agent is detected, else skills and MCP, then link --yes and config init --services none. link --yes still asks for a project unless one is already linked"
29
+ describe: "Empty dir: bootstrap --default. Otherwise plugin, or skills and MCP, for project folders, else the host CLI agent. Exits if none. Then link --yes and config init --services none. link --yes still asks for a project unless one is already linked"
28
30
  }).option("agent", {
29
- hidden: true,
30
- type: "boolean"
31
+ alias: "a",
32
+ type: "array",
33
+ string: true,
34
+ describe: "Coding agent to install into (repeatable). Forwarded to plugins, or to skills and mcp. Skips agent selection. Values listed below",
35
+ coerce: coerceAgentFlag
31
36
  }).option("data", {
32
37
  hidden: true,
33
38
  type: "string"
@@ -35,8 +40,8 @@ const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file
35
40
  alias: "o",
36
41
  hidden: true,
37
42
  describe: "Not supported; the commands init runs print their own output"
38
- }).example("$0 init", "Empty dir: bootstrap (scaffold, agent tooling, link). Existing app: agent tooling, link, config init").example("$0 init -y", "Same steps, using each child's defaults").epilogue(helpEpilogue("Interactive agent setup: plugin (recommended), skills and MCP separately, or skip agent setup. Never both plugin and skills+MCP.", "-y installs the plugin when Cursor, Claude Code, or Codex is detected in the project. Otherwise skills and MCP. Then link unless already linked. link --yes may still ask for a project.")).check((argv) => {
39
- if (argv.agent === true || argv.data !== void 0) throw new Error(removedProtocol());
43
+ }).example("$0 init", "Empty dir: bootstrap (scaffold, agent tooling, link). Existing app: agent tooling, link, config init").example("$0 init -y", "Same steps, using each child's defaults").example("$0 init --agent cursor --agent claude-code", "Skip agent selection; install the plugin for those agents").epilogue(helpEpilogue("Interactive agent setup: plugin (recommended), skills and MCP separately, or skip agent setup. Never both plugin and skills+MCP.", "-y installs the 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. Then link unless already linked. link --yes may still ask for a project.", "--agent / -a is forwarded to plugins, or to skills and mcp, not both. It skips agent selection, including with -y.", helpCsv("Plugin agents", initPluginAgents()), helpCsv("Skills and MCP agents", initSkillsMcpAgents()))).check((argv) => {
44
+ if (argv.data !== void 0) throw new Error(removedProtocol());
40
45
  if (argv.help !== true && (argv.output === "json" || argv.output === "yaml")) throw new Error(`\`${getCliName()} init\` does not support --output. The commands it runs print their own output.`);
41
46
  return true;
42
47
  }).strict();
@@ -50,6 +55,8 @@ const handler = async (props) => {
50
55
  const names = existsSync(cwd) ? readdirSync(cwd) : [];
51
56
  const contextFile = resolve(cwd, props.contextFile);
52
57
  const yes = props.yes === true;
58
+ const named = resolveNamedAgents(props.agent ?? []);
59
+ assertNamedAgentTooling(named, "init", yes ? { yes: true } : void 0);
53
60
  const run = props.run ?? spawnCliChild;
54
61
  const explicitKey = props.profile ? "" : credentialInputs().apiKeyFlag;
55
62
  const authEnv = explicitKey ? { NEON_API_KEY: explicitKey } : void 0;
@@ -61,7 +68,8 @@ const handler = async (props) => {
61
68
  ...props.analytics === false ? { analytics: false } : {}
62
69
  };
63
70
  if (directoryIsEmpty(names)) {
64
- await runInitSteps([bootstrapInitStep(yes)], {
71
+ if (!yes && !canPickAgentsInteractively()) throw new Error(INIT_NEEDS_YES_OR_TERMINAL);
72
+ await runInitSteps([bootstrapInitStep(yes, named)], {
65
73
  cwd,
66
74
  run,
67
75
  forward,
@@ -77,8 +85,12 @@ const handler = async (props) => {
77
85
  run,
78
86
  forward,
79
87
  authEnv,
88
+ ...named.length > 0 ? { agents: named } : {},
80
89
  ...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
81
- ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {}
90
+ ...props.detectProjectAgents ? { detectProjectAgents: props.detectProjectAgents } : {},
91
+ ...props.detectAgent ? { detectAgent: props.detectAgent } : {},
92
+ ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {},
93
+ command: "init"
82
94
  });
83
95
  await runInitSteps(planExistingInit({
84
96
  linked: alreadyLinked,
@@ -4,10 +4,10 @@ import { log } from "../log.js";
4
4
  import { isNeonApiError, messageFromBody } from "../api.js";
5
5
  import { applyContext, contextBranch, readContextFile, setContext, updateContextFile } from "../context.js";
6
6
  import { getCliName } from "../utils/cli_name.js";
7
+ import { helpEpilogue } from "../utils/help_text.js";
7
8
  import { createBranch, pickBranchInteractively } from "../utils/branch_picker.js";
8
9
  import { autoPullEnvAfterPin } from "./env.js";
9
10
  import { hasNeonConfigFile, initCmd } from "./config.js";
10
- import { helpEpilogue } from "../utils/help_text.js";
11
11
  import { REGIONS } from "./projects.js";
12
12
  import prompts from "prompts";
13
13
  //#region src/commands/link.ts
@@ -31,7 +31,6 @@ const nonInteractiveLinkCommands = () => {
31
31
  };
32
32
  const nonInteractiveLinkHelp = () => nonInteractiveLinkCommands().map((command) => ` ${command}`).join("\n");
33
33
  const orgScopedKeyHint = "Organization-scoped API keys cannot list orgs; pass --org-id.";
34
- const removedAgent = () => `\`${getCliName()} link --agent\` was removed.\nUse:\n${nonInteractiveLinkHelp()}\n${orgScopedKeyHint}`;
35
34
  const command = "link";
36
35
  const describe = "Link the current directory to a Neon project";
37
36
  const builder = (argv) => argv.usage("$0 link [options]").options({
@@ -60,10 +59,6 @@ const builder = (argv) => argv.usage("$0 link [options]").options({
60
59
  describe: "JSON object with link parameters, e.g. '{\"orgId\":\"...\",\"projectId\":\"...\"}' or '{\"orgId\":\"...\",\"projectName\":\"...\",\"regionId\":\"...\"}'. Flags take precedence over fields in --params.",
61
60
  type: "string"
62
61
  },
63
- agent: {
64
- hidden: true,
65
- type: "boolean"
66
- },
67
62
  yes: {
68
63
  alias: "y",
69
64
  describe: "Skip the \"already linked\" confirmation, and pin the project's default branch when linking a project that has more than one.",
@@ -92,10 +87,7 @@ const builder = (argv) => argv.usage("$0 link [options]").options({
92
87
  ["$0 link --branch-id br-…", "Pin a branch in the already-linked project"],
93
88
  ["$0 link --no-checks --org-id org-… --project-id polished-snowflake-12345678", "Write the context offline (no API calls, no verification)"],
94
89
  ["$0 link --clear", "Forget the current org/project/branch context"]
95
- ]).epilogue(helpEpilogue("Non-interactive (CI, scripts, agents):", ...nonInteractiveLinkCommands().map((command) => ` ${command}`), orgScopedKeyHint)).check((argv) => {
96
- if (argv.agent === true) throw new Error(removedAgent());
97
- return true;
98
- });
90
+ ]).epilogue(helpEpilogue("Non-interactive (CI, scripts, agents):", ...nonInteractiveLinkCommands().map((command) => ` ${command}`), orgScopedKeyHint)).strict();
99
91
  const handler = async (props) => {
100
92
  if (props.clear) {
101
93
  clearContext(props.contextFile);
@@ -4,11 +4,11 @@ import { readContextFile } from "../context.js";
4
4
  import { getCliName } from "../utils/cli_name.js";
5
5
  import { writer } from "../writer.js";
6
6
  import { noPassthrough, single } from "../utils/flags.js";
7
+ import { NEON_MCP_CATEGORIES, NEON_MCP_URL, existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
8
+ import { mcpInstallableAgents, resolveInstallTargets } from "../mcp/targets.js";
7
9
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
8
10
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
9
- import { NEON_MCP_CATEGORIES, NEON_MCP_URL, existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
10
11
  import { mintMcpApiKey, mintedKeyRevokeCommand, withdrawMintedKey } from "../mcp/mint.js";
11
- import { mcpInstallableAgents, resolveInstallTargets } from "../mcp/targets.js";
12
12
  import { confirmMcpInstall } from "../mcp/wizard.js";
13
13
  import { resolveMcpPlan } from "../mcp/plan.js";
14
14
  //#region src/commands/mcp.ts
@@ -38,7 +38,7 @@ const builder = (argv) => argv.usage("$0 mcp [options]").options({
38
38
  alias: "y",
39
39
  type: "boolean",
40
40
  default: false,
41
- describe: "Skip prompts. Defaults listed below. --project, --oauth, --agent, --read-only, --project-id and --category still apply"
41
+ describe: "Skip prompts. Defaults listed below. --agent, --project, --oauth, --read-only, --project-id and --category still apply"
42
42
  },
43
43
  agent: {
44
44
  alias: "a",
@@ -86,7 +86,7 @@ const builder = (argv) => argv.usage("$0 mcp [options]").options({
86
86
  return parseMcpCategories(parts);
87
87
  }
88
88
  }
89
- }).example("$0 mcp", "Interactive: config location, agents, auth, then confirm").example("$0 mcp -y", "Global config, detected agents, reuse or mint an API key").example("$0 mcp --oauth", "Install with OAuth; the agent signs in on first use").example("$0 mcp --project", "Write project-level config").example("$0 mcp --agent cursor --agent claude-code", "Install into specific agents").example("$0 mcp --read-only", "Hide write tools via ?readonly=true").example("$0 mcp --project-id <id>", "Pin tools to one project via ?projectId=").example("$0 mcp --category querying --category schema", "Limit tools to those categories").epilogue(helpEpilogue(`Installs ${NEON_MCP_URL}`, helpCsv("Supported agents at global scope", mcpGlobalAgents), helpCsv("--project does not support", mcpProjectDroppedAgents), helpCsv("Supported categories", NEON_MCP_CATEGORIES), "neon mcp -y:", " global config", " every globally detected agent", " reuse an existing Neon MCP API key, else mint an account-wide key", " write tools on, all categories", " no project pin (including from .neon)")).strict().check(noPassthrough("mcp"));
89
+ }).example("$0 mcp", "Interactive: config location, agents, auth, then confirm").example("$0 mcp -y", "Global config, installed apps else the host CLI agent, reuse or mint an API key").example("$0 mcp --oauth", "Install with OAuth; the agent signs in on first use").example("$0 mcp --agent cursor --agent claude-code", "Install into specific agents").example("$0 mcp --project", "Write project-level config").example("$0 mcp --read-only", "Hide write tools via ?readonly=true").example("$0 mcp --project-id <id>", "Pin tools to one project via ?projectId=").example("$0 mcp --category querying --category schema", "Limit tools to those categories").epilogue(helpEpilogue(`Installs ${NEON_MCP_URL}`, helpCsv("Supported agents at global scope", mcpGlobalAgents), helpCsv("--project does not support", mcpProjectDroppedAgents), helpCsv("Supported categories", NEON_MCP_CATEGORIES), "neon mcp -y:", " global config", " globally installed apps, else the host CLI agent", " reuse an existing Neon MCP API key, else mint an account-wide key", " write tools on, all categories", " no project pin (including from .neon)")).strict().check(noPassthrough("mcp"));
90
90
  const handler = async (props) => {
91
91
  const cwd = process.cwd();
92
92
  const interactive = canPickAgentsInteractively() && props.yes !== true;
@@ -3,22 +3,22 @@ import { log } from "../log.js";
3
3
  import { writer } from "../writer.js";
4
4
  import { noPassthrough } from "../utils/flags.js";
5
5
  import { getAgentDisplayName } from "../mcp/agents.js";
6
+ import { NEON_MCP_URL } from "../mcp/install.js";
6
7
  import "../init/agents.js";
7
8
  import { pluginsInstallableAgents } from "../plugins/targets.js";
8
9
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
9
10
  import { NEON_PLUGIN_NAME, PLUGIN_SKILLS, PLUGIN_SOURCE, neonPluginsRetryCommand, pluginsAddArgs, runPluginsCli } from "../plugins/run.js";
10
11
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
11
- import { NEON_MCP_URL } from "../mcp/install.js";
12
12
  import { resolvePluginsPlan } from "../plugins/plan.js";
13
13
  import { confirmPluginsInstall } from "../plugins/wizard.js";
14
14
  //#region src/commands/plugins.ts
15
15
  var plugins_exports = /* @__PURE__ */ __exportAll({
16
+ aliases: () => aliases,
16
17
  builder: () => builder,
17
18
  command: () => command,
18
19
  describe: () => describe,
19
20
  handler: () => handler
20
21
  });
21
- const scopeLabel = (scope) => scope === "project" ? "project" : "user";
22
22
  const coerceAgents = (value) => {
23
23
  if (value === void 0) return [];
24
24
  const list = Array.isArray(value) ? value : [value];
@@ -28,7 +28,9 @@ const coerceAgents = (value) => {
28
28
  return item;
29
29
  });
30
30
  };
31
+ const scopeLabel = (scope) => scope === "project" ? "project" : "user";
31
32
  const command = "plugins";
33
+ const aliases = ["plugin"];
32
34
  const describe = "Install the Neon plugin into coding agents";
33
35
  const pluginProjectAgents = pluginsInstallableAgents("project");
34
36
  const pluginGlobalOnlyAgents = pluginsInstallableAgents("global").filter((id) => !pluginProjectAgents.includes(id));
@@ -37,7 +39,7 @@ const builder = (argv) => argv.usage("$0 plugins [options]").options({
37
39
  alias: "y",
38
40
  type: "boolean",
39
41
  default: false,
40
- describe: "Skip prompts"
42
+ describe: "Skip prompts. Detected agents (project folders, else the host CLI agent). --global uses installed apps, else the host CLI agent"
41
43
  },
42
44
  global: {
43
45
  type: "boolean",
@@ -51,7 +53,7 @@ const builder = (argv) => argv.usage("$0 plugins [options]").options({
51
53
  describe: "Coding agent to install into (repeatable). Skips the agent picker. Values listed below",
52
54
  coerce: coerceAgents
53
55
  }
54
- }).example("$0 plugins", "Interactive: agents, then confirm").example("$0 plugins -y", "Detected agents, skip prompts").example("$0 plugins --agent cursor --agent claude-code", "Install into specific agents").example("$0 plugins --global", "Install user-level").epilogue(helpEpilogue(helpCsv("Supported agents at project scope", pluginProjectAgents), helpCsv("Also with --global", pluginGlobalOnlyAgents), `Currently one plugin: ${NEON_PLUGIN_NAME} from ${PLUGIN_SOURCE}.`, `It includes the Neon MCP server (${NEON_MCP_URL})`, helpCsv("and these skills", PLUGIN_SKILLS))).strict().check(noPassthrough("plugins"));
56
+ }).example("$0 plugins", "Interactive: agents, then confirm").example("$0 plugins -y", "Detected agents (project folders, else the host CLI agent), skip prompts").example("$0 plugins --agent cursor --agent claude-code", "Install into specific agents").example("$0 plugins --global", "Install user-level").epilogue(helpEpilogue(helpCsv("Supported agents at project scope", pluginProjectAgents), helpCsv("Also with --global", pluginGlobalOnlyAgents), `Currently one plugin: ${NEON_PLUGIN_NAME} from ${PLUGIN_SOURCE}.`, `It includes the Neon MCP server (${NEON_MCP_URL})`, helpCsv("and these skills", PLUGIN_SKILLS))).strict().check(noPassthrough("plugins"));
55
57
  const handler = async (props) => {
56
58
  const cwd = process.cwd();
57
59
  const yes = props.yes === true;
@@ -137,4 +139,4 @@ const handler = async (props) => {
137
139
  throw new Error(`Failed to install the Neon plugin for: ${failed.flatMap((row) => row.agents).join(", ")}.\n${detail}\nRetry with: ${retry}`);
138
140
  };
139
141
  //#endregion
140
- export { builder, command, describe, handler, plugins_exports as t };
142
+ export { aliases, builder, command, describe, handler, plugins_exports as t };
@@ -4,15 +4,16 @@ import { writer } from "../writer.js";
4
4
  import { noPassthrough } from "../utils/flags.js";
5
5
  import { getAgentDisplayName } from "../mcp/agents.js";
6
6
  import "../init/agents.js";
7
+ import { mappedSkillsAgentNames, skillsInstallableAgents } from "../skills/targets.js";
7
8
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
8
9
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
9
10
  import { skillsHelpValues, skillsYesHelp } from "../skills/catalog.js";
10
- import { mappedSkillsAgentNames, skillsInstallableAgents } from "../skills/targets.js";
11
11
  import { confirmSkillsInstall, confirmSkillsUpdate } from "../skills/wizard.js";
12
12
  import { assertSkillsCanRun, resolveSkillsPlan } from "../skills/plan.js";
13
13
  import { assertSkillsNode, neonSkillsRetryCommand, neonSkillsUpdateRetryCommand, runSkillsCli, skillsAddArgs, skillsMetadata, skillsUpdateArgs, skillsUpdateDetail, skillsUpdateHadNothing } from "../skills/run.js";
14
14
  //#region src/commands/skills.ts
15
15
  var skills_exports = /* @__PURE__ */ __exportAll({
16
+ aliases: () => aliases,
16
17
  builder: () => builder,
17
18
  command: () => command,
18
19
  describe: () => describe,
@@ -38,12 +39,16 @@ const coerceSkills = (value) => {
38
39
  });
39
40
  };
40
41
  const command = "skills";
42
+ const aliases = ["skill"];
41
43
  const describe = "Install Neon agent skills into coding agents";
42
- const builder = (argv) => argv.usage("$0 skills [command] [options]").command("update", "Update installed skills to the latest versions", (yargs) => yargs.usage("$0 skills update [options]").options({ global: {
43
- type: "boolean",
44
- default: false,
45
- describe: "Update user-level skills (skills CLI -g). Default is this directory"
46
- } }).example("$0 skills update", "Interactive: confirm, then update skills in this directory").example("$0 skills update -y", "Update skills in this directory without prompting").example("$0 skills update --global -y", "Update user-level skills").strict().hide("agent").hide("skill").check((args) => {
44
+ const builder = (argv) => argv.usage("$0 skills [command] [options]").command("update", "Update installed skills to the latest versions", (yargs) => yargs.usage("$0 skills update [options]").options({
45
+ yes: { describe: "Skip the confirm prompt" },
46
+ global: {
47
+ type: "boolean",
48
+ default: false,
49
+ describe: "Update user-level skills (skills CLI -g). Default is this directory"
50
+ }
51
+ }).example("$0 skills update", "Interactive: confirm, then update skills in this directory").example("$0 skills update -y", "Update skills in this directory without prompting").example("$0 skills update --global -y", "Update user-level skills").strict().hide("agent").hide("skill").check((args) => {
47
52
  noPassthrough("skills update")(args);
48
53
  const agents = args.agent;
49
54
  if (Array.isArray(agents) && agents.length > 0) throw new Error("neon skills update does not take --agent. It refreshes every installed skill in this directory (or --global).");
@@ -55,7 +60,7 @@ const builder = (argv) => argv.usage("$0 skills [command] [options]").command("u
55
60
  alias: "y",
56
61
  type: "boolean",
57
62
  default: false,
58
- describe: "Skip prompts"
63
+ describe: "Skip prompts. Detected agents (project folders, else the host CLI agent). --global uses installed apps, else the host CLI agent"
59
64
  },
60
65
  global: {
61
66
  type: "boolean",
@@ -73,10 +78,10 @@ const builder = (argv) => argv.usage("$0 skills [command] [options]").command("u
73
78
  alias: "s",
74
79
  type: "array",
75
80
  string: true,
76
- describe: "Skill to install (repeatable). Skips the skill picker. Values listed below",
81
+ describe: "Skill to install (repeatable). Skips the skill picker. Does not select agents. Values listed below",
77
82
  coerce: coerceSkills
78
83
  }
79
- }).example("$0 skills", "Interactive: this directory, agents, skills, then confirm").example("$0 skills -y", "This directory, detected agents, the default skills").example("$0 skills -s neon -s neon-ai-gateway --agent cursor", "Install named skills into specific agents").example("$0 skills --global", "Install user-level skills").epilogue(helpEpilogue(helpCsv("Supported agents", skillsInstallableAgents()), "Supported skills, by source repo:", skillsHelpValues(), skillsYesHelp())).strict().check(noPassthrough("skills"));
84
+ }).example("$0 skills", "Interactive: this directory, agents, skills, then confirm").example("$0 skills -y", "This directory, detected agents (project folders, else the host CLI agent), the default skills").example("$0 skills -y -s neon -s neon-ai-gateway", "Named skills into detected agents").example("$0 skills -s neon -s neon-ai-gateway --agent cursor", "Named skills into a named agent").example("$0 skills --global", "Install user-level skills").epilogue(helpEpilogue(helpCsv("Supported agents", skillsInstallableAgents()), "Supported skills, by source repo:", skillsHelpValues(), skillsYesHelp())).strict().check(noPassthrough("skills"));
80
85
  const handler = async (props) => {
81
86
  assertSkillsNode();
82
87
  const cwd = process.cwd();
@@ -222,4 +227,4 @@ const updateHandler = async (props) => {
222
227
  if (failure !== void 0) throw new Error(failure.includes("needs npx (Node.js)") || failure.includes("needs Node.js") ? failure : `${failure}\nRetry with: ${neonSkillsUpdateRetryCommand(scope === "global")}`);
223
228
  };
224
229
  //#endregion
225
- export { builder, command, describe, handler, skills_exports as t };
230
+ export { aliases, builder, command, describe, handler, skills_exports as t };
package/dist/context.js CHANGED
@@ -52,8 +52,8 @@ const isClaimCommand = (args) => args._[0] === "claim" || args._[0] === "claimab
52
52
  */
53
53
  const isApiKeysCommand = (args) => args._[0] === "api-keys" || args._[0] === "api-key";
54
54
  const isMcpCommand = (args) => args._[0] === "mcp";
55
- const isSkillsCommand = (args) => args._[0] === "skills";
56
- const isPluginsCommand = (args) => args._[0] === "plugins";
55
+ const isSkillsCommand = (args) => args._[0] === "skills" || args._[0] === "skill";
56
+ const isPluginsCommand = (args) => args._[0] === "plugins" || args._[0] === "plugin";
57
57
  /** Raw argv is required because auth middleware runs before MCP flags are parsed. */
58
58
  const isMcpOauth = (args) => isMcpCommand(args) && argvEnablesMcpOauth(process.argv);
59
59
  const OAUTH_FALSE = /* @__PURE__ */ new Set([
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import { defaultClientID } from "./auth.js";
11
11
  import { getCliName } from "./utils/cli_name.js";
12
12
  import { showHelp } from "./help.js";
13
13
  import { deleteCredentialsAt, ensureAuth } from "./commands/auth.js";
14
+ import { rewriteUnknownAgentArg } from "./init/plan.js";
14
15
  import commands_default from "./commands/index.js";
15
16
  import { fillInArgs, resolveApiKeyFromEnv } from "./utils/middlewares.js";
16
17
  import yargs from "yargs";
@@ -31,7 +32,9 @@ const NO_SUBCOMMANDS_VERBS = [
31
32
  "init",
32
33
  "mcp",
33
34
  "plugins",
35
+ "plugin",
34
36
  "skills",
37
+ "skill",
35
38
  "dev",
36
39
  "deploy",
37
40
  "diff",
@@ -125,6 +128,19 @@ async function handleError(msg, err) {
125
128
  process.exit(0);
126
129
  }
127
130
  if (err instanceof Error && err.stack) log.debug("Stack: %s", err.stack);
131
+ if (err instanceof Error) {
132
+ const rewritten = rewriteUnknownAgentArg({
133
+ message: err.message,
134
+ argv: process.argv,
135
+ cliName: getCliName()
136
+ });
137
+ if (rewritten !== void 0) {
138
+ const error = new Error(rewritten);
139
+ sendError(error, matchErrorCode(error.message));
140
+ log.error(error.message);
141
+ return false;
142
+ }
143
+ }
128
144
  if (isNetworkError(err)) {
129
145
  log.error(NETWORK_ERROR_MESSAGE);
130
146
  const error = err instanceof Error ? err : new Error(NETWORK_ERROR_MESSAGE);
@@ -0,0 +1,38 @@
1
+ //#region src/init/detect_host.ts
2
+ const CLAUDE_ENV = ["CLAUDECODE", "CLAUDE_CODE_CHILD_SESSION"];
3
+ const CURSOR_ENV = [
4
+ "CURSOR_TRACE_ID",
5
+ "CURSOR_EXTENSION_HOST_ROLE",
6
+ "CURSOR_LAYOUT",
7
+ "CURSOR_SPAWNED_BY_EXTENSION_ID"
8
+ ];
9
+ const CURSOR_PATH_ENV = [
10
+ "GIT_ASKPASS",
11
+ "VSCODE_GIT_ASKPASS_NODE",
12
+ "VSCODE_GIT_ASKPASS_MAIN",
13
+ "VSCODE_IPC_HOOK_CLI"
14
+ ];
15
+ const looksLikeCursor = (value) => {
16
+ const lower = value.toLowerCase();
17
+ return lower.includes("cursor.app") || lower.includes("/cursor/") || lower.includes("/.cursor/");
18
+ };
19
+ const isSet = (value) => value !== void 0;
20
+ const isNonEmpty = (value) => typeof value === "string" && value.trim() !== "";
21
+ function detectAgent(env = process.env) {
22
+ if (CLAUDE_ENV.some((key) => env[key] === "1")) return "claude-code";
23
+ if (env.CODEX === "1") return "codex";
24
+ if (isNonEmpty(env.CODEX_THREAD_ID) || isNonEmpty(env.CODEX_SESSION_ID)) return "codex";
25
+ if (env.GEMINI_CLI === "1") return "gemini-cli";
26
+ if (env.OPENCODE === "1") return "opencode";
27
+ if (env.GOOSE_TERMINAL === "1" || env.AGENT === "goose") return "goose";
28
+ if (env.TERM_PROGRAM === "cursor" || CURSOR_ENV.some((key) => isSet(env[key])) || CURSOR_PATH_ENV.some((key) => {
29
+ const value = env[key];
30
+ return typeof value === "string" && looksLikeCursor(value);
31
+ })) return "cursor";
32
+ if (env.TERM_PROGRAM === "windsurf") return "windsurf";
33
+ if (env.TERM_PROGRAM === "zed") return "zed";
34
+ if (env.TERM_PROGRAM === "vscode" || isSet(env.VSCODE_PID) || isSet(env.VSCODE_CWD)) return "vscode";
35
+ return null;
36
+ }
37
+ //#endregion
38
+ export { detectAgent };
package/dist/init/plan.js CHANGED
@@ -1,11 +1,18 @@
1
+ import { getCliName } from "../utils/cli_name.js";
2
+ import { tryResolveAddMcpAgentId, uniqueAgentIds } from "../mcp/agents.js";
3
+ import { mcpInstallableAgents } from "../mcp/targets.js";
4
+ import { supportsSkills } from "./agents.js";
5
+ import { pluginsInstallableAgents } from "../plugins/targets.js";
6
+ import { skillsInstallableAgents } from "../skills/targets.js";
7
+ import { agentArgv } from "../utils/agent_flag.js";
1
8
  import { isAbsolute, join, relative, resolve } from "node:path";
2
9
  //#region src/init/plan.ts
3
10
  const directoryIsEmpty = (names) => names.filter((name) => name !== ".git").length === 0;
4
- const bootstrapInitStep = (yes) => yes ? [
11
+ const bootstrapInitStep = (yes, agents = []) => [...yes ? [
5
12
  "bootstrap",
6
13
  ".",
7
14
  "--default"
8
- ] : ["bootstrap", "."];
15
+ ] : ["bootstrap", "."], ...agentArgv(agents)];
9
16
  const projectContextFile = (projectDir, contextFile) => {
10
17
  const resolved = isAbsolute(contextFile) ? contextFile : resolve(projectDir, contextFile);
11
18
  const rel = relative(projectDir, resolved);
@@ -33,6 +40,184 @@ const planAgentSteps = (input) => {
33
40
  if (input.agentSetup === "skills-mcp") return [["skills", ...y], ["mcp", ...y]];
34
41
  return [];
35
42
  };
43
+ const planToolingSteps = (tooling, options) => {
44
+ const prefix = options.yes ? ["-y"] : [];
45
+ const flagsFor = (ids) => options.named ? [...prefix, ...agentArgv(ids)] : [...prefix];
46
+ switch (tooling.setup) {
47
+ case "skip": return [];
48
+ case "plugin": return [["plugins", ...flagsFor(tooling.agents)]];
49
+ case "skills-mcp": {
50
+ const steps = [];
51
+ if (tooling.skillsAgents.length > 0) steps.push(["skills", ...flagsFor(tooling.skillsAgents)]);
52
+ if (tooling.mcpAgents.length > 0) steps.push(["mcp", ...flagsFor(tooling.mcpAgents)]);
53
+ return steps;
54
+ }
55
+ default: return tooling;
56
+ }
57
+ };
58
+ const noDetectedAgentsMessage = (input) => {
59
+ const lead = input.scope === "project" ? "No coding agents detected in this project." : "No coding agents detected.";
60
+ const name = input.nameAgent === true;
61
+ let hint;
62
+ if (input.fix === "pass-yes") {
63
+ const detected = input.scope === "project" ? "Pass -y to use project folders, else the host CLI agent" : "Pass -y to use installed apps, else the host CLI agent";
64
+ hint = name ? `${detected}, --agent <name> to name them, or run from a terminal to pick one.` : `${detected}, or run from a terminal to pick one.`;
65
+ } else hint = name ? "Pass --agent <name>, run this command from a supported agent, or omit -y in a terminal to pick one." : "Run this command from a supported agent, or omit -y in a terminal to pick one.";
66
+ return `${lead} ${hint} Supported agents: ${input.supported.join(", ")}`;
67
+ };
68
+ const INIT_NEEDS_YES_OR_TERMINAL = "No interactive terminal. Pass -y to use defaults, or run this command in a terminal to pick.";
69
+ const UNKNOWN_AGENT_COMMANDS = ["link"];
70
+ const unknownAgentArgHint = (command, cliName) => `${cliName} ${command} has no --agent. Pass --project-id <id> to link without a TTY, or run ${cliName} ${command} in a terminal.`;
71
+ const BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
72
+ "-y",
73
+ "--yes",
74
+ "--default",
75
+ "--force",
76
+ "-h",
77
+ "--help",
78
+ "-v",
79
+ "--version",
80
+ "--color",
81
+ "--no-color",
82
+ "--analytics",
83
+ "--no-analytics",
84
+ "--oauth",
85
+ "--project",
86
+ "--global",
87
+ "--read-only",
88
+ "--readonly",
89
+ "--clear",
90
+ "--checks",
91
+ "--no-checks",
92
+ "--env-pull",
93
+ "--no-env-pull",
94
+ "--no-agent-setup",
95
+ "--no-link",
96
+ "--list",
97
+ "--list-templates",
98
+ "--force-auth",
99
+ "--no-force-auth"
100
+ ]);
101
+ const isUnknownAgentCommand = (token) => {
102
+ for (const command of UNKNOWN_AGENT_COMMANDS) if (command === token) return true;
103
+ return false;
104
+ };
105
+ const commandFromArgv = (argv) => {
106
+ let skipValue = false;
107
+ for (const token of argv) {
108
+ if (skipValue) {
109
+ skipValue = false;
110
+ continue;
111
+ }
112
+ if (token === "--") break;
113
+ if (token.startsWith("-")) {
114
+ const flag = token.split("=")[0];
115
+ if (flag !== void 0 && !token.includes("=") && !BOOLEAN_FLAGS.has(flag)) skipValue = true;
116
+ continue;
117
+ }
118
+ if (isUnknownAgentCommand(token)) return token;
119
+ }
120
+ };
121
+ const rewriteUnknownAgentArg = (input) => {
122
+ if (input.message !== "Unknown argument: agent" && input.message !== "Unknown argument: a") return;
123
+ const command = commandFromArgv(input.argv);
124
+ if (command === void 0) return;
125
+ return unknownAgentArgHint(command, input.cliName);
126
+ };
127
+ const initYesSupportedAgents = () => uniqueAgentIds([
128
+ ...pluginsInstallableAgents("project"),
129
+ ...skillsInstallableAgents(),
130
+ ...mcpInstallableAgents("global")
131
+ ]);
132
+ const initPluginAgents = () => pluginsInstallableAgents("project");
133
+ const initSkillsMcpAgents = () => {
134
+ const plugin = new Set(initPluginAgents());
135
+ return initYesSupportedAgents().filter((id) => !plugin.has(id));
136
+ };
137
+ const resolveYesAgentList = (input) => {
138
+ if (input.detected.length > 0) return uniqueAgentIds(input.detected);
139
+ if (input.host !== null) return [input.host];
140
+ return [];
141
+ };
142
+ async function collectYesAgents(sources) {
143
+ const detected = await sources.detected();
144
+ if (detected.length > 0) return resolveYesAgentList({
145
+ detected,
146
+ host: null
147
+ });
148
+ const rawHost = sources.detectAgent();
149
+ const accept = sources.acceptHost ?? (() => true);
150
+ const host = rawHost !== null && accept(rawHost) ? rawHost : null;
151
+ return resolveYesAgentList({
152
+ detected,
153
+ host
154
+ });
155
+ }
156
+ const chooseYesAgentTooling = (agents) => {
157
+ const projectPlugin = new Set(pluginsInstallableAgents("project"));
158
+ const [pluginFirst, ...pluginRest] = agents.filter((id) => projectPlugin.has(id));
159
+ if (pluginFirst !== void 0) return {
160
+ setup: "plugin",
161
+ agents: [pluginFirst, ...pluginRest]
162
+ };
163
+ const globalMcp = new Set(mcpInstallableAgents("global"));
164
+ const skillsAgents = agents.filter((id) => supportsSkills(id));
165
+ const mcpAgents = agents.filter((id) => globalMcp.has(id));
166
+ if (skillsAgents.length === 0 && mcpAgents.length === 0) return { setup: "skip" };
167
+ return {
168
+ setup: "skills-mcp",
169
+ skillsAgents,
170
+ mcpAgents
171
+ };
172
+ };
173
+ const planYesAgentSteps = (tooling) => planToolingSteps(tooling, {
174
+ yes: true,
175
+ named: false
176
+ });
177
+ const NAMED_AGENTS_UNSUPPORTED = "None of the selected agents can install the Neon plugin, skills, or MCP.";
178
+ const NAMED_AGENTS_MIXED = "Cannot install the plugin and skills/MCP in one run.";
179
+ const quoteIfNeeded = (value) => /[\s"'\\]/.test(value) ? `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"` : value;
180
+ const mixedRerun = (command, ids, context) => {
181
+ const tokens = [getCliName(), command];
182
+ if (command === "bootstrap" && context?.directory !== void 0 && context.directory.length > 0) tokens.push(quoteIfNeeded(context.directory));
183
+ if (context?.yes === true) tokens.push(command === "bootstrap" ? "--default" : "-y");
184
+ tokens.push(...ids.flatMap((id) => ["--agent", id]));
185
+ return `\`${tokens.join(" ")}\``;
186
+ };
187
+ const namedAgentsMixedMessage = (named, command, context) => {
188
+ const plugin = new Set(initPluginAgents());
189
+ const pluginNamed = uniqueAgentIds(named.filter((id) => plugin.has(id)));
190
+ const otherNamed = uniqueAgentIds(named.filter((id) => !plugin.has(id)));
191
+ return `${NAMED_AGENTS_MIXED} Plugin: ${pluginNamed.join(", ")}. Skills/MCP: ${otherNamed.join(", ")}. Re-run ${mixedRerun(command, pluginNamed, context)} or ${mixedRerun(command, otherNamed, context)}.`;
192
+ };
193
+ const namedAgentsNeedSplit = (named, tooling) => {
194
+ if (tooling.setup === "skip") return false;
195
+ if (tooling.setup === "plugin") {
196
+ const used = new Set(tooling.agents);
197
+ const leftover = named.filter((id) => !used.has(id));
198
+ return chooseYesAgentTooling(leftover).setup !== "skip";
199
+ }
200
+ const used = /* @__PURE__ */ new Set([...tooling.skillsAgents, ...tooling.mcpAgents]);
201
+ return named.some((id) => !used.has(id));
202
+ };
203
+ const assertNamedAgentTooling = (named, command = "init", context) => {
204
+ if (named.length === 0) return;
205
+ const tooling = chooseYesAgentTooling(named);
206
+ if (tooling.setup === "skip") throw new Error(`${NAMED_AGENTS_UNSUPPORTED} Supported agents: ${initYesSupportedAgents().join(", ")}`);
207
+ if (namedAgentsNeedSplit(named, tooling)) throw new Error(namedAgentsMixedMessage(named, command, context));
208
+ };
209
+ const resolveNamedAgents = (raw) => {
210
+ if (raw.length === 0) return [];
211
+ const available = initYesSupportedAgents();
212
+ const resolved = [];
213
+ for (const item of raw) {
214
+ if (item === "*") throw new Error("--agent * is not accepted. Pass --agent <name> for each coding agent, or omit --agent to detect or pick.");
215
+ const id = tryResolveAddMcpAgentId(item);
216
+ if (!id) throw new Error(`Unknown agent: "${item}". Supported agents: ${available.join(", ")}`);
217
+ resolved.push(id);
218
+ }
219
+ return uniqueAgentIds(resolved);
220
+ };
36
221
  const planExistingInit = (input) => {
37
222
  const steps = [...planAgentSteps(input)];
38
223
  if (!input.linked) steps.push(input.yes ? ["link", "--yes"] : ["link"]);
@@ -45,9 +230,8 @@ const planExistingInit = (input) => {
45
230
  return steps;
46
231
  };
47
232
  const resolveInitAgentSetup = async (input) => {
48
- if (input.yes) return input.hasProjectPlugins ? "plugin" : "skills-mcp";
49
233
  if (input.interactive) return input.pick();
50
- return "skills-mcp";
234
+ throw new Error(INIT_NEEDS_YES_OR_TERMINAL);
51
235
  };
52
236
  const childArgv = (step, forward) => {
53
237
  const args = [...step];
@@ -59,4 +243,4 @@ const childArgv = (step, forward) => {
59
243
  return args;
60
244
  };
61
245
  //#endregion
62
- export { bootstrapInitStep, childArgv, directoryIsEmpty, planAgentSteps, planExistingInit, postScaffoldActions, projectContextFile, resolveInitAgentSetup };
246
+ export { INIT_NEEDS_YES_OR_TERMINAL, NAMED_AGENTS_MIXED, NAMED_AGENTS_UNSUPPORTED, assertNamedAgentTooling, bootstrapInitStep, childArgv, chooseYesAgentTooling, collectYesAgents, directoryIsEmpty, initPluginAgents, initSkillsMcpAgents, initYesSupportedAgents, namedAgentsMixedMessage, namedAgentsNeedSplit, noDetectedAgentsMessage, planAgentSteps, planExistingInit, planToolingSteps, planYesAgentSteps, postScaffoldActions, projectContextFile, resolveInitAgentSetup, resolveNamedAgents, resolveYesAgentList, rewriteUnknownAgentArg };
@@ -1,17 +1,13 @@
1
1
  import { log } from "../log.js";
2
2
  import { getCliName } from "../utils/cli_name.js";
3
3
  import { AUTH_CHILD } from "./child.js";
4
- import { childArgv, planAgentSteps, resolveInitAgentSetup } from "./plan.js";
5
- import { detectInstallablePluginsAgents } from "../plugins/targets.js";
4
+ import { assertNamedAgentTooling, childArgv, chooseYesAgentTooling, collectYesAgents, initYesSupportedAgents, noDetectedAgentsMessage, planAgentSteps, planToolingSteps, planYesAgentSteps, resolveInitAgentSetup } from "./plan.js";
6
5
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
6
+ import { detectAgent } from "./detect_host.js";
7
7
  import { pickAgentSetupInteractively } from "./wizard.js";
8
+ import { detectProjectAgents } from "add-mcp";
8
9
  //#region src/init/tooling.ts
9
- const defaultHasProjectPlugins = async (cwd) => {
10
- return (await detectInstallablePluginsAgents({
11
- scope: "project",
12
- cwd
13
- })).length > 0;
14
- };
10
+ const defaultProjectAgents = (cwd) => detectProjectAgents(cwd);
15
11
  const runInitSteps = async (steps, options) => {
16
12
  for (const step of steps) {
17
13
  log.info("Running `%s %s`", getCliName(), step.join(" "));
@@ -20,14 +16,53 @@ const runInitSteps = async (steps, options) => {
20
16
  if (!await options.run(argv, options.cwd, command !== void 0 && AUTH_CHILD.has(command) ? options.authEnv : void 0)) throw new Error(`\`${getCliName()} ${step.join(" ")}\` failed.`);
21
17
  }
22
18
  };
19
+ const yesAgentsFromOptions = async (options) => collectYesAgents({
20
+ detected: () => (options.detectProjectAgents ?? defaultProjectAgents)(options.cwd),
21
+ detectAgent: options.detectAgent ?? detectAgent
22
+ });
23
+ const yesMiss = () => new Error(noDetectedAgentsMessage({
24
+ scope: "project",
25
+ supported: initYesSupportedAgents(),
26
+ fix: "run-without-yes",
27
+ nameAgent: true
28
+ }));
23
29
  const runAgentTooling = async (options) => {
24
30
  const yes = options.yes;
25
- const hasProjectPlugins = yes ? await (options.hasProjectPlugins ?? defaultHasProjectPlugins)(options.cwd) : false;
26
- const interactive = !yes && (options.pickAgentSetup !== void 0 || canPickAgentsInteractively());
27
- const agentSetup = options.agentSetup ?? await resolveInitAgentSetup({
28
- yes,
31
+ const named = options.agents ?? [];
32
+ if (named.length > 0) {
33
+ assertNamedAgentTooling(named, options.command ?? "init");
34
+ const tooling = chooseYesAgentTooling(named);
35
+ await runInitSteps(planToolingSteps(tooling, {
36
+ yes,
37
+ named: true
38
+ }), options);
39
+ return tooling.setup;
40
+ }
41
+ if (options.agentSetup !== void 0) {
42
+ await runInitSteps(planAgentSteps({
43
+ yes,
44
+ agentSetup: options.agentSetup
45
+ }), options);
46
+ return options.agentSetup;
47
+ }
48
+ if (yes) {
49
+ if (options.hasProjectPlugins !== void 0) {
50
+ const agentSetup = await options.hasProjectPlugins(options.cwd) ? "plugin" : "skills-mcp";
51
+ await runInitSteps(planAgentSteps({
52
+ yes: true,
53
+ agentSetup
54
+ }), options);
55
+ return agentSetup;
56
+ }
57
+ const agents = await yesAgentsFromOptions(options);
58
+ const tooling = chooseYesAgentTooling(agents);
59
+ if (tooling.setup === "skip") throw yesMiss();
60
+ await runInitSteps(planYesAgentSteps(tooling), options);
61
+ return tooling.setup;
62
+ }
63
+ const interactive = options.pickAgentSetup !== void 0 || canPickAgentsInteractively();
64
+ const agentSetup = await resolveInitAgentSetup({
29
65
  interactive,
30
- hasProjectPlugins,
31
66
  pick: options.pickAgentSetup ?? pickAgentSetupInteractively
32
67
  });
33
68
  await runInitSteps(planAgentSteps({
@@ -1,3 +1,4 @@
1
+ import { INIT_NEEDS_YES_OR_TERMINAL } from "./plan.js";
1
2
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
2
3
  import { NEON_PLUGIN_NAME } from "../plugins/run.js";
3
4
  import prompts from "prompts";
@@ -10,7 +11,7 @@ const restoreCursorOnAbort = (state) => {
10
11
  }
11
12
  };
12
13
  const pickAgentSetupInteractively = async () => {
13
- if (!canPickAgentsInteractively()) throw new Error("No interactive terminal. Pass -y to use defaults, or run this command in a terminal.");
14
+ if (!canPickAgentsInteractively()) throw new Error(INIT_NEEDS_YES_OR_TERMINAL);
14
15
  const { setup } = await prompts({
15
16
  onState: restoreCursorOnAbort,
16
17
  type: "select",
package/dist/mcp/plan.js CHANGED
@@ -1,22 +1,35 @@
1
1
  import { tryResolveAddMcpAgentId } from "./agents.js";
2
- import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
3
2
  import { detectMcpAgents, mcpInstallableAgents } from "./targets.js";
3
+ import { collectYesAgents, noDetectedAgentsMessage } from "../init/plan.js";
4
+ import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
5
+ import { detectAgent } from "../init/detect_host.js";
4
6
  import { pickMcpAuth, pickMcpProjectPin, pickMcpScope } from "./wizard.js";
5
7
  //#region src/mcp/plan.ts
6
8
  async function resolveMcpPlan(options) {
7
9
  const prompt = options.interactive && !options.yes;
8
10
  const scope = options.project ? "project" : prompt ? await (options.pickScope ?? pickMcpScope)() : "global";
9
11
  const available = mcpInstallableAgents(scope);
10
- const detected = await detectMcpAgents({
12
+ const availableSet = new Set(available);
13
+ const scoped = await detectMcpAgents({
11
14
  scope,
12
15
  cwd: options.cwd
13
16
  });
17
+ const detected = options.yes && options.agents.length === 0 ? await collectYesAgents({
18
+ detected: () => scoped,
19
+ detectAgent: options.detectAgent ?? detectAgent,
20
+ acceptHost: (id) => availableSet.has(id)
21
+ }) : scoped;
14
22
  const agents = await resolveAgentSelection({
15
23
  specified: options.agents,
16
24
  choices: agentChoicesFrom(available, detected),
17
25
  detected,
18
26
  message: "Which coding agents should get the Neon MCP server? (space to toggle, enter to confirm)",
19
- nonInteractiveMessage: scope === "project" ? `No coding agents detected in this project. Pass --agent <name>. Supported agents: ${available.join(", ")}` : `No coding agents detected. Pass --agent <name>. Supported agents: ${available.join(", ")}`,
27
+ nonInteractiveMessage: noDetectedAgentsMessage({
28
+ scope,
29
+ supported: available,
30
+ fix: options.yes ? "run-without-yes" : "pass-yes",
31
+ nameAgent: true
32
+ }),
20
33
  resolveSpecified: (raw) => {
21
34
  const id = tryResolveAddMcpAgentId(raw);
22
35
  if (!id) throw new Error(`Unknown agent: "${raw}". Supported agents: ${available.join(", ")}`);
@@ -1,11 +1,13 @@
1
1
  import { getAgentDisplayName, tryResolveAddMcpAgentId } from "../mcp/agents.js";
2
2
  import "../init/agents.js";
3
3
  import { detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, pluginsInstallableAgents } from "./targets.js";
4
+ import { collectYesAgents, noDetectedAgentsMessage } from "../init/plan.js";
4
5
  import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
6
+ import { detectAgent } from "../init/detect_host.js";
5
7
  //#region src/plugins/plan.ts
6
8
  const assertPluginsCanRun = (options) => {
7
9
  if (options.yes || options.interactive || options.hasAgents) return;
8
- throw new Error("No interactive terminal. Pass -y to install into detected agents, or --agent <name>.");
10
+ throw new Error("No interactive terminal. Pass -y to install into detected agents, or --agent <name> to name them.");
9
11
  };
10
12
  async function resolvePluginsPlan(options) {
11
13
  assertPluginsCanRun({
@@ -16,16 +18,28 @@ async function resolvePluginsPlan(options) {
16
18
  const prompt = options.interactive && !options.yes;
17
19
  const scope = options.global ? "global" : "project";
18
20
  const available = pluginsInstallableAgents(scope);
19
- const detected = await detectPluginsAgents({
21
+ const availableSet = new Set(available);
22
+ const scoped = await detectPluginsAgents({
20
23
  scope,
21
- cwd: options.cwd
24
+ cwd: options.cwd,
25
+ ...options.detectInstalledAgents ? { detectInstalledAgents: options.detectInstalledAgents } : {}
22
26
  });
27
+ const detected = options.yes && options.agents.length === 0 ? await collectYesAgents({
28
+ detected: () => scoped,
29
+ detectAgent: options.detectAgent ?? detectAgent,
30
+ acceptHost: (id) => availableSet.has(id) || getPluginsTargetName(id) !== void 0
31
+ }) : scoped;
23
32
  const selected = await resolveAgentSelection({
24
33
  specified: options.agents,
25
34
  choices: agentChoicesFrom(available, detected),
26
35
  detected,
27
36
  message: "Which coding agents should get the Neon plugin? (space to toggle, enter to confirm)",
28
- nonInteractiveMessage: scope === "project" ? `No coding agents detected in this project. Pass --agent <name>. Supported agents: ${available.join(", ")}` : `No coding agents detected. Pass --agent <name>. Supported agents: ${available.join(", ")}`,
37
+ nonInteractiveMessage: noDetectedAgentsMessage({
38
+ scope,
39
+ supported: available,
40
+ fix: options.yes ? "run-without-yes" : "pass-yes",
41
+ nameAgent: true
42
+ }),
29
43
  resolveSpecified: (raw) => {
30
44
  if (raw === "*") throw new Error("neon plugins does not accept --agent *. Pass --agent <name> for each coding agent, or omit --agent to use detected agents.");
31
45
  const id = tryResolveAddMcpAgentId(raw);
@@ -35,7 +35,7 @@ const pluginsInstallableAgents = (scope) => pluginsMappedAgents().filter((id) =>
35
35
  return target !== void 0 && !isUserScopeOnlyPluginsTarget(target);
36
36
  });
37
37
  const detectPluginsAgents = async (options) => {
38
- const detected = options.scope === "project" ? detectProjectAgents(options.cwd) : await detectInstalledAgents();
38
+ const detected = options.scope === "project" ? detectProjectAgents(options.cwd) : await (options.detectInstalledAgents ?? detectInstalledAgents)();
39
39
  return uniqueAgentIds(detected).filter((id) => supportsPlugins(id));
40
40
  };
41
41
  const detectInstallablePluginsAgents = async (options) => {
@@ -1,8 +1,10 @@
1
1
  import { tryResolveAddMcpAgentId } from "../mcp/agents.js";
2
2
  import "../init/agents.js";
3
+ import { detectSkillsAgents, skillsInstallableAgents } from "./targets.js";
4
+ import { collectYesAgents, noDetectedAgentsMessage } from "../init/plan.js";
3
5
  import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
6
+ import { detectAgent } from "../init/detect_host.js";
4
7
  import { invocationsForSelection, resolveSkillId, uniqueSkillEntries, yesInstallInvocations } from "./catalog.js";
5
- import { detectSkillsAgents, skillsInstallableAgents } from "./targets.js";
6
8
  import { pickSkillsInteractively } from "./wizard.js";
7
9
  //#region src/skills/plan.ts
8
10
  const assertSkillsCanRun = (options) => {
@@ -11,7 +13,7 @@ const assertSkillsCanRun = (options) => {
11
13
  throw new Error("No interactive terminal. Pass -y to update installed skills.");
12
14
  }
13
15
  if (options.yes || options.interactive || options.hasSkills === true) return;
14
- throw new Error("No interactive terminal. Pass -y to install the default skills, or --skill <name>.");
16
+ throw new Error("No interactive terminal. Pass -y to install the default skills into detected agents, or --skill <name>.");
15
17
  };
16
18
  async function resolveSkillsPlan(options) {
17
19
  assertSkillsCanRun({
@@ -24,16 +26,28 @@ async function resolveSkillsPlan(options) {
24
26
  const prompt = options.interactive && !options.yes;
25
27
  const scope = options.global ? "global" : "project";
26
28
  const available = skillsInstallableAgents();
27
- const detected = await detectSkillsAgents({
29
+ const availableSet = new Set(available);
30
+ const scoped = await detectSkillsAgents({
28
31
  scope,
29
- cwd: options.cwd
32
+ cwd: options.cwd,
33
+ ...options.detectInstalledAgents ? { detectInstalledAgents: options.detectInstalledAgents } : {}
30
34
  });
35
+ const detected = options.yes && options.agents.length === 0 ? await collectYesAgents({
36
+ detected: () => scoped,
37
+ detectAgent: options.detectAgent ?? detectAgent,
38
+ acceptHost: (id) => availableSet.has(id)
39
+ }) : scoped;
31
40
  const selected = await resolveAgentSelection({
32
41
  specified: options.agents,
33
42
  choices: agentChoicesFrom(available, detected),
34
43
  detected,
35
44
  message: "Which coding agents should get Neon agent skills? (space to toggle, enter to confirm)",
36
- nonInteractiveMessage: scope === "project" ? `No coding agents detected in this project. Pass --agent <name>. Supported agents: ${available.join(", ")}` : `No coding agents detected. Pass --agent <name>. Supported agents: ${available.join(", ")}`,
45
+ nonInteractiveMessage: noDetectedAgentsMessage({
46
+ scope,
47
+ supported: available,
48
+ fix: options.yes ? "run-without-yes" : "pass-yes",
49
+ nameAgent: true
50
+ }),
37
51
  resolveSpecified: (raw) => {
38
52
  if (raw === "*") throw new Error("neon skills does not accept --agent *. Pass --agent <name> for each coding agent, or omit --agent to use detected agents.");
39
53
  const id = tryResolveAddMcpAgentId(raw);
@@ -4,7 +4,7 @@ import { detectProjectAgents } from "add-mcp";
4
4
  //#region src/skills/targets.ts
5
5
  const skillsInstallableAgents = () => listMcpAgentIds().filter((id) => supportsSkills(id));
6
6
  const detectSkillsAgents = async (options) => {
7
- const detected = options.scope === "project" ? detectProjectAgents(options.cwd) : await detectInstalledAgents();
7
+ const detected = options.scope === "project" ? detectProjectAgents(options.cwd) : await (options.detectInstalledAgents ?? detectInstalledAgents)();
8
8
  return uniqueAgentIds(detected).filter((id) => supportsSkills(id));
9
9
  };
10
10
  const mappedSkillsAgentNames = (agents) => {
@@ -12,7 +12,7 @@ const restoreCursorOnAbort = (state) => {
12
12
  }
13
13
  };
14
14
  const pickSkillsInteractively = async () => {
15
- if (!canPickAgentsInteractively()) throw new Error("No interactive terminal. Pass -y to install the default skills, or --skill <name>.");
15
+ if (!canPickAgentsInteractively()) throw new Error("No interactive terminal. Pass -y to install the default skills into detected agents.");
16
16
  const { skills } = await prompts({
17
17
  onState: restoreCursorOnAbort,
18
18
  type: "multiselect",
@@ -0,0 +1,13 @@
1
+ //#region src/utils/agent_flag.ts
2
+ const coerceAgentFlag = (value) => {
3
+ if (value === void 0) return [];
4
+ const list = Array.isArray(value) ? value : [value];
5
+ if (list.length === 0) throw new Error("--agent needs a value. Pass one, or omit the flag entirely.");
6
+ return list.map((item) => {
7
+ if (typeof item !== "string" || item.trim() === "") throw new Error("--agent needs a value. Pass one, or omit the flag entirely.");
8
+ return item;
9
+ });
10
+ };
11
+ const agentArgv = (agents) => agents.flatMap((id) => ["--agent", id]);
12
+ //#endregion
13
+ export { agentArgv, coerceAgentFlag };
@@ -4,7 +4,7 @@ import prompts from "prompts";
4
4
  //#region src/utils/agent_picker.ts
5
5
  const canPickAgentsInteractively = () => !isCi() && Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY);
6
6
  const pickAgentsInteractively = async (options) => {
7
- if (!canPickAgentsInteractively()) throw new Error("No interactive terminal. Pass --agent <name>, or run this command in a terminal to pick agents.");
7
+ if (!canPickAgentsInteractively()) throw new Error("No interactive terminal. Pass -y, or run this command in a terminal to pick agents.");
8
8
  if (options.choices.length === 0) throw new Error("No coding agents are available to pick.");
9
9
  const selected = new Set(options.selected ?? []);
10
10
  const { agents } = await prompts({
@@ -48,7 +48,7 @@ const resolveAgentSelection = async (options) => {
48
48
  choices: options.choices,
49
49
  selected: options.detected.filter((id) => options.choices.some((choice) => choice.id === id))
50
50
  });
51
- if (selected.length === 0) throw new Error("No agents selected. Pass --agent <name>, or pick at least one agent.");
51
+ if (selected.length === 0) throw new Error("No agents selected. Pick at least one agent, or pass -y to use detected agents.");
52
52
  return uniqueAgentIds(selected);
53
53
  }
54
54
  if (options.detected.length > 0) return uniqueAgentIds(options.detected);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "4.10.2",
3
+ "version": "4.11.1",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",