neon 4.10.2 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -19
- package/dist/commands/bootstrap.js +33 -21
- package/dist/commands/checkout.js +0 -1
- package/dist/commands/init.js +22 -10
- package/dist/commands/link.js +2 -10
- package/dist/commands/mcp.js +4 -4
- package/dist/commands/plugins.js +4 -4
- package/dist/commands/skills.js +12 -9
- package/dist/index.js +14 -0
- package/dist/init/detect_host.js +38 -0
- package/dist/init/plan.js +189 -5
- package/dist/init/tooling.js +48 -13
- package/dist/init/wizard.js +2 -1
- package/dist/mcp/plan.js +16 -3
- package/dist/plugins/plan.js +18 -4
- package/dist/plugins/targets.js +1 -1
- package/dist/skills/plan.js +19 -5
- package/dist/skills/targets.js +1 -1
- package/dist/skills/wizard.js +1 -1
- package/dist/utils/agent_flag.js +13 -0
- package/dist/utils/agent_picker.js +2 -2
- package/package.json +3 -3
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
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
786
|
+
# Named skills into detected agents.
|
|
787
|
+
$ neon skills -y -s neon -s neon-ai-gateway
|
|
778
788
|
|
|
779
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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").
|
|
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:
|
|
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
|
package/dist/commands/init.js
CHANGED
|
@@ -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 {
|
|
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 --
|
|
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
|
|
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
|
-
|
|
30
|
-
type: "
|
|
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
|
|
39
|
-
if (argv.
|
|
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
|
-
|
|
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.
|
|
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,
|
package/dist/commands/link.js
CHANGED
|
@@ -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)).
|
|
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);
|
package/dist/commands/mcp.js
CHANGED
|
@@ -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. --
|
|
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,
|
|
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;
|
package/dist/commands/plugins.js
CHANGED
|
@@ -3,12 +3,12 @@ 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
|
|
@@ -18,7 +18,6 @@ var plugins_exports = /* @__PURE__ */ __exportAll({
|
|
|
18
18
|
describe: () => describe,
|
|
19
19
|
handler: () => handler
|
|
20
20
|
});
|
|
21
|
-
const scopeLabel = (scope) => scope === "project" ? "project" : "user";
|
|
22
21
|
const coerceAgents = (value) => {
|
|
23
22
|
if (value === void 0) return [];
|
|
24
23
|
const list = Array.isArray(value) ? value : [value];
|
|
@@ -28,6 +27,7 @@ const coerceAgents = (value) => {
|
|
|
28
27
|
return item;
|
|
29
28
|
});
|
|
30
29
|
};
|
|
30
|
+
const scopeLabel = (scope) => scope === "project" ? "project" : "user";
|
|
31
31
|
const command = "plugins";
|
|
32
32
|
const describe = "Install the Neon plugin into coding agents";
|
|
33
33
|
const pluginProjectAgents = pluginsInstallableAgents("project");
|
|
@@ -37,7 +37,7 @@ const builder = (argv) => argv.usage("$0 plugins [options]").options({
|
|
|
37
37
|
alias: "y",
|
|
38
38
|
type: "boolean",
|
|
39
39
|
default: false,
|
|
40
|
-
describe: "Skip prompts"
|
|
40
|
+
describe: "Skip prompts. Detected agents (project folders, else the host CLI agent). --global uses installed apps, else the host CLI agent"
|
|
41
41
|
},
|
|
42
42
|
global: {
|
|
43
43
|
type: "boolean",
|
|
@@ -51,7 +51,7 @@ const builder = (argv) => argv.usage("$0 plugins [options]").options({
|
|
|
51
51
|
describe: "Coding agent to install into (repeatable). Skips the agent picker. Values listed below",
|
|
52
52
|
coerce: coerceAgents
|
|
53
53
|
}
|
|
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"));
|
|
54
|
+
}).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
55
|
const handler = async (props) => {
|
|
56
56
|
const cwd = process.cwd();
|
|
57
57
|
const yes = props.yes === true;
|
package/dist/commands/skills.js
CHANGED
|
@@ -4,10 +4,10 @@ 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";
|
|
@@ -39,11 +39,14 @@ const coerceSkills = (value) => {
|
|
|
39
39
|
};
|
|
40
40
|
const command = "skills";
|
|
41
41
|
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({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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({
|
|
43
|
+
yes: { describe: "Skip the confirm prompt" },
|
|
44
|
+
global: {
|
|
45
|
+
type: "boolean",
|
|
46
|
+
default: false,
|
|
47
|
+
describe: "Update user-level skills (skills CLI -g). Default is this directory"
|
|
48
|
+
}
|
|
49
|
+
}).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
50
|
noPassthrough("skills update")(args);
|
|
48
51
|
const agents = args.agent;
|
|
49
52
|
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 +58,7 @@ const builder = (argv) => argv.usage("$0 skills [command] [options]").command("u
|
|
|
55
58
|
alias: "y",
|
|
56
59
|
type: "boolean",
|
|
57
60
|
default: false,
|
|
58
|
-
describe: "Skip prompts"
|
|
61
|
+
describe: "Skip prompts. Detected agents (project folders, else the host CLI agent). --global uses installed apps, else the host CLI agent"
|
|
59
62
|
},
|
|
60
63
|
global: {
|
|
61
64
|
type: "boolean",
|
|
@@ -73,10 +76,10 @@ const builder = (argv) => argv.usage("$0 skills [command] [options]").command("u
|
|
|
73
76
|
alias: "s",
|
|
74
77
|
type: "array",
|
|
75
78
|
string: true,
|
|
76
|
-
describe: "Skill to install (repeatable). Skips the skill picker. Values listed below",
|
|
79
|
+
describe: "Skill to install (repeatable). Skips the skill picker. Does not select agents. Values listed below",
|
|
77
80
|
coerce: coerceSkills
|
|
78
81
|
}
|
|
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", "
|
|
82
|
+
}).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
83
|
const handler = async (props) => {
|
|
81
84
|
assertSkillsNode();
|
|
82
85
|
const cwd = process.cwd();
|
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";
|
|
@@ -125,6 +126,19 @@ async function handleError(msg, err) {
|
|
|
125
126
|
process.exit(0);
|
|
126
127
|
}
|
|
127
128
|
if (err instanceof Error && err.stack) log.debug("Stack: %s", err.stack);
|
|
129
|
+
if (err instanceof Error) {
|
|
130
|
+
const rewritten = rewriteUnknownAgentArg({
|
|
131
|
+
message: err.message,
|
|
132
|
+
argv: process.argv,
|
|
133
|
+
cliName: getCliName()
|
|
134
|
+
});
|
|
135
|
+
if (rewritten !== void 0) {
|
|
136
|
+
const error = new Error(rewritten);
|
|
137
|
+
sendError(error, matchErrorCode(error.message));
|
|
138
|
+
log.error(error.message);
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
128
142
|
if (isNetworkError(err)) {
|
|
129
143
|
log.error(NETWORK_ERROR_MESSAGE);
|
|
130
144
|
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
|
-
|
|
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 };
|
package/dist/init/tooling.js
CHANGED
|
@@ -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
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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({
|
package/dist/init/wizard.js
CHANGED
|
@@ -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(
|
|
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
|
|
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:
|
|
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(", ")}`);
|
package/dist/plugins/plan.js
CHANGED
|
@@ -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
|
|
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:
|
|
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);
|
package/dist/plugins/targets.js
CHANGED
|
@@ -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) => {
|
package/dist/skills/plan.js
CHANGED
|
@@ -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
|
|
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:
|
|
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);
|
package/dist/skills/targets.js
CHANGED
|
@@ -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) => {
|
package/dist/skills/wizard.js
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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.
|
|
3
|
+
"version": "4.11.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": "1.0.5",
|
|
67
66
|
"@neon/config-runtime": "1.0.5",
|
|
68
|
-
"@neon/sdk": "3.0.0"
|
|
67
|
+
"@neon/sdk": "3.0.0",
|
|
68
|
+
"@neon/config": "1.0.5"
|
|
69
69
|
},
|
|
70
70
|
"optionalDependencies": {
|
|
71
71
|
"@napi-rs/keyring": "1.3.0",
|