neon 6.2.3 → 6.3.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.
@@ -6,10 +6,11 @@ import { getCliName } from "../utils/cli_name.js";
6
6
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
7
7
  import { writer } from "../writer.js";
8
8
  import { noPassthrough, single } from "../utils/flags.js";
9
+ import { AGENT_MCP_SKIP_MESSAGE } from "../init/copy.js";
9
10
  import { NEON_MCP_CATEGORIES, NEON_MCP_URL, existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
10
11
  import { mcpInstallableAgents, resolveInstallTargets } from "../mcp/targets.js";
11
12
  import { mintMcpApiKey, mintedKeyRevokeCommand, withdrawMintedKey } from "../mcp/mint.js";
12
- import { canPickAgentsInteractively } from "../utils/agent_picker.js";
13
+ import { canPickAgentsInteractively, skippableAgentPicker } from "../utils/agent_picker.js";
13
14
  import { resolveMcpPlan } from "../mcp/plan.js";
14
15
  //#region src/commands/mcp.ts
15
16
  var mcp_exports = /* @__PURE__ */ __exportAll({
@@ -113,7 +114,8 @@ const setupNeonMcp = async (options) => {
113
114
  readOnly: options.readOnly === true,
114
115
  projectId: options.projectId,
115
116
  categories: options.category ?? [],
116
- linkedProjectId
117
+ linkedProjectId,
118
+ pickAgents: skippableAgentPicker(options.allowAgentSkip, AGENT_MCP_SKIP_MESSAGE)
117
119
  });
118
120
  const { install, skipped } = resolveInstallTargets({
119
121
  agents: plan.agents,
@@ -4,11 +4,12 @@ import { recordCommandSuccessExtras } from "../analytics.js";
4
4
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
5
5
  import { writer } from "../writer.js";
6
6
  import { noPassthrough } from "../utils/flags.js";
7
+ import { AGENT_PLUGIN_SKIP_MESSAGE } from "../init/copy.js";
7
8
  import { getAgentDisplayName } from "../mcp/agents.js";
8
9
  import { NEON_MCP_URL } from "../mcp/install.js";
9
10
  import "../init/agents.js";
10
11
  import { pluginsInstallableAgents } from "../plugins/targets.js";
11
- import { canPickAgentsInteractively } from "../utils/agent_picker.js";
12
+ import { canPickAgentsInteractively, skippableAgentPicker } from "../utils/agent_picker.js";
12
13
  import { resolvePluginsPlan } from "../plugins/plan.js";
13
14
  import { NEON_PLUGIN_NAME, PLUGIN_SKILLS, PLUGIN_SOURCE, neonPluginsRetryCommand, pluginsAddArgs, runPluginsCli } from "../plugins/run.js";
14
15
  //#region src/commands/plugins.ts
@@ -71,10 +72,12 @@ const installPlugins = async (options) => {
71
72
  agents: options.agents ?? [],
72
73
  yes,
73
74
  cwd,
74
- interactive
75
+ interactive,
76
+ pickAgents: skippableAgentPicker(options.allowAgentSkip, AGENT_PLUGIN_SKIP_MESSAGE)
75
77
  });
76
78
  for (const agent of plan.skipped) log.warning("Skipping %s: no plugins mapping.", getAgentDisplayName(agent));
77
79
  for (const agent of plan.userScopeSkipped) log.warning("Skipping %s: plugins are user-level. Pass --global.", getAgentDisplayName(agent));
80
+ for (const { agent, command } of plan.missingCommands) log.warning("Skipping %s: the plugin installs through \"%s\", which was not found on PATH.", getAgentDisplayName(agent), command);
78
81
  const rows = [];
79
82
  const failed = [];
80
83
  const scope = scopeLabel(plan.scope);
@@ -147,7 +150,8 @@ const reportPluginsInstall = (props, outcome) => {
147
150
  title: "Plugins"
148
151
  });
149
152
  out.end();
150
- if (outcome.failed.length === 0) log.info(outcome.scope === "project" ? "Installed the Neon plugin (project)." : "Installed the Neon plugin (user).");
153
+ if (outcome.rows.length === 0) log.info("No Neon plugins installed.");
154
+ else if (outcome.failed.length === 0) log.info(outcome.scope === "project" ? "Installed the Neon plugin (project)." : "Installed the Neon plugin (user).");
151
155
  };
152
156
  const handler = async (props) => {
153
157
  const outcome = await installPlugins({
@@ -4,10 +4,11 @@ import { recordCommandSuccessExtras } from "../analytics.js";
4
4
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
5
5
  import { writer } from "../writer.js";
6
6
  import { noPassthrough } from "../utils/flags.js";
7
+ import { AGENT_SKILLS_SKIP_MESSAGE } from "../init/copy.js";
7
8
  import { getAgentDisplayName } from "../mcp/agents.js";
8
9
  import "../init/agents.js";
9
10
  import { mappedSkillsAgentNames, skillsInstallableAgents } from "../skills/targets.js";
10
- import { canPickAgentsInteractively } from "../utils/agent_picker.js";
11
+ import { canPickAgentsInteractively, skippableAgentPicker } from "../utils/agent_picker.js";
11
12
  import { skillsHelpValues, skillsYesHelp } from "../skills/catalog.js";
12
13
  import { confirmSkillsUpdate } from "../skills/wizard.js";
13
14
  import { assertSkillsCanRun, resolveSkillsPlan } from "../skills/plan.js";
@@ -102,7 +103,8 @@ const installSkills = async (options) => {
102
103
  skills: options.skills ?? [],
103
104
  yes,
104
105
  cwd,
105
- interactive
106
+ interactive,
107
+ pickAgents: skippableAgentPicker(options.allowAgentSkip, AGENT_SKILLS_SKIP_MESSAGE)
106
108
  });
107
109
  const mapped = mappedSkillsAgentNames(plan.agents);
108
110
  for (const agent of plan.skipped) log.warning("Skipping %s: no skills mapping.", getAgentDisplayName(agent));
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { log } from "./log.js";
2
2
  import pkg_default from "./pkg.js";
3
- import { isNeonApiError, messageFromBody } from "./api.js";
3
+ import { codeFromBody, isNeonApiError, messageFromBody } from "./api.js";
4
4
  import { getAuthContext } from "./auth_context.js";
5
5
  import { r as defaultDir } from "./_chunks/paths-DMq0Lt7a.js";
6
6
  import { ensureConfigDir } from "./config.js";
@@ -14,6 +14,7 @@ import { recoverFrom401 } from "./auth_recovery.js";
14
14
  import { showHelp } from "./help.js";
15
15
  import { rewriteUnknownAgentArg } from "./init/plan.js";
16
16
  import commands_default from "./commands/index.js";
17
+ import { recoverFromSSO } from "./sso_recovery.js";
17
18
  import { notifyIfUpdateAvailable } from "./update_notifier.js";
18
19
  import { fillInArgs, resolveApiKeyFromEnv } from "./utils/middlewares.js";
19
20
  import yargs from "yargs";
@@ -165,6 +166,10 @@ async function handleError(msg, err, canRetry) {
165
166
  log.error("Request timed out");
166
167
  sendError(err, "REQUEST_TIMEOUT");
167
168
  return false;
169
+ } else if (codeFromBody(err.data) === "SSO_AUTHORIZATION_REQUIRED" || codeFromBody(err.data) === "SSO_ORG_CREDS_ONLY") {
170
+ if (await recoverFromSSO(err, canRetry)) return true;
171
+ sendError(err, "AUTH_FAILED");
172
+ return false;
168
173
  } else if (err.status === 401) {
169
174
  sendError(err, "AUTH_FAILED");
170
175
  return await recoverFrom401(canRetry);
@@ -49,6 +49,8 @@ const printInitDone = (text) => {
49
49
  const agentSetupLabel = (setup) => {
50
50
  if (setup === "plugin") return "plugin";
51
51
  if (setup === "skills-mcp") return "skills and MCP";
52
+ if (setup === "skills") return "skills";
53
+ if (setup === "mcp") return "MCP";
52
54
  return "skipped";
53
55
  };
54
56
  const agentSetupDoneLabel = (input) => {
@@ -56,13 +58,31 @@ const agentSetupDoneLabel = (input) => {
56
58
  return agentSetupLabel(input.setup);
57
59
  };
58
60
  const formatAgentIds = (agents) => agents.join(", ");
61
+ const SETUP_LABELS = {
62
+ skip: "skipped",
63
+ plugin: "Neon plugin",
64
+ skills: "skills",
65
+ "skills-mcp": "skills and MCP",
66
+ mixed: "plugin and skills/MCP"
67
+ };
68
+ /** Names the agents behind each mechanism, since init picks skills and MCP agents separately. */
59
69
  const agentsRowValue = (input) => {
60
- if (input.setup === null || input.setup === "skip") return "skipped";
61
- if (input.setup === "skills") return "default Neon skills in this directory";
62
- const ids = formatAgentIds(input.agents);
63
- if (input.setup === "plugin") return ids.length > 0 ? `Neon plugin: ${ids}` : "Neon plugin";
64
- if (input.setup === "skills-mcp") return ids.length > 0 ? `skills and MCP: ${ids}` : "skills and MCP";
65
- return ids.length > 0 ? `plugin and skills/MCP: ${ids}` : "plugin and skills/MCP";
70
+ const { tooling } = input;
71
+ const parts = [];
72
+ const add = (label, ids) => {
73
+ const ran = ids.filter((id) => input.installed.includes(id));
74
+ if (ran.length > 0) parts.push(`${label}: ${formatAgentIds(ran)}`);
75
+ };
76
+ if (tooling.setup === "plugin" || tooling.setup === "skills") add(SETUP_LABELS[tooling.setup], tooling.agents);
77
+ else if (tooling.setup === "skills-mcp" || tooling.setup === "mixed") {
78
+ if (tooling.setup === "mixed") add("Neon plugin", tooling.pluginAgents);
79
+ if (tooling.skillsAgents.length === tooling.mcpAgents.length && tooling.skillsAgents.every((id) => tooling.mcpAgents.includes(id))) add("skills and MCP", tooling.skillsAgents);
80
+ else {
81
+ add("skills", tooling.skillsAgents);
82
+ add("MCP", tooling.mcpAgents);
83
+ }
84
+ }
85
+ return parts.length > 0 ? parts.join("; ") : SETUP_LABELS[tooling.setup];
66
86
  };
67
87
  const projectRowValue = (link) => {
68
88
  switch (link) {
package/dist/init/copy.js CHANGED
@@ -16,6 +16,12 @@ const AGENT_SETUP_SKIP_DESCRIPTION = "Continue to project setup without changing
16
16
  const AGENT_PICKER_MESSAGE = "Which coding agents should receive Neon tooling? (space to toggle, enter to confirm)";
17
17
  const AGENT_DETECTED_DESCRIPTION = "Detected on this machine or in this directory.";
18
18
  const AGENT_OTHER_DESCRIPTION = "Install for this agent.";
19
+ const agentMissingCommandDescription = (command) => `Detected, but "${command}" was not found on PATH. Its plugin cannot install.`;
20
+ const AGENT_PLUGIN_SKIP_MESSAGE = "No agents selected. Continue without installing the Neon plugin?";
21
+ const AGENT_SKILLS_PICKER_MESSAGE = "Which coding agents should receive Neon skills? (space to toggle, enter to confirm)";
22
+ const AGENT_MCP_PICKER_MESSAGE = "Which coding agents should receive the Neon MCP server? (space to toggle, enter to confirm)";
23
+ const AGENT_SKILLS_SKIP_MESSAGE = "No agents selected. Continue without installing Neon skills?";
24
+ const AGENT_MCP_SKIP_MESSAGE = "No agents selected. Continue without configuring the Neon MCP server?";
19
25
  const SKILLS_PICKER_MESSAGE = "Which Neon skills should be installed? (space to toggle, enter to confirm)";
20
26
  const MCP_SCOPE_MESSAGE = "Where should the Neon MCP server be configured?";
21
27
  const MCP_SCOPE_GLOBAL_TITLE = "User configuration";
@@ -130,4 +136,4 @@ const headingForOutcome = (outcome) => {
130
136
  }
131
137
  };
132
138
  //#endregion
133
- export { AGENT_DETECTED_DESCRIPTION, AGENT_OTHER_DESCRIPTION, AGENT_PICKER_MESSAGE, AGENT_SETUP_MESSAGE, AGENT_SETUP_PLUGIN_DESCRIPTION, AGENT_SETUP_PLUGIN_TITLE, AGENT_SETUP_SKILLS_DESCRIPTION, AGENT_SETUP_SKILLS_TITLE, AGENT_SETUP_SKIP_DESCRIPTION, AGENT_SETUP_SKIP_TITLE, CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, CONFIG_CONFIRM_HINT, CONFIG_CONFIRM_MESSAGE, HEADING_CANCELLED, HEADING_COMPLETE, HEADING_FAILED, HEADING_PENDING, INIT_SUBTITLE, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, MCP_SCOPED_NEEDS_PROJECT, MCP_SCOPE_GLOBAL_DESCRIPTION, MCP_SCOPE_GLOBAL_TITLE, MCP_SCOPE_MESSAGE, MCP_SCOPE_PROJECT_DESCRIPTION, MCP_SCOPE_PROJECT_TITLE, MODE_CUSTOM_DESCRIPTION, MODE_CUSTOM_TITLE, MODE_MESSAGE, MODE_RECOMMENDED_DESCRIPTION, MODE_RECOMMENDED_TITLE, NON_TTY_AGENT_SETUP, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, NO_AGENT_SETUP_CONFLICT, PACKAGE_MANAGER_MESSAGE, PROGRESS, PROJECT_SETUP_CLAIMABLE_DESCRIPTION, PROJECT_SETUP_CLAIMABLE_TITLE, PROJECT_SETUP_LINK_DESCRIPTION, PROJECT_SETUP_LINK_TITLE, PROJECT_SETUP_MESSAGE, SERVICES_HINT, SERVICES_MESSAGE, SKILLS_PICKER_MESSAGE, YES_LINK_NEEDS_AUTH, claimableNext, claimableThenServicesNext, envPullFailedNext, existingConfigNext, extraServicesNext, headingForOutcome, installFailedNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, namedAgentsUnavailable, skippedLinkNext, unattendedUnauthedNext };
139
+ export { AGENT_DETECTED_DESCRIPTION, AGENT_MCP_PICKER_MESSAGE, AGENT_MCP_SKIP_MESSAGE, AGENT_OTHER_DESCRIPTION, AGENT_PICKER_MESSAGE, AGENT_PLUGIN_SKIP_MESSAGE, AGENT_SETUP_MESSAGE, AGENT_SETUP_PLUGIN_DESCRIPTION, AGENT_SETUP_PLUGIN_TITLE, AGENT_SETUP_SKILLS_DESCRIPTION, AGENT_SETUP_SKILLS_TITLE, AGENT_SETUP_SKIP_DESCRIPTION, AGENT_SETUP_SKIP_TITLE, AGENT_SKILLS_PICKER_MESSAGE, AGENT_SKILLS_SKIP_MESSAGE, CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, CONFIG_CONFIRM_HINT, CONFIG_CONFIRM_MESSAGE, HEADING_CANCELLED, HEADING_COMPLETE, HEADING_FAILED, HEADING_PENDING, INIT_SUBTITLE, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, MCP_SCOPED_NEEDS_PROJECT, MCP_SCOPE_GLOBAL_DESCRIPTION, MCP_SCOPE_GLOBAL_TITLE, MCP_SCOPE_MESSAGE, MCP_SCOPE_PROJECT_DESCRIPTION, MCP_SCOPE_PROJECT_TITLE, MODE_CUSTOM_DESCRIPTION, MODE_CUSTOM_TITLE, MODE_MESSAGE, MODE_RECOMMENDED_DESCRIPTION, MODE_RECOMMENDED_TITLE, NON_TTY_AGENT_SETUP, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, NO_AGENT_SETUP_CONFLICT, PACKAGE_MANAGER_MESSAGE, PROGRESS, PROJECT_SETUP_CLAIMABLE_DESCRIPTION, PROJECT_SETUP_CLAIMABLE_TITLE, PROJECT_SETUP_LINK_DESCRIPTION, PROJECT_SETUP_LINK_TITLE, PROJECT_SETUP_MESSAGE, SERVICES_HINT, SERVICES_MESSAGE, SKILLS_PICKER_MESSAGE, YES_LINK_NEEDS_AUTH, agentMissingCommandDescription, claimableNext, claimableThenServicesNext, envPullFailedNext, existingConfigNext, extraServicesNext, headingForOutcome, installFailedNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, namedAgentsUnavailable, skippedLinkNext, unattendedUnauthedNext };
package/dist/init/mode.js CHANGED
@@ -18,10 +18,8 @@ const assertAgentSetupFlags = (input) => {
18
18
  };
19
19
  const inferInitAgentSetup = (input) => {
20
20
  if (input.skipAgents) return { kind: "skip" };
21
- const hasSkills = hasSkillsFlag(input.skills);
22
- if (hasSkills && input.hasMcpFlags) return { kind: "skills-mcp" };
23
- if (hasSkills) return { kind: "skills" };
24
21
  if (input.hasMcpFlags) return { kind: "skills-mcp" };
22
+ if (hasSkillsFlag(input.skills)) return { kind: "skills" };
25
23
  if (input.namedAgents || input.yes) return { kind: "auto" };
26
24
  if (input.canAsk) return { kind: "ask" };
27
25
  throw new Error(NON_TTY_AGENT_SETUP);
package/dist/init/plan.js CHANGED
@@ -201,8 +201,9 @@ const chooseYesAgentTooling = (agents) => {
201
201
  };
202
202
  };
203
203
  const FALLBACK_SKILLS_AGENTS = ["cursor", "codex"];
204
- const splitInitTooling = (agents, pluginScope, mcpConfigLocation = pluginScope) => {
205
- const pluginSet = new Set(pluginsInstallableAgents(pluginScope));
204
+ const splitInitTooling = (agents, pluginScope, mcpConfigLocation = pluginScope, unavailablePluginAgents = []) => {
205
+ const unavailable = new Set(unavailablePluginAgents);
206
+ const pluginSet = new Set(pluginsInstallableAgents(pluginScope).filter((id) => !unavailable.has(id)));
206
207
  const mcpSet = new Set(mcpInstallableAgents(mcpConfigLocation));
207
208
  const pluginAgents = uniqueAgentIds(agents.filter((id) => pluginSet.has(id)));
208
209
  const rest = uniqueAgentIds(agents.filter((id) => !pluginSet.has(id)));
@@ -226,8 +227,8 @@ const splitInitTooling = (agents, pluginScope, mcpConfigLocation = pluginScope)
226
227
  mcpAgents
227
228
  };
228
229
  };
229
- const recommendedTooling = (detected) => {
230
- const split = splitInitTooling(detected, "global");
230
+ const recommendedTooling = (detected, unavailablePluginAgents = []) => {
231
+ const split = splitInitTooling(detected, "global", "global", unavailablePluginAgents);
231
232
  if (split.setup === "skip") return {
232
233
  setup: "skills",
233
234
  agents: FALLBACK_SKILLS_AGENTS
@@ -297,7 +298,12 @@ const planInitToolingSteps = (input) => {
297
298
  default: return input.tooling;
298
299
  }
299
300
  };
300
- const funnelAgentSetup = (tooling) => tooling.setup;
301
+ const funnelAgentSetup = (tooling) => {
302
+ if (tooling.setup !== "skills-mcp") return tooling.setup;
303
+ if (tooling.skillsAgents.length === 0) return "mcp";
304
+ if (tooling.mcpAgents.length === 0) return "skills";
305
+ return "skills-mcp";
306
+ };
301
307
  const planYesAgentSteps = (tooling) => planToolingSteps(tooling, {
302
308
  yes: true,
303
309
  named: false
package/dist/init/run.js CHANGED
@@ -13,8 +13,9 @@ import { agentsRowValue, configRowValue, formatInitDone, headingForKind, printIn
13
13
  import { inferPackageManager, resolvePackageManager } from "../utils/package_manager.js";
14
14
  import { ConfigInstallFailed, hasNeonConfigFile, initCmd, neonConfigFilename } from "../commands/config.js";
15
15
  import { runAuthenticatedLink } from "./link.js";
16
+ import { getAgentDisplayName } from "../mcp/agents.js";
16
17
  import { mcpInstallableAgents } from "../mcp/targets.js";
17
- import { pluginsInstallableAgents } from "../plugins/targets.js";
18
+ import { missingPluginsCommands, pluginsInstallableAgents } from "../plugins/targets.js";
18
19
  import { skillsInstallableAgents } from "../skills/targets.js";
19
20
  import { FALLBACK_SKILLS_AGENTS, assertNamedAgentTooling, funnelAgentSetup, planInitToolingSteps, recommendedTooling, resolveNamedAgents, splitInitTooling } from "./plan.js";
20
21
  import { pullInitEnv } from "./auth.js";
@@ -56,11 +57,10 @@ const pluginScopeFor = (agents, preferred) => {
56
57
  const project = new Set(pluginsInstallableAgents("project"));
57
58
  return agents.some((id) => !project.has(id) && pluginsInstallableAgents("global").includes(id)) ? "global" : "project";
58
59
  };
59
- const availableForSetup = (setup, scope) => {
60
- if (setup === "plugin") return pluginsInstallableAgents(scope);
61
- if (setup === "skip") return [];
62
- if (setup === "skills") return [...skillsInstallableAgents()];
63
- return [.../* @__PURE__ */ new Set([...skillsInstallableAgents(), ...mcpInstallableAgents(scope === "project" ? "project" : "global")])];
60
+ const warnMissingPluginCommands = (missing, tooling) => {
61
+ if (tooling.setup !== "skills-mcp" && tooling.setup !== "mixed") return;
62
+ const routed = /* @__PURE__ */ new Set([...tooling.skillsAgents, ...tooling.mcpAgents]);
63
+ for (const { agent, command } of missing) if (routed.has(agent)) log.warning("%s: \"%s\" was not found on PATH, so its plugin cannot install. Installing Neon skills and MCP instead.", getAgentDisplayName(agent), command);
64
64
  };
65
65
  const nonEmptyAgents = (ids, fallback) => {
66
66
  const [first, ...rest] = ids;
@@ -75,14 +75,15 @@ const skillsTooling = (ids, fallback) => {
75
75
  agents
76
76
  };
77
77
  };
78
- const skillsMcpTooling = (ids, scope, fallback) => {
79
- const mcpConfigLocation = scope === "project" ? "project" : "global";
80
- let selected = ids;
81
- if (selected.length === 0 && fallback) selected = FALLBACK_SKILLS_AGENTS;
78
+ const skillsMcpTooling = (skillsSelection, mcpSelection, mcpConfigLocation, fallback) => {
79
+ const useFallback = fallback && skillsSelection?.length === 0 && mcpSelection?.length === 0;
80
+ const selectedSkills = (useFallback ? FALLBACK_SKILLS_AGENTS : skillsSelection ?? []).filter((id) => skillsInstallableAgents().includes(id));
81
+ const selectedMcp = (useFallback ? FALLBACK_SKILLS_AGENTS : mcpSelection ?? []).filter((id) => mcpInstallableAgents(mcpConfigLocation).includes(id));
82
+ if (selectedMcp.length === 0) return skillsTooling(selectedSkills, false);
82
83
  return {
83
84
  setup: "skills-mcp",
84
- skillsAgents: selected.filter((id) => skillsInstallableAgents().includes(id)),
85
- mcpAgents: selected.filter((id) => mcpInstallableAgents(mcpConfigLocation).includes(id))
85
+ skillsAgents: selectedSkills,
86
+ mcpAgents: selectedMcp
86
87
  };
87
88
  };
88
89
  const validateMcpConfigLocationSupport = (ids, location) => {
@@ -103,13 +104,18 @@ const pickOrDetectAgents = async (input) => {
103
104
  return selected;
104
105
  }
105
106
  const detected = input.detected.filter((id) => input.available.includes(id));
107
+ const preselected = (input.preselected ?? input.detected).filter((id) => input.available.includes(id));
106
108
  if (input.pickAgents !== void 0) return input.pickAgents({
107
109
  available: [...input.available],
108
- detected
110
+ detected: preselected,
111
+ setup: input.setup
109
112
  });
110
113
  if (input.interactive) return pickInitAgentsInteractively({
111
114
  available: [...input.available],
112
- detected
115
+ detected,
116
+ preselected,
117
+ ...input.missingCommands !== void 0 ? { missingCommands: input.missingCommands } : {},
118
+ setup: input.setup
113
119
  });
114
120
  return detected;
115
121
  };
@@ -229,7 +235,9 @@ const runInit = async (props) => {
229
235
  }
230
236
  const recommended = mode === "recommended";
231
237
  const targets = named.length > 0 ? named : detection.detectedAgents;
232
- let agentSetupChoice = "plugin";
238
+ const missingPluginCommands = missingPluginsCommands(targets, { cwd });
239
+ const unavailablePluginAgents = missingPluginCommands.map((row) => row.agent);
240
+ const pluginReady = (agents) => agents.filter((id) => !unavailablePluginAgents.includes(id));
233
241
  let tooling = { setup: "skip" };
234
242
  let mcpAuth = props.mcpAuth;
235
243
  let mcpConfigLocation = props.mcpConfigLocation ?? "global";
@@ -238,9 +246,9 @@ const runInit = async (props) => {
238
246
  let pluginScope = recommended ? "global" : "project";
239
247
  if (recommended) {
240
248
  pluginScope = "global";
241
- tooling = named.length > 0 ? splitInitTooling(named, "global") : recommendedTooling(detection.detectedAgents);
249
+ tooling = named.length > 0 ? splitInitTooling(named, "global", "global", unavailablePluginAgents) : recommendedTooling(detection.detectedAgents, unavailablePluginAgents);
242
250
  if (named.length === 0 && detection.detectedAgents.length === 0) printInitProgress(NO_AGENTS_FALLBACK_STATUS);
243
- agentSetupChoice = funnelAgentSetup(tooling);
251
+ warnMissingPluginCommands(missingPluginCommands, tooling);
244
252
  mcpAuth = !detection.authenticated && (tooling.setup === "skills-mcp" || tooling.setup === "mixed") ? "oauth" : mcpAuth;
245
253
  } else {
246
254
  const inferred = inferInitAgentSetup({
@@ -251,53 +259,58 @@ const runInit = async (props) => {
251
259
  canAsk: detection.interactive || props.pickAgentSetup !== void 0,
252
260
  ...props.skill !== void 0 ? { skills: props.skill } : {}
253
261
  });
254
- if (inferred.kind === "skip") {
255
- tooling = { setup: "skip" };
256
- agentSetupChoice = "skip";
257
- } else if (inferred.kind === "auto") {
262
+ if (inferred.kind === "skip") tooling = { setup: "skip" };
263
+ else if (inferred.kind === "auto") {
258
264
  const autoAgents = named.length > 0 ? named : detection.detectedAgents;
259
- pluginScope = pluginScopeFor(autoAgents, "project");
260
- tooling = splitInitTooling(autoAgents, pluginScope, mcpConfigLocation);
265
+ pluginScope = pluginScopeFor(pluginReady(autoAgents), "project");
266
+ tooling = splitInitTooling(autoAgents, pluginScope, mcpConfigLocation, unavailablePluginAgents);
267
+ warnMissingPluginCommands(missingPluginCommands, tooling);
261
268
  if (tooling.setup === "skip" && yes && named.length === 0) {
262
269
  tooling = recommendedTooling([]);
263
270
  printInitProgress(NO_AGENTS_FALLBACK_STATUS);
264
271
  }
265
- agentSetupChoice = funnelAgentSetup(tooling);
266
272
  } else {
267
273
  const setup = inferred.kind === "ask" ? await (props.pickAgentSetup ?? pickAgentSetupInteractively)() : inferred.kind;
268
- agentSetupChoice = setup === "skills" ? "skills" : setup;
269
274
  if (setup !== "skip") {
270
275
  pluginScope = pluginScopeFor(targets, "project");
271
- const available = setup === "plugin" ? uniqueAgents([...pluginsInstallableAgents("project"), ...pluginsInstallableAgents("global")]) : availableForSetup(setup, pluginScope);
272
- const selected = await pickOrDetectAgents({
276
+ const namedFallback = named.length === 0;
277
+ const pickAgents = (setup, available) => pickOrDetectAgents({
273
278
  named,
274
279
  available,
275
280
  detected: detection.detectedAgents,
281
+ ...setup === "plugin" ? {
282
+ preselected: pluginReady(detection.detectedAgents),
283
+ missingCommands: missingPluginCommands
284
+ } : {},
276
285
  interactive: detection.interactive,
277
- ...props.pickAgents !== void 0 ? { pickAgents: props.pickAgents } : {}
286
+ setup,
287
+ pickAgents: props.pickAgents
278
288
  });
279
- const namedFallback = named.length === 0;
280
- if (setup === "plugin") {
281
- pluginScope = pluginScopeFor(selected, pluginScope);
282
- tooling = splitInitTooling(selected, pluginScope);
283
- if (tooling.setup !== "plugin" && tooling.setup !== "skip") tooling = splitInitTooling(selected.filter((id) => pluginsInstallableAgents(pluginScope).includes(id)), pluginScope);
284
- agentSetupChoice = funnelAgentSetup(tooling);
285
- } else if (setup === "skills") {
286
- tooling = skillsTooling(selected, namedFallback);
287
- agentSetupChoice = funnelAgentSetup(tooling);
289
+ if (setup === "plugin" || setup === "skills") {
290
+ const selected = await pickAgents(setup, setup === "plugin" ? uniqueAgents([...pluginsInstallableAgents("project"), ...pluginsInstallableAgents("global")]) : [...skillsInstallableAgents()]);
291
+ if (selected === void 0) tooling = { setup: "skip" };
292
+ else if (setup === "plugin") {
293
+ pluginScope = pluginScopeFor(selected, pluginScope);
294
+ tooling = splitInitTooling(selected.filter((id) => pluginsInstallableAgents(pluginScope).includes(id)), pluginScope);
295
+ } else tooling = skillsTooling(selected, namedFallback);
288
296
  } else {
289
- if (selectedSkills === void 0 && inferred.kind === "ask") selectedSkills = props.pickSkills !== void 0 ? await props.pickSkills() : detection.interactive ? await pickInitSkillsInteractively() : void 0;
290
- mcpConfigLocation = props.mcpConfigLocation ?? (yes ? "global" : props.pickMcpConfigLocation !== void 0 ? await props.pickMcpConfigLocation() : detection.interactive ? await pickInitMcpConfigLocationInteractively() : "global");
291
- mcpAuth = props.mcpAuth ?? (yes ? detection.authenticated && props.claimable !== true ? "api-key" : "oauth" : props.pickMcpAuth !== void 0 ? await props.pickMcpAuth({ authenticated: detection.authenticated }) : detection.interactive ? await pickInitMcpAuthInteractively({ authenticated: detection.authenticated }) : detection.authenticated && props.claimable !== true ? "api-key" : "oauth");
292
- validateMcpConfigLocationSupport(selected, mcpConfigLocation);
293
- tooling = skillsMcpTooling(selected, mcpConfigLocation, namedFallback);
294
- agentSetupChoice = "skills-mcp";
297
+ const skillsAvailable = skillsInstallableAgents();
298
+ const skillsSelection = named.length > 0 ? named.filter((id) => skillsAvailable.includes(id)) : await pickAgents("skills", skillsAvailable);
299
+ if (skillsSelection !== void 0 && selectedSkills === void 0 && inferred.kind === "ask") selectedSkills = props.pickSkills !== void 0 ? await props.pickSkills() : detection.interactive ? await pickInitSkillsInteractively() : void 0;
300
+ const mcpSelection = named.length > 0 ? named : await pickAgents("mcp", mcpInstallableAgents("global"));
301
+ if (mcpSelection !== void 0) {
302
+ mcpConfigLocation = props.mcpConfigLocation ?? (yes ? "global" : props.pickMcpConfigLocation !== void 0 ? await props.pickMcpConfigLocation() : detection.interactive ? await pickInitMcpConfigLocationInteractively() : "global");
303
+ if (mcpSelection.length > 0) validateMcpConfigLocationSupport(mcpSelection, mcpConfigLocation);
304
+ const pickAuth = props.pickMcpAuth ?? (detection.interactive ? pickInitMcpAuthInteractively : void 0);
305
+ mcpAuth = props.mcpAuth ?? (yes ? void 0 : await pickAuth?.({ authenticated: detection.authenticated })) ?? (detection.authenticated && props.claimable !== true ? "api-key" : "oauth");
306
+ }
307
+ tooling = skillsMcpTooling(skillsSelection, mcpSelection, mcpConfigLocation, namedFallback);
295
308
  }
296
309
  }
297
310
  }
298
311
  }
299
- funnel.agentSetup = agentSetupChoice === "plugin" || agentSetupChoice === "skills-mcp" || agentSetupChoice === "skills" || agentSetupChoice === "mixed" || agentSetupChoice === "skip" ? agentSetupChoice : funnelAgentSetup(tooling);
300
- const usesMcp = tooling.setup === "skills-mcp" || tooling.setup === "mixed";
312
+ funnel.agentSetup = funnelAgentSetup(tooling);
313
+ const usesMcp = (tooling.setup === "skills-mcp" || tooling.setup === "mixed") && tooling.mcpAgents.length > 0;
301
314
  if (mcpAuth === void 0 && usesMcp && (!detection.authenticated || props.claimable === true)) mcpAuth = "oauth";
302
315
  delayMcp = usesMcp && (mcpAuth === "api-key" || props.mcpProjectScoped === true);
303
316
  const mcpOauth = mcpAuth === "oauth";
@@ -355,7 +368,7 @@ const runInit = async (props) => {
355
368
  funnel.link = "skipped";
356
369
  } else if (props.pickProjectSetup !== void 0 || detection.interactive) projectSetup = await (props.pickProjectSetup ?? pickInitProjectSetupInteractively)();
357
370
  else throw new Error(NON_TTY_LINK_NEEDS_AUTH);
358
- if (mcpAuth === "api-key" && (projectSetup === "claimable" || props.claimable === true)) throw new Error(CLAIMABLE_MCP_API_KEY);
371
+ if (usesMcp && mcpAuth === "api-key" && (projectSetup === "claimable" || props.claimable === true)) throw new Error(CLAIMABLE_MCP_API_KEY);
359
372
  if (projectSetup === "claimable") {
360
373
  printInitProgress(PROGRESS.claim);
361
374
  await (props.createClaimable ?? defaultCreateClaimable)({
@@ -529,8 +542,8 @@ const runInit = async (props) => {
529
542
  {
530
543
  label: "Agents",
531
544
  value: agentsRowValue({
532
- setup: funnel.agentSetup,
533
- agents: funnel.agentsInstalled
545
+ tooling,
546
+ installed: funnel.agentsInstalled
534
547
  })
535
548
  },
536
549
  {
@@ -578,8 +591,8 @@ const runInit = async (props) => {
578
591
  {
579
592
  label: "Agents",
580
593
  value: agentsRowValue({
581
- setup: funnel.agentSetup,
582
- agents: funnel.agentsInstalled
594
+ tooling,
595
+ installed: funnel.agentsInstalled
583
596
  })
584
597
  },
585
598
  {
@@ -3,7 +3,7 @@ import { getCliName } from "../utils/cli_name.js";
3
3
  import { PROGRESS } from "./copy.js";
4
4
  import { assertNamedAgentTooling, chooseYesAgentTooling, collectYesAgents, initYesSupportedAgents, noDetectedAgentsMessage, planAgentSteps, planToolingSteps, planYesAgentSteps, resolveInitAgentSetup } from "./plan.js";
5
5
  import { detectAgent } from "./detect_host.js";
6
- import { canPickAgentsInteractively } from "../utils/agent_picker.js";
6
+ import { AgentSelectionSkipped, canPickAgentsInteractively } from "../utils/agent_picker.js";
7
7
  import { mcpInstallError, reportMcpInstall } from "../commands/mcp.js";
8
8
  import { installPlugins, pluginsInstallError, reportPluginsInstall } from "../commands/plugins.js";
9
9
  import { installSkills, reportSkillsInstall, skillsInstallError } from "../commands/skills.js";
@@ -66,45 +66,61 @@ const runToolingSteps = async (steps, options) => {
66
66
  ...options.operations
67
67
  };
68
68
  const reportProps = { output: options.output };
69
+ const sharedOptions = {
70
+ cwd: options.cwd,
71
+ allowAgentSkip: options.allowAgentSkip
72
+ };
73
+ const completed = [];
69
74
  for (const step of steps) {
70
75
  const label = options.narrate === "human" ? TOOLING_STEP_LABELS[step.kind] : void 0;
71
76
  if (label !== void 0) process.stdout.write(`${label}\n`);
72
77
  else log.info("Running `%s %s`", getCliName(), toolingStepCommandText(step));
73
78
  log.debug("Running `%s %s`", getCliName(), toolingStepCommandText(step));
74
- switch (step.kind) {
75
- case "plugins": {
76
- const outcome = await raceSigint(ops.installPlugins({
77
- ...step.options,
78
- cwd: options.cwd
79
- }));
80
- reportPluginsInstall(reportProps, outcome);
81
- const error = pluginsInstallError(outcome);
82
- if (error) throw error;
83
- break;
84
- }
85
- case "skills": {
86
- const outcome = await raceSigint(ops.installSkills({
87
- ...step.options,
88
- cwd: options.cwd
89
- }));
90
- reportSkillsInstall(reportProps, outcome);
91
- const error = skillsInstallError(outcome);
92
- if (error) throw error;
93
- break;
79
+ try {
80
+ switch (step.kind) {
81
+ case "plugins": {
82
+ const outcome = await raceSigint(ops.installPlugins({
83
+ ...step.options,
84
+ ...sharedOptions
85
+ }));
86
+ reportPluginsInstall(reportProps, outcome);
87
+ const error = pluginsInstallError(outcome);
88
+ if (error) throw error;
89
+ break;
90
+ }
91
+ case "skills": {
92
+ const outcome = await raceSigint(ops.installSkills({
93
+ ...step.options,
94
+ ...sharedOptions
95
+ }));
96
+ reportSkillsInstall(reportProps, outcome);
97
+ const error = skillsInstallError(outcome);
98
+ if (error) throw error;
99
+ break;
100
+ }
101
+ case "mcp": {
102
+ const outcome = await raceSigint(ops.installMcp({
103
+ ...step.options,
104
+ ...sharedOptions
105
+ }));
106
+ reportMcpInstall(reportProps, outcome);
107
+ const error = mcpInstallError(outcome);
108
+ if (error) throw error;
109
+ break;
110
+ }
111
+ default: return step;
94
112
  }
95
- case "mcp": {
96
- const outcome = await raceSigint(ops.installMcp({
97
- ...step.options,
98
- cwd: options.cwd
99
- }));
100
- reportMcpInstall(reportProps, outcome);
101
- const error = mcpInstallError(outcome);
102
- if (error) throw error;
103
- break;
104
- }
105
- default: return step;
113
+ completed.push(step.kind);
114
+ } catch (error) {
115
+ if (!(error instanceof AgentSelectionSkipped)) throw error;
106
116
  }
107
117
  }
118
+ return completed;
119
+ };
120
+ const agentSetupResult = (setup, completed) => {
121
+ if (completed.length === 0) return "skip";
122
+ if (setup === "skills-mcp" && completed.length === 1) return completed[0] === "skills" ? "skills" : "mcp";
123
+ return setup;
108
124
  };
109
125
  const yesAgentsFromOptions = async (options) => collectYesAgents({
110
126
  detected: () => (options.detectProjectAgents ?? defaultProjectAgents)(options.cwd),
@@ -119,47 +135,44 @@ const yesMiss = () => new Error(noDetectedAgentsMessage({
119
135
  const runAgentTooling = async (options) => {
120
136
  const yes = options.yes;
121
137
  const named = options.agents ?? [];
138
+ const runSetup = async (setup, steps) => agentSetupResult(setup, await runToolingSteps(steps, {
139
+ ...options,
140
+ allowAgentSkip: !yes
141
+ }));
122
142
  if (named.length > 0) {
123
143
  assertNamedAgentTooling(named, options.command ?? "init");
124
144
  const tooling = chooseYesAgentTooling(named);
125
- await runToolingSteps(planToolingSteps(tooling, {
145
+ return runSetup(tooling.setup, planToolingSteps(tooling, {
126
146
  yes,
127
147
  named: true
128
- }), options);
129
- return tooling.setup;
130
- }
131
- if (options.agentSetup !== void 0) {
132
- await runToolingSteps(planAgentSteps({
133
- yes,
134
- agentSetup: options.agentSetup
135
- }), options);
136
- return options.agentSetup;
148
+ }));
137
149
  }
150
+ if (options.agentSetup !== void 0) return runSetup(options.agentSetup, planAgentSteps({
151
+ yes,
152
+ agentSetup: options.agentSetup
153
+ }));
138
154
  if (yes) {
139
155
  if (options.hasProjectPlugins !== void 0) {
140
156
  const agentSetup = await options.hasProjectPlugins(options.cwd) ? "plugin" : "skills-mcp";
141
- await runToolingSteps(planAgentSteps({
157
+ return runSetup(agentSetup, planAgentSteps({
142
158
  yes: true,
143
159
  agentSetup
144
- }), options);
145
- return agentSetup;
160
+ }));
146
161
  }
147
162
  const agents = await yesAgentsFromOptions(options);
148
163
  const tooling = chooseYesAgentTooling(agents);
149
164
  if (tooling.setup === "skip") throw yesMiss();
150
- await runToolingSteps(planYesAgentSteps(tooling), options);
151
- return tooling.setup;
165
+ return runSetup(tooling.setup, planYesAgentSteps(tooling));
152
166
  }
153
167
  const interactive = options.pickAgentSetup !== void 0 || canPickAgentsInteractively();
154
168
  const agentSetup = await resolveInitAgentSetup({
155
169
  interactive,
156
170
  pick: options.pickAgentSetup ?? pickAgentSetupInteractively
157
171
  });
158
- await runToolingSteps(planAgentSteps({
172
+ return runSetup(agentSetup, planAgentSteps({
159
173
  yes,
160
174
  agentSetup
161
- }), options);
162
- return agentSetup;
175
+ }));
163
176
  };
164
177
  //#endregion
165
178
  export { runAgentTooling, runToolingSteps };