neon 4.10.1 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,14 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
- import { log } from "../log.js";
3
2
  import { readContextFile } from "../context.js";
4
3
  import { getCliName } from "../utils/cli_name.js";
5
4
  import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
6
5
  import { initChildEnv, spawnCliChild } from "../init/child.js";
7
- import { bootstrapInitStep, directoryIsEmpty, planExistingInit } from "../init/plan.js";
6
+ import { agentSetupLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
7
+ import { coerceAgentFlag } from "../utils/agent_flag.js";
8
+ import { INIT_NEEDS_YES_OR_TERMINAL, assertNamedAgentTooling, bootstrapInitStep, directoryIsEmpty, initPluginAgents, initSkillsMcpAgents, planExistingInit, resolveNamedAgents } from "../init/plan.js";
9
+ import { canPickAgentsInteractively } from "../utils/agent_picker.js";
8
10
  import { runAgentTooling, runInitSteps } from "../init/tooling.js";
9
- import { helpEpilogue } from "../utils/help_text.js";
11
+ import { helpCsv, helpEpilogue } from "../utils/help_text.js";
10
12
  import { existsSync, readdirSync } from "node:fs";
11
13
  import { resolve } from "node:path";
12
14
  //#region src/commands/init.ts
@@ -19,15 +21,18 @@ var init_exports = /* @__PURE__ */ __exportAll({
19
21
  });
20
22
  const command = "init";
21
23
  const describe = "Set up this directory for Neon: agent tooling, a linked project, and neon.ts. In an empty directory, scaffolds a template first. Skills needs Node.js 22.20 or newer.";
22
- const removedProtocol = () => `\`${getCliName()} init --agent\` and \`--data\` were removed. Run \`${getCliName()} init\` or \`${getCliName()} init -y\`.`;
24
+ const removedProtocol = () => `\`${getCliName()} init --data\` was removed. Run \`${getCliName()} init\` or \`${getCliName()} init -y\`.`;
23
25
  const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file", { hidden: true }).option("yes", {
24
26
  alias: "y",
25
27
  type: "boolean",
26
28
  default: false,
27
- describe: "Empty dir: bootstrap --default. Otherwise plugin when a project plugin agent is detected, else skills and MCP, then link --yes and config init --services none. link --yes still asks for a project unless one is already linked"
29
+ describe: "Empty dir: bootstrap --default. Otherwise plugin, or skills and MCP, for project folders, else the host CLI agent. Exits if none. Then link --yes and config init --services none. link --yes still asks for a project unless one is already linked"
28
30
  }).option("agent", {
29
- hidden: true,
30
- type: "boolean"
31
+ alias: "a",
32
+ type: "array",
33
+ string: true,
34
+ describe: "Coding agent to install into (repeatable). Forwarded to plugins, or to skills and mcp. Skips agent selection. Values listed below",
35
+ coerce: coerceAgentFlag
31
36
  }).option("data", {
32
37
  hidden: true,
33
38
  type: "string"
@@ -35,8 +40,8 @@ const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file
35
40
  alias: "o",
36
41
  hidden: true,
37
42
  describe: "Not supported; the commands init runs print their own output"
38
- }).example("$0 init", "Empty dir: bootstrap (scaffold, agent tooling, link). Existing app: agent tooling, link, config init").example("$0 init -y", "Same steps, using each child's defaults").epilogue(helpEpilogue("Interactive agent setup: plugin (recommended), skills and MCP separately, or skip agent setup. Never both plugin and skills+MCP.", "-y installs the plugin when Cursor, Claude Code, or Codex is detected in the project. Otherwise skills and MCP. Then link unless already linked. link --yes may still ask for a project.")).check((argv) => {
39
- if (argv.agent === true || argv.data !== void 0) throw new Error(removedProtocol());
43
+ }).example("$0 init", "Empty dir: bootstrap (scaffold, agent tooling, link). Existing app: agent tooling, link, config init").example("$0 init -y", "Same steps, using each child's defaults").example("$0 init --agent cursor --agent claude-code", "Skip agent selection; install the plugin for those agents").epilogue(helpEpilogue("Interactive agent setup: plugin (recommended), skills and MCP separately, or skip agent setup. Never both plugin and skills+MCP.", "-y installs the plugin when Cursor, Claude Code, or Codex is in project folders, else the host CLI agent. Otherwise skills and MCP. If none are found, it exits: pass --agent <name>, run from a supported agent, or omit -y in a terminal to pick. Then link unless already linked. link --yes may still ask for a project.", "--agent / -a is forwarded to plugins, or to skills and mcp, not both. It skips agent selection, including with -y.", helpCsv("Plugin agents", initPluginAgents()), helpCsv("Skills and MCP agents", initSkillsMcpAgents()))).check((argv) => {
44
+ if (argv.data !== void 0) throw new Error(removedProtocol());
40
45
  if (argv.help !== true && (argv.output === "json" || argv.output === "yaml")) throw new Error(`\`${getCliName()} init\` does not support --output. The commands it runs print their own output.`);
41
46
  return true;
42
47
  }).strict();
@@ -50,6 +55,8 @@ const handler = async (props) => {
50
55
  const names = existsSync(cwd) ? readdirSync(cwd) : [];
51
56
  const contextFile = resolve(cwd, props.contextFile);
52
57
  const yes = props.yes === true;
58
+ const named = resolveNamedAgents(props.agent ?? []);
59
+ assertNamedAgentTooling(named, "init", yes ? { yes: true } : void 0);
53
60
  const run = props.run ?? spawnCliChild;
54
61
  const explicitKey = props.profile ? "" : credentialInputs().apiKeyFlag;
55
62
  const authEnv = explicitKey ? { NEON_API_KEY: explicitKey } : void 0;
@@ -61,26 +68,32 @@ const handler = async (props) => {
61
68
  ...props.analytics === false ? { analytics: false } : {}
62
69
  };
63
70
  if (directoryIsEmpty(names)) {
64
- await runInitSteps([bootstrapInitStep(yes)], {
71
+ if (!yes && !canPickAgentsInteractively()) throw new Error(INIT_NEEDS_YES_OR_TERMINAL);
72
+ await runInitSteps([bootstrapInitStep(yes, named)], {
65
73
  cwd,
66
74
  run,
67
75
  forward,
68
76
  authEnv
69
77
  });
70
- log.info("Done.");
71
78
  return;
72
79
  }
73
- await runAgentTooling({
80
+ if (shouldPrintInitBanner(yes)) printInitBanner();
81
+ const alreadyLinked = isLinked(contextFile);
82
+ const agentSetup = await runAgentTooling({
74
83
  cwd,
75
84
  yes,
76
85
  run,
77
86
  forward,
78
87
  authEnv,
88
+ ...named.length > 0 ? { agents: named } : {},
79
89
  ...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
80
- ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {}
90
+ ...props.detectProjectAgents ? { detectProjectAgents: props.detectProjectAgents } : {},
91
+ ...props.detectAgent ? { detectAgent: props.detectAgent } : {},
92
+ ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {},
93
+ command: "init"
81
94
  });
82
95
  await runInitSteps(planExistingInit({
83
- linked: isLinked(contextFile),
96
+ linked: alreadyLinked,
84
97
  yes,
85
98
  agentSetup: "skip"
86
99
  }), {
@@ -89,7 +102,24 @@ const handler = async (props) => {
89
102
  forward,
90
103
  authEnv
91
104
  });
92
- log.info("Done.");
105
+ printInitDone(formatInitDone({
106
+ heading: "Configured this directory for Neon.",
107
+ rows: [
108
+ {
109
+ label: "Agents",
110
+ value: agentSetupLabel(agentSetup)
111
+ },
112
+ {
113
+ label: "Project",
114
+ value: alreadyLinked ? "already linked" : "linked"
115
+ },
116
+ {
117
+ label: "Config",
118
+ value: "neon.ts"
119
+ }
120
+ ],
121
+ next: []
122
+ }));
93
123
  };
94
124
  //#endregion
95
125
  export { builder, command, describe, handler, initChildEnv, init_exports as t };
@@ -4,10 +4,10 @@ import { log } from "../log.js";
4
4
  import { isNeonApiError, messageFromBody } from "../api.js";
5
5
  import { applyContext, contextBranch, readContextFile, setContext, updateContextFile } from "../context.js";
6
6
  import { getCliName } from "../utils/cli_name.js";
7
+ import { helpEpilogue } from "../utils/help_text.js";
7
8
  import { createBranch, pickBranchInteractively } from "../utils/branch_picker.js";
8
9
  import { autoPullEnvAfterPin } from "./env.js";
9
10
  import { hasNeonConfigFile, initCmd } from "./config.js";
10
- import { helpEpilogue } from "../utils/help_text.js";
11
11
  import { REGIONS } from "./projects.js";
12
12
  import prompts from "prompts";
13
13
  //#region src/commands/link.ts
@@ -31,7 +31,6 @@ const nonInteractiveLinkCommands = () => {
31
31
  };
32
32
  const nonInteractiveLinkHelp = () => nonInteractiveLinkCommands().map((command) => ` ${command}`).join("\n");
33
33
  const orgScopedKeyHint = "Organization-scoped API keys cannot list orgs; pass --org-id.";
34
- const removedAgent = () => `\`${getCliName()} link --agent\` was removed.\nUse:\n${nonInteractiveLinkHelp()}\n${orgScopedKeyHint}`;
35
34
  const command = "link";
36
35
  const describe = "Link the current directory to a Neon project";
37
36
  const builder = (argv) => argv.usage("$0 link [options]").options({
@@ -60,10 +59,6 @@ const builder = (argv) => argv.usage("$0 link [options]").options({
60
59
  describe: "JSON object with link parameters, e.g. '{\"orgId\":\"...\",\"projectId\":\"...\"}' or '{\"orgId\":\"...\",\"projectName\":\"...\",\"regionId\":\"...\"}'. Flags take precedence over fields in --params.",
61
60
  type: "string"
62
61
  },
63
- agent: {
64
- hidden: true,
65
- type: "boolean"
66
- },
67
62
  yes: {
68
63
  alias: "y",
69
64
  describe: "Skip the \"already linked\" confirmation, and pin the project's default branch when linking a project that has more than one.",
@@ -92,10 +87,7 @@ const builder = (argv) => argv.usage("$0 link [options]").options({
92
87
  ["$0 link --branch-id br-…", "Pin a branch in the already-linked project"],
93
88
  ["$0 link --no-checks --org-id org-… --project-id polished-snowflake-12345678", "Write the context offline (no API calls, no verification)"],
94
89
  ["$0 link --clear", "Forget the current org/project/branch context"]
95
- ]).epilogue(helpEpilogue("Non-interactive (CI, scripts, agents):", ...nonInteractiveLinkCommands().map((command) => ` ${command}`), orgScopedKeyHint)).check((argv) => {
96
- if (argv.agent === true) throw new Error(removedAgent());
97
- return true;
98
- });
90
+ ]).epilogue(helpEpilogue("Non-interactive (CI, scripts, agents):", ...nonInteractiveLinkCommands().map((command) => ` ${command}`), orgScopedKeyHint)).strict();
99
91
  const handler = async (props) => {
100
92
  if (props.clear) {
101
93
  clearContext(props.contextFile);
@@ -4,11 +4,11 @@ import { readContextFile } from "../context.js";
4
4
  import { getCliName } from "../utils/cli_name.js";
5
5
  import { writer } from "../writer.js";
6
6
  import { noPassthrough, single } from "../utils/flags.js";
7
+ import { NEON_MCP_CATEGORIES, NEON_MCP_URL, existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
8
+ import { mcpInstallableAgents, resolveInstallTargets } from "../mcp/targets.js";
7
9
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
8
10
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
9
- import { NEON_MCP_CATEGORIES, NEON_MCP_URL, existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
10
11
  import { mintMcpApiKey, mintedKeyRevokeCommand, withdrawMintedKey } from "../mcp/mint.js";
11
- import { mcpInstallableAgents, resolveInstallTargets } from "../mcp/targets.js";
12
12
  import { confirmMcpInstall } from "../mcp/wizard.js";
13
13
  import { resolveMcpPlan } from "../mcp/plan.js";
14
14
  //#region src/commands/mcp.ts
@@ -38,7 +38,7 @@ const builder = (argv) => argv.usage("$0 mcp [options]").options({
38
38
  alias: "y",
39
39
  type: "boolean",
40
40
  default: false,
41
- describe: "Skip prompts. Defaults listed below. --project, --oauth, --agent, --read-only, --project-id and --category still apply"
41
+ describe: "Skip prompts. Defaults listed below. --agent, --project, --oauth, --read-only, --project-id and --category still apply"
42
42
  },
43
43
  agent: {
44
44
  alias: "a",
@@ -86,7 +86,7 @@ const builder = (argv) => argv.usage("$0 mcp [options]").options({
86
86
  return parseMcpCategories(parts);
87
87
  }
88
88
  }
89
- }).example("$0 mcp", "Interactive: config location, agents, auth, then confirm").example("$0 mcp -y", "Global config, detected agents, reuse or mint an API key").example("$0 mcp --oauth", "Install with OAuth; the agent signs in on first use").example("$0 mcp --project", "Write project-level config").example("$0 mcp --agent cursor --agent claude-code", "Install into specific agents").example("$0 mcp --read-only", "Hide write tools via ?readonly=true").example("$0 mcp --project-id <id>", "Pin tools to one project via ?projectId=").example("$0 mcp --category querying --category schema", "Limit tools to those categories").epilogue(helpEpilogue(`Installs ${NEON_MCP_URL}`, helpCsv("Supported agents at global scope", mcpGlobalAgents), helpCsv("--project does not support", mcpProjectDroppedAgents), helpCsv("Supported categories", NEON_MCP_CATEGORIES), "neon mcp -y:", " global config", " every globally detected agent", " reuse an existing Neon MCP API key, else mint an account-wide key", " write tools on, all categories", " no project pin (including from .neon)")).strict().check(noPassthrough("mcp"));
89
+ }).example("$0 mcp", "Interactive: config location, agents, auth, then confirm").example("$0 mcp -y", "Global config, installed apps else the host CLI agent, reuse or mint an API key").example("$0 mcp --oauth", "Install with OAuth; the agent signs in on first use").example("$0 mcp --agent cursor --agent claude-code", "Install into specific agents").example("$0 mcp --project", "Write project-level config").example("$0 mcp --read-only", "Hide write tools via ?readonly=true").example("$0 mcp --project-id <id>", "Pin tools to one project via ?projectId=").example("$0 mcp --category querying --category schema", "Limit tools to those categories").epilogue(helpEpilogue(`Installs ${NEON_MCP_URL}`, helpCsv("Supported agents at global scope", mcpGlobalAgents), helpCsv("--project does not support", mcpProjectDroppedAgents), helpCsv("Supported categories", NEON_MCP_CATEGORIES), "neon mcp -y:", " global config", " globally installed apps, else the host CLI agent", " reuse an existing Neon MCP API key, else mint an account-wide key", " write tools on, all categories", " no project pin (including from .neon)")).strict().check(noPassthrough("mcp"));
90
90
  const handler = async (props) => {
91
91
  const cwd = process.cwd();
92
92
  const interactive = canPickAgentsInteractively() && props.yes !== true;
@@ -3,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;
@@ -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({ global: {
43
- type: "boolean",
44
- default: false,
45
- describe: "Update user-level skills (skills CLI -g). Default is this directory"
46
- } }).example("$0 skills update", "Interactive: confirm, then update skills in this directory").example("$0 skills update -y", "Update skills in this directory without prompting").example("$0 skills update --global -y", "Update user-level skills").strict().hide("agent").hide("skill").check((args) => {
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", "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"));
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/errors.js CHANGED
@@ -2,7 +2,8 @@
2
2
  const ERROR_MATCHERS = [
3
3
  [/^Unknown command: (.*)$/, "UNKNOWN_COMMAND"],
4
4
  [/^Missing required argument: (.*)$/, "MISSING_ARGUMENT"],
5
- [/^Failed to open web browser. (.*)$/, "AUTH_BROWSER_FAILED"]
5
+ [/^Failed to open web browser. (.*)$/, "AUTH_BROWSER_FAILED"],
6
+ [/^Setup did not finish\.$/, "NEON_INIT_FAILED"]
6
7
  ];
7
8
  const matchErrorCode = (message) => {
8
9
  if (!message) return "UNKNOWN_ERROR";
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);
@@ -162,8 +176,9 @@ async function handleError(msg, err) {
162
176
  }
163
177
  } else {
164
178
  const error = err instanceof Error ? err : new Error(msg || "Unknown error");
165
- sendError(error, matchErrorCode(error.message));
166
- log.error(error.message);
179
+ const code = matchErrorCode(error.message);
180
+ sendError(error, code);
181
+ if (code !== "NEON_INIT_FAILED") log.error(error.message);
167
182
  return false;
168
183
  }
169
184
  }
@@ -0,0 +1,44 @@
1
+ import { isCi } from "../env.js";
2
+ import chalk from "chalk";
3
+ //#region src/init/chrome.ts
4
+ const NEON_GREEN = "#4BB578";
5
+ const INIT_BANNER_LINES = [
6
+ " ██╗ ██╗██████╗ ██████╗ ██╗ ██╗",
7
+ " ███╗ ██║██╔═══╝ ██╔═══██╗███╗ ██║",
8
+ " ████╗██║██████╗ ██║ ██║████╗██║",
9
+ " ██╔████║██╔═══╝ ██║ ██║██╔████║",
10
+ " ██║╚███║██████╗ ╚██████╔╝██║╚███║",
11
+ " ╚═╝ ╚══╝╚═════╝ ╚═════╝ ╚═╝ ╚══╝"
12
+ ];
13
+ const formatInitBanner = () => INIT_BANNER_LINES.join("\n");
14
+ const shouldPrintInitBanner = (yes) => !yes && !isCi() && Boolean(process.stdout.isTTY);
15
+ const printInitBanner = () => {
16
+ process.stdout.write(`\n${chalk.hex(NEON_GREEN)(formatInitBanner())}\n\n${chalk.dim("Let's get this directory set up with Neon.")}\n\n`);
17
+ };
18
+ const formatInitDone = (input) => {
19
+ const labelWidth = Math.max(0, ...input.rows.map((row) => row.label.length));
20
+ const body = input.rows.length === 0 ? "" : `\n\n${input.rows.map((row) => ` ${row.label.padEnd(labelWidth)} ${row.value}`).join("\n")}`;
21
+ const next = input.next.length === 0 ? "" : `\n\nNext:\n${input.next.map((line) => ` ${line}`).join("\n")}`;
22
+ const rule = "-".repeat(input.heading.length);
23
+ return `${input.heading}\n${rule}${body}${next}\n`;
24
+ };
25
+ const printInitDone = (text) => {
26
+ const painted = (text.endsWith("\n") ? text.slice(0, -1) : text).split("\n").map((line, index) => {
27
+ if (index === 0) return chalk.hex(NEON_GREEN).bold(line);
28
+ if (/^-{3,}$/.test(line)) return chalk.hex(NEON_GREEN)(line);
29
+ if (line === "Next:") return chalk.bold(line);
30
+ return line;
31
+ });
32
+ process.stdout.write(`\n${painted.join("\n")}\n\n`);
33
+ };
34
+ const agentSetupLabel = (setup) => {
35
+ if (setup === "plugin") return "plugin";
36
+ if (setup === "skills-mcp") return "skills and MCP";
37
+ return "skipped";
38
+ };
39
+ const agentSetupDoneLabel = (input) => {
40
+ if (!input.ran && input.setup !== "skip") return "not run";
41
+ return agentSetupLabel(input.setup);
42
+ };
43
+ //#endregion
44
+ export { INIT_BANNER_LINES, NEON_GREEN, agentSetupDoneLabel, agentSetupLabel, formatInitBanner, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner };
@@ -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 };