neon 4.5.0 → 4.6.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
@@ -710,6 +710,12 @@ $ neon bootstrap my-app
710
710
 
711
711
  # Scaffold a specific template into the current directory (no prompts)
712
712
  $ neon bootstrap . --template hono
713
+
714
+ # List templates
715
+ $ neon bootstrap --list-templates
716
+
717
+ # Machine-readable catalog
718
+ $ neon bootstrap --list-templates --output json
713
719
  ```
714
720
 
715
721
  The target directory must be empty unless you pass `--force` (a lone `.git` is ignored, so a freshly `git init`ed folder is fine). Symlinks and executable bits in the template are preserved.
@@ -781,7 +787,7 @@ That message reports where parsing stopped and nothing more. `--data` carries wh
781
787
  # Interactive: global or project, then agents, then API key or OAuth, then confirm.
782
788
  $ neon mcp
783
789
 
784
- # Skip prompts. Global config, every globally detected agent, minted API key.
790
+ # Skip prompts. Global config, every globally detected agent, reuse or mint an API key.
785
791
  $ neon mcp -y
786
792
 
787
793
  # OAuth: no API key minted. The agent prompts for Neon sign-in on first use.
@@ -804,7 +810,7 @@ $ neon mcp --category querying --category schema
804
810
 
805
811
  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.
806
812
 
807
- `-y` skips those questions. `--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`. `-y` does not infer a project from `.neon`. Without a TTY, pass `-y` to mint into every detected agent, `--agent` to name the agents or `--oauth` to write the URL only.
813
+ `-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.
808
814
 
809
815
  `--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.
810
816
 
@@ -838,9 +844,9 @@ On a TTY the command asks which agents and which skills, then shows a summary to
838
844
 
839
845
  `-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.
840
846
 
841
- `--skill` names: `claimable-postgres`, `neon`, `neon-ai-gateway`, `neon-functions`, `neon-object-storage`, `neon-postgres`, `neon-postgres-branches`, `neon-postgres-egress-optimizer`, `neon-postgres-agent-platforms`.
847
+ `--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`).
842
848
 
843
- `--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.
849
+ `--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`.
844
850
 
845
851
  ## Install the Neon plugin (`plugins`)
846
852
 
@@ -868,7 +874,9 @@ Default scope is `project`. `--global` is `user`. On macOS and Linux, Cursor and
868
874
 
869
875
  `--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.
870
876
 
871
- The plugins CLI installs every plugin it finds in the Neon plugin package. Today that is `neon-postgres`.
877
+ 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`.
878
+
879
+ `neon plugins --help` lists the plugin, those contents, and the supported `--agent` names.
872
880
 
873
881
  ## Snapshots (`snapshots`)
874
882
 
@@ -3,8 +3,9 @@ import { isCi } from "../env.js";
3
3
  import { log } from "../log.js";
4
4
  import { getCliName } from "../utils/cli_name.js";
5
5
  import { t as credentialInputs } from "../_chunks/auth_selection-ktL6uoEe.js";
6
- import { BootstrapInputError, FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds } from "../init/bootstrap.js";
7
- import { DO_NOT_SUBSTITUTE_HINT, formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
6
+ import { writer } from "../writer.js";
7
+ import { FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds } from "../init/bootstrap.js";
8
+ import { formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
8
9
  import { existsSync } from "node:fs";
9
10
  import { join, relative, resolve } from "node:path";
10
11
  import chalk from "chalk";
@@ -16,6 +17,7 @@ var bootstrap_exports = /* @__PURE__ */ __exportAll({
16
17
  describe: () => describe,
17
18
  handler: () => handler
18
19
  });
20
+ 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\`.`;
19
21
  const command = "bootstrap [directory]";
20
22
  const describe = "Scaffold a new project from a Neon starter template";
21
23
  const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").positional("directory", {
@@ -28,7 +30,7 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
28
30
  },
29
31
  "list-templates": {
30
32
  alias: ["list", "ls"],
31
- describe: "List available templates and exit.",
33
+ describe: "List available templates and exit. --output json and --output yaml print a machine-readable catalog.",
32
34
  type: "boolean",
33
35
  default: false
34
36
  },
@@ -38,9 +40,8 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
38
40
  default: false
39
41
  },
40
42
  agent: {
41
- describe: "Emit a JSON state-machine response designed for AI agents instead of prompting. The output is a single JSON object with a discriminated `status` field describing the next step.",
42
- type: "boolean",
43
- default: false
43
+ hidden: true,
44
+ type: "boolean"
44
45
  },
45
46
  default: {
46
47
  alias: "y",
@@ -63,20 +64,33 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
63
64
  type: "boolean",
64
65
  default: true
65
66
  }
66
- }).example("$0 bootstrap my-app", "Create ./my-app from an interactively chosen template").example("$0 bootstrap . --template hono", "Scaffold the Hono template into the current directory").example("$0 bootstrap my-app --default", "Quick start: scaffold the default template and run setup without prompting").example("$0 bootstrap my-app --template hono --agent", "Scaffold without prompting and emit the JSON state machine for AI agents").strict();
67
+ }).example("$0 bootstrap my-app", "Create ./my-app from an interactively chosen template").example("$0 bootstrap . --template hono", "Scaffold the Hono template into the current directory").example("$0 bootstrap my-app --default", "Quick start: scaffold the default template and run setup without prompting").example("$0 bootstrap --list-templates --output json", "Print the template catalog as JSON").check((argv) => {
68
+ if (argv.agent === true) throw new Error(removedAgent());
69
+ return true;
70
+ }).strict();
67
71
  const handler = async (props) => {
68
72
  if (props.listTemplates) {
69
73
  const templates = await fetchTemplates();
74
+ if (props.output === "json" || props.output === "yaml") {
75
+ writer(props).end(templates.map((t) => ({
76
+ id: t.id,
77
+ title: t.title,
78
+ description: t.description,
79
+ services: t.services ?? []
80
+ })), { fields: [
81
+ "id",
82
+ "title",
83
+ "description",
84
+ "services"
85
+ ] });
86
+ return;
87
+ }
70
88
  for (const t of templates) {
71
89
  const services = t.services && t.services.length > 0 ? ` [${t.services.join(" · ")}]` : "";
72
90
  process.stdout.write(`${t.id} — ${t.description}${services}\n`);
73
91
  }
74
92
  return;
75
93
  }
76
- if (props.agent) {
77
- await runAgentSafely(props);
78
- return;
79
- }
80
94
  const templates = await resolveTemplateList(props);
81
95
  const interactive = !props.default && Boolean(process.stdout.isTTY) && !isCi();
82
96
  const template = await resolveSelectedTemplate(props, interactive, templates);
@@ -163,15 +177,6 @@ const scaffold = async (template, targetDir) => {
163
177
  log.info("Scaffolded %d files into %s.", filesWritten, targetDir);
164
178
  return filesWritten;
165
179
  };
166
- /**
167
- * After a human scaffold, offer the things you almost always do next: install
168
- * dependencies, initialize a git repo, and link the directory to a Neon
169
- * project. In an interactive terminal each is a y/n prompt (skippable up front
170
- * with --no-install / --no-git / --no-link); `--default` runs install + git
171
- * without asking; otherwise we just print the manual steps so nothing runs
172
- * behind the user's back. Agent mode never reaches here — it returns these as
173
- * structured `next_steps` instead (see {@link runAgent}).
174
- */
175
180
  const runPostScaffoldSteps = async (props, targetDir, interactive) => {
176
181
  const inferred = inferPackageManager(targetDir);
177
182
  const defaultPm = resolvePackageManager(targetDir);
@@ -307,103 +312,6 @@ const printNextSteps = (targetDir, pm, opts) => {
307
312
  log.info(" See the README to run it.");
308
313
  log.info("");
309
314
  };
310
- const runAgentSafely = async (props) => {
311
- try {
312
- await runAgent(props);
313
- } catch (err) {
314
- emitAgent(toAgentError(err));
315
- process.exit(1);
316
- }
317
- };
318
- /**
319
- * The `--agent` flow: resolve what the flags determine and emit one JSON object
320
- * describing either the next input needed (`needs_template` / `needs_directory`)
321
- * or the terminal result (`scaffolded`). Unlike interactive mode it never
322
- * prompts and never runs install/git/link itself — those come back as structured
323
- * `next_steps` so the agent can confirm with the user and run them (the link
324
- * step chains into `neon link --agent`).
325
- */
326
- const runAgent = async (props) => {
327
- if (!props.template) {
328
- const templates = await fetchTemplates();
329
- emitAgent({
330
- status: "needs_template",
331
- instruction: `Ask the user which template to scaffold, then re-run the next_command_template with the chosen --template value${props.directory ? "" : " and a target directory"}.`,
332
- options: templates.map((template) => ({
333
- id: template.id,
334
- title: template.title,
335
- description: template.description,
336
- ...template.services ? { services: template.services } : {}
337
- })),
338
- next_command_template: `${getCliName()} bootstrap --agent ${props.directory ? shellArg(props.directory) : "<directory>"} --template <template_id>`
339
- });
340
- return;
341
- }
342
- const templates = await resolveTemplateList(props);
343
- const template = findTemplate(templates, props.template);
344
- if (!template) throw new BootstrapInputError(`Unknown template "${props.template}". Available templates: ${templateIds(templates)}.`, "UNKNOWN_TEMPLATE");
345
- if (props.directory === void 0) {
346
- emitAgent({
347
- status: "needs_directory",
348
- instruction: "Ask the user which directory to scaffold into (use \".\" for the current directory), then re-run the next_command_template with it.",
349
- next_command_template: `${getCliName()} bootstrap --agent <directory> --template ${shellArg(template.id)}`
350
- });
351
- return;
352
- }
353
- const targetDir = resolve(process.cwd(), props.directory === "." ? "" : props.directory);
354
- ensureTargetUsable(targetDir, props.force);
355
- const filesWritten = await scaffold(template, targetDir);
356
- const dir = displayDir(targetDir);
357
- const runIn = isCurrentDir(targetDir) ? "" : `cd ${shellArg(dir)} && `;
358
- const installPm = resolvePackageManager(targetDir);
359
- emitAgent({
360
- status: "scaffolded",
361
- directory: targetDir,
362
- template: {
363
- id: template.id,
364
- title: template.title
365
- },
366
- files_written: filesWritten,
367
- next_steps: [
368
- {
369
- action: "install_dependencies",
370
- instruction: `Ask the user whether to install dependencies, then run this in the project directory. ${DO_NOT_SUBSTITUTE_HINT}`,
371
- command: `${runIn}${formatInstallCommand(installPm)}`
372
- },
373
- {
374
- action: "initialize_git",
375
- instruction: "Ask the user whether to initialize a git repository in the project directory.",
376
- command: `${runIn}git init`
377
- },
378
- {
379
- action: "link_neon_project",
380
- instruction: "Ask the user whether to link the project to a Neon project now. This runs the link state machine — follow its JSON output for the next step.",
381
- command: `${runIn}${getCliName()} link --agent`
382
- }
383
- ],
384
- message: `Scaffolded "${template.title}" (${filesWritten} files) into ${dir}. Offer the next_steps to the user: install dependencies, initialize git, then link a Neon project.`
385
- });
386
- };
387
- const emitAgent = (response) => {
388
- process.stdout.write(`${JSON.stringify(response, null, 2)}\n`);
389
- };
390
- const toAgentError = (err) => {
391
- if (err instanceof BootstrapInputError) return {
392
- status: "error",
393
- code: err.agentCode,
394
- message: err.message
395
- };
396
- if (err instanceof Error) return {
397
- status: "error",
398
- code: "INTERNAL_ERROR",
399
- message: err.message
400
- };
401
- return {
402
- status: "error",
403
- code: "INTERNAL_ERROR",
404
- message: String(err)
405
- };
406
- };
407
315
  const isCurrentDir = (targetDir) => relative(process.cwd(), targetDir) === "";
408
316
  /**
409
317
  * The path to show the user: the bare relative path for the common
@@ -415,10 +323,6 @@ const displayDir = (targetDir) => {
415
323
  if (rel === "") return ".";
416
324
  return rel.startsWith("..") ? targetDir : rel;
417
325
  };
418
- const shellArg = (value) => {
419
- if (/^[A-Za-z0-9._:/-]+$/.test(value)) return value;
420
- return `'${value.replace(/'/g, `'\\''`)}'`;
421
- };
422
326
  const onPromptState = (state) => {
423
327
  if (state.aborted) {
424
328
  process.stdout.write("\x1B[?25h");
@@ -4,12 +4,13 @@ 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 { existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
7
+ import { NEON_MCP_CATEGORIES, NEON_MCP_URL, existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
8
8
  import { mintMcpApiKey, mintedKeyRevokeCommand, withdrawMintedKey } from "../mcp/mint.js";
9
9
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
10
- import { resolveInstallTargets } from "../mcp/targets.js";
10
+ import { mcpInstallableAgents, resolveInstallTargets } from "../mcp/targets.js";
11
11
  import { confirmMcpInstall } from "../mcp/wizard.js";
12
12
  import { resolveMcpPlan } from "../mcp/plan.js";
13
+ import { helpCsv, helpEpilogue } from "../utils/help_text.js";
13
14
  //#region src/commands/mcp.ts
14
15
  var mcp_exports = /* @__PURE__ */ __exportAll({
15
16
  builder: () => builder,
@@ -19,6 +20,9 @@ var mcp_exports = /* @__PURE__ */ __exportAll({
19
20
  });
20
21
  const command = "mcp";
21
22
  const describe = "Install the Neon MCP server into coding agents";
23
+ const mcpGlobalAgents = mcpInstallableAgents("global");
24
+ const mcpProjectAgents = mcpInstallableAgents("project");
25
+ const mcpProjectDroppedAgents = mcpGlobalAgents.filter((id) => !mcpProjectAgents.includes(id));
22
26
  const builder = (argv) => argv.usage("$0 mcp [options]").options({
23
27
  oauth: {
24
28
  type: "boolean",
@@ -34,13 +38,13 @@ const builder = (argv) => argv.usage("$0 mcp [options]").options({
34
38
  alias: "y",
35
39
  type: "boolean",
36
40
  default: false,
37
- describe: "Skip prompts. Defaults to global config, every detected agent and a minted account-wide API key. --project, --oauth, --agent, --read-only, --project-id and --category still apply"
41
+ describe: "Skip prompts. Defaults listed below. --project, --oauth, --agent, --read-only, --project-id and --category still apply"
38
42
  },
39
43
  agent: {
40
44
  alias: "a",
41
45
  type: "array",
42
46
  string: true,
43
- describe: "Coding agent to install into (repeatable). Skips the agent picker",
47
+ describe: "Coding agent to install into (repeatable). Skips the agent picker. Values listed below",
44
48
  coerce: (value) => {
45
49
  if (value === void 0) return [];
46
50
  const list = Array.isArray(value) ? value : [value];
@@ -65,7 +69,7 @@ const builder = (argv) => argv.usage("$0 mcp [options]").options({
65
69
  category: {
66
70
  type: "array",
67
71
  string: true,
68
- describe: "MCP tool category (repeatable or comma-separated). Default: all",
72
+ describe: "MCP tool category (repeatable or comma-separated). Default: all. Values listed below",
69
73
  coerce: (value) => {
70
74
  if (value === void 0) return [];
71
75
  const list = Array.isArray(value) ? value : [value];
@@ -82,7 +86,7 @@ const builder = (argv) => argv.usage("$0 mcp [options]").options({
82
86
  return parseMcpCategories(parts);
83
87
  }
84
88
  }
85
- }).example("$0 mcp", "Interactive: config location, agents, auth, then confirm").example("$0 mcp -y", "Global config, detected agents, minted 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").strict().check(noPassthrough("mcp"));
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"));
86
90
  const handler = async (props) => {
87
91
  const cwd = process.cwd();
88
92
  const interactive = canPickAgentsInteractively() && props.yes !== true;
@@ -2,11 +2,14 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { log } from "../log.js";
3
3
  import { writer } from "../writer.js";
4
4
  import { noPassthrough } from "../utils/flags.js";
5
+ import { NEON_MCP_URL } from "../mcp/install.js";
5
6
  import { getAgentDisplayName } from "../mcp/agents.js";
6
7
  import "../init/agents.js";
7
8
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
9
+ import { helpCsv, helpEpilogue } from "../utils/help_text.js";
10
+ import { pluginsInstallableAgents } from "../plugins/targets.js";
8
11
  import { resolvePluginsPlan } from "../plugins/plan.js";
9
- import { NEON_PLUGIN_NAME, neonPluginsRetryCommand, pluginsAddArgs, runPluginsCli } from "../plugins/run.js";
12
+ import { NEON_PLUGIN_NAME, PLUGIN_SKILLS, PLUGIN_SOURCE, neonPluginsRetryCommand, pluginsAddArgs, runPluginsCli } from "../plugins/run.js";
10
13
  import { confirmPluginsInstall } from "../plugins/wizard.js";
11
14
  //#region src/commands/plugins.ts
12
15
  var plugins_exports = /* @__PURE__ */ __exportAll({
@@ -27,6 +30,8 @@ const coerceAgents = (value) => {
27
30
  };
28
31
  const command = "plugins";
29
32
  const describe = "Install the Neon plugin into coding agents";
33
+ const pluginProjectAgents = pluginsInstallableAgents("project");
34
+ const pluginGlobalOnlyAgents = pluginsInstallableAgents("global").filter((id) => !pluginProjectAgents.includes(id));
30
35
  const builder = (argv) => argv.usage("$0 plugins [options]").options({
31
36
  yes: {
32
37
  alias: "y",
@@ -43,10 +48,10 @@ const builder = (argv) => argv.usage("$0 plugins [options]").options({
43
48
  alias: "a",
44
49
  type: "array",
45
50
  string: true,
46
- describe: "Coding agent to install into (repeatable). Skips the agent picker",
51
+ describe: "Coding agent to install into (repeatable). Skips the agent picker. Values listed below",
47
52
  coerce: coerceAgents
48
53
  }
49
- }).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").strict().check(noPassthrough("plugins"));
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"));
50
55
  const handler = async (props) => {
51
56
  const cwd = process.cwd();
52
57
  const yes = props.yes === true;
@@ -5,7 +5,9 @@ import { noPassthrough } from "../utils/flags.js";
5
5
  import { getAgentDisplayName } from "../mcp/agents.js";
6
6
  import "../init/agents.js";
7
7
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
8
- import { mappedSkillsAgentNames } from "../skills/targets.js";
8
+ import { helpCsv, helpEpilogue } from "../utils/help_text.js";
9
+ import { skillsHelpValues, skillsYesHelp } from "../skills/catalog.js";
10
+ import { mappedSkillsAgentNames, skillsInstallableAgents } from "../skills/targets.js";
9
11
  import { confirmSkillsInstall, confirmSkillsUpdate } from "../skills/wizard.js";
10
12
  import { assertSkillsCanRun, resolveSkillsPlan } from "../skills/plan.js";
11
13
  import { assertSkillsNode, neonSkillsRetryCommand, neonSkillsUpdateRetryCommand, runSkillsCli, skillsAddArgs, skillsMetadata, skillsUpdateArgs, skillsUpdateDetail, skillsUpdateHadNothing } from "../skills/run.js";
@@ -64,17 +66,17 @@ const builder = (argv) => argv.usage("$0 skills [command] [options]").command("u
64
66
  alias: "a",
65
67
  type: "array",
66
68
  string: true,
67
- describe: "Coding agent to install into (repeatable). Skips the agent picker",
69
+ describe: "Coding agent to install into (repeatable). Skips the agent picker. Values listed below",
68
70
  coerce: coerceAgents
69
71
  },
70
72
  skill: {
71
73
  alias: "s",
72
74
  type: "array",
73
75
  string: true,
74
- describe: "Skill to install (repeatable). Skips the skill picker",
76
+ describe: "Skill to install (repeatable). Skips the skill picker. Values listed below",
75
77
  coerce: coerceSkills
76
78
  }
77
- }).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").strict().check(noPassthrough("skills"));
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"));
78
80
  const handler = async (props) => {
79
81
  assertSkillsNode();
80
82
  const cwd = process.cwd();
@@ -305,12 +305,7 @@ const downloadTemplate = async (template) => {
305
305
  if (files.length === 0) throw new Error(`Template subdirectory "${subdir}" was not found in ${owner}/${repo}@${ref}.`);
306
306
  return files;
307
307
  };
308
- /**
309
- * A bad caller-supplied input that an agent (or human) can correct: an unknown
310
- * template id or a non-empty target directory. Carries an `agentCode` so an
311
- * agent surface can report a precise error code instead of a generic
312
- * INTERNAL_ERROR, while a human path just surfaces the clear `message`.
313
- */
308
+ /** `agentCode` lets programmatic callers branch without parsing human-readable messages. */
314
309
  var BootstrapInputError = class extends Error {
315
310
  constructor(message, agentCode) {
316
311
  super(message);
@@ -4,6 +4,15 @@ import { execa } from "execa";
4
4
  const TELEMETRY_BLOCKERS = ["DISABLE_TELEMETRY", "DO_NOT_TRACK"];
5
5
  const PLUGIN_SOURCE = "neondatabase/agent-skills";
6
6
  const NEON_PLUGIN_NAME = "neon-postgres";
7
+ const PLUGIN_SKILLS = [
8
+ "neon",
9
+ "neon-ai-gateway",
10
+ "neon-functions",
11
+ "neon-object-storage",
12
+ "neon-postgres",
13
+ "neon-postgres-branches",
14
+ "neon-postgres-egress-optimizer"
15
+ ];
7
16
  const PLUGINS_CLI_TIMEOUT_MS = 12e4;
8
17
  const pluginsChildEnv = (base = process.env) => {
9
18
  const env = { ...base };
@@ -120,4 +129,4 @@ const killProcessTree = (pid) => {
120
129
  const isCommandMissing = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
121
130
  const isExecaFailure = (error) => typeof error === "object" && error !== null && "shortMessage" in error && typeof error.shortMessage === "string";
122
131
  //#endregion
123
- export { NEON_PLUGIN_NAME, PLUGINS_CLI_TIMEOUT_MS, PLUGIN_SOURCE, neonPluginsRetryCommand, pluginsAddArgs, pluginsChildEnv, pluginsCliFailureMessage, pluginsScopeFlag, quoteNpxArg, runPluginsCli };
132
+ export { NEON_PLUGIN_NAME, PLUGINS_CLI_TIMEOUT_MS, PLUGIN_SKILLS, PLUGIN_SOURCE, neonPluginsRetryCommand, pluginsAddArgs, pluginsChildEnv, pluginsCliFailureMessage, pluginsScopeFlag, quoteNpxArg, runPluginsCli };
@@ -1,3 +1,4 @@
1
+ import { helpCsv } from "../utils/help_text.js";
1
2
  //#region src/skills/catalog.ts
2
3
  const AGENT_SKILLS_SOURCE = "neondatabase/agent-skills";
3
4
  const PLATFORMS_SKILLS_SOURCE = "neondatabase/neon-for-agent-platforms";
@@ -97,5 +98,11 @@ const invocationsForSelection = (selected) => {
97
98
  });
98
99
  };
99
100
  const yesInstallInvocations = () => invocationsForSelection(defaultSkillEntries());
101
+ const skillsHelpValues = () => invocationsForSelection(NEON_SKILL_CATALOG).map((group) => helpCsv(` ${group.source}`, group.skills)).join("\n");
102
+ const skillsYesHelp = () => {
103
+ const omitted = NEON_SKILL_CATALOG.filter((entry) => !entry.defaultSelected).map((entry) => entry.skill);
104
+ if (omitted.length === 0) return "neon skills -y installs every listed skill";
105
+ return `neon skills -y installs every listed skill except ${omitted.join(", ")}`;
106
+ };
100
107
  //#endregion
101
- export { AGENT_SKILLS_SOURCE, NEON_SKILL_CATALOG, PLATFORMS_SKILLS_SOURCE, defaultSkillEntries, invocationsForSelection, listSkillIds, resolveSkillId, uniqueSkillEntries, yesInstallInvocations };
108
+ export { AGENT_SKILLS_SOURCE, NEON_SKILL_CATALOG, PLATFORMS_SKILLS_SOURCE, defaultSkillEntries, invocationsForSelection, listSkillIds, resolveSkillId, skillsHelpValues, skillsYesHelp, uniqueSkillEntries, yesInstallInvocations };
@@ -0,0 +1,29 @@
1
+ //#region src/utils/help_text.ts
2
+ const HELP_WRAP = 78;
3
+ const WRAP_INDENT = " ";
4
+ const helpWidth = () => {
5
+ const columns = process.stderr.columns ?? process.stdout.columns;
6
+ if (typeof columns !== "number" || !Number.isFinite(columns) || columns < 1) return 78;
7
+ return Math.max(24, columns - 1);
8
+ };
9
+ const helpCsv = (label, items, width = helpWidth()) => {
10
+ if (items.length === 0) return "";
11
+ const prefix = `${label}:`;
12
+ const lines = [];
13
+ let line = prefix;
14
+ for (const item of items) {
15
+ const addition = line === prefix ? ` ${item}` : `, ${item}`;
16
+ if (line.length + addition.length <= width) {
17
+ line += addition;
18
+ continue;
19
+ }
20
+ if (line === prefix) lines.push(prefix);
21
+ else lines.push(`${line},`);
22
+ line = `${WRAP_INDENT}${item}`;
23
+ }
24
+ lines.push(line);
25
+ return lines.join("\n");
26
+ };
27
+ const helpEpilogue = (...blocks) => ["", ...blocks.filter((block) => block !== "")].join("\n");
28
+ //#endregion
29
+ export { HELP_WRAP, helpCsv, helpEpilogue, helpWidth };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -63,9 +63,9 @@
63
63
  "yaml": "^2.9.0",
64
64
  "yargs": "17.7.2",
65
65
  "yoctocolors": "^2.1.2",
66
- "@neon/config-runtime": "1.0.3",
67
- "@neon/sdk": "2.2.0",
68
- "@neon/config": "1.0.3"
66
+ "@neon/config": "1.0.4",
67
+ "@neon/config-runtime": "1.0.4",
68
+ "@neon/sdk": "2.3.0"
69
69
  },
70
70
  "optionalDependencies": {
71
71
  "@napi-rs/keyring": "1.3.0",
@@ -97,8 +97,8 @@
97
97
  "typescript": "^5.9.0",
98
98
  "vitest": "^3.0.9",
99
99
  "@neon-internals/cli-core": "0.0.0",
100
- "@neon/e2e-harness": "0.0.0",
101
- "@neon-internals/env-core": "0.0.3"
100
+ "@neon-internals/env-core": "0.0.4",
101
+ "@neon/e2e-harness": "0.0.0"
102
102
  },
103
103
  "publishConfig": {
104
104
  "access": "public",