neon 6.2.4 → 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.
- package/README.md +1 -1
- package/dist/commands/mcp.js +4 -2
- package/dist/commands/plugins.js +7 -3
- package/dist/commands/skills.js +4 -2
- package/dist/index.js +6 -1
- package/dist/init/chrome.js +26 -6
- package/dist/init/copy.js +7 -1
- package/dist/init/mode.js +1 -3
- package/dist/init/plan.js +11 -5
- package/dist/init/run.js +63 -50
- package/dist/init/tooling.js +63 -50
- package/dist/init/wizard.js +17 -18
- package/dist/plugins/plan.js +32 -4
- package/dist/plugins/targets.js +49 -9
- package/dist/sso_recovery.js +92 -0
- package/dist/utils/agent_picker.js +44 -18
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -792,7 +792,7 @@ Recommended installs the Neon plugin for every detected agent (global config, pr
|
|
|
792
792
|
|
|
793
793
|
Unauthenticated `-y` skips linking and prints the next step: sign up at https://neon.com/signup, then `neon auth`, `neon link`, or `neon claim create` (no account, expires in 72 hours unless claimed). `-y` never opens a browser. `-y` with `--project-id` or other account flags links when the CLI is already signed in; otherwise it errors and names `neon auth` or `--claimable`.
|
|
794
794
|
|
|
795
|
-
Custom asks how to add Neon to coding agents (plugin, skills and MCP separately, or skip), then how to get a project (sign in and link, or a claimable project when you are not signed in), then which services `neon.ts` should declare. Postgres is listed first as always included. The package manager is inferred from the directory; Custom asks only when none is detected. `--skill` selects skills (not the plugin) and skips that picker. MCP flags (`--mcp-config-location`, `--mcp-auth`, `--mcp-project-scoped`) select skills and MCP. `--mcp-config-location` is where the config is written: `global` maps to `neon mcp`, and `project` maps to `neon mcp --project`. With `-y`, the location defaults to `global`; interactive Custom asks. If some selected agents cannot write that location, init warns and skips MCP for those agents while continuing with the supported agents. It fails when none of the selected agents support the location. `--mcp-project-scoped` limits MCP tools to the linked Neon project by passing its ID to `neon mcp --project-id`. Omitted is false, including in Custom; there is no prompt. `--no-agent-setup` skips agent setup.
|
|
795
|
+
Custom asks how to add Neon to coding agents (plugin, skills and MCP separately, or skip), then how to get a project (sign in and link, or a claimable project when you are not signed in), then which services `neon.ts` should declare. Skills and MCP use separate agent pickers, and either can be skipped. Postgres is listed first as always included. The package manager is inferred from the directory; Custom asks only when none is detected. `--skill` selects skills (not the plugin) and skips that picker. MCP flags (`--mcp-config-location`, `--mcp-auth`, `--mcp-project-scoped`) select skills and MCP. `--mcp-config-location` is where the config is written: `global` maps to `neon mcp`, and `project` maps to `neon mcp --project`. With `-y`, the location defaults to `global`; interactive Custom asks. If some selected agents cannot write that location, init warns and skips MCP for those agents while continuing with the supported agents. It fails when none of the selected agents support the location. `--mcp-project-scoped` limits MCP tools to the linked Neon project by passing its ID to `neon mcp --project-id`. Omitted is false, including in Custom; there is no prompt. `--no-agent-setup` skips agent setup.
|
|
796
796
|
|
|
797
797
|
Empty directories are set up in place. `neon init` does not scaffold starter files.
|
|
798
798
|
|
package/dist/commands/mcp.js
CHANGED
|
@@ -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,
|
package/dist/commands/plugins.js
CHANGED
|
@@ -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.
|
|
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({
|
package/dist/commands/skills.js
CHANGED
|
@@ -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);
|
package/dist/init/chrome.js
CHANGED
|
@@ -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
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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) =>
|
|
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
|
|
60
|
-
if (setup
|
|
61
|
-
|
|
62
|
-
if (
|
|
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 = (
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
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:
|
|
85
|
-
mcpAgents:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
272
|
-
const
|
|
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
|
-
|
|
286
|
+
setup,
|
|
287
|
+
pickAgents: props.pickAgents
|
|
278
288
|
});
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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 =
|
|
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
|
-
|
|
533
|
-
|
|
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
|
-
|
|
582
|
-
|
|
594
|
+
tooling,
|
|
595
|
+
installed: funnel.agentsInstalled
|
|
583
596
|
})
|
|
584
597
|
},
|
|
585
598
|
{
|
package/dist/init/tooling.js
CHANGED
|
@@ -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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
145
|
+
return runSetup(tooling.setup, planToolingSteps(tooling, {
|
|
126
146
|
yes,
|
|
127
147
|
named: true
|
|
128
|
-
})
|
|
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
|
-
|
|
157
|
+
return runSetup(agentSetup, planAgentSteps({
|
|
142
158
|
yes: true,
|
|
143
159
|
agentSetup
|
|
144
|
-
})
|
|
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
|
-
|
|
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
|
-
|
|
172
|
+
return runSetup(agentSetup, planAgentSteps({
|
|
159
173
|
yes,
|
|
160
174
|
agentSetup
|
|
161
|
-
})
|
|
162
|
-
return agentSetup;
|
|
175
|
+
}));
|
|
163
176
|
};
|
|
164
177
|
//#endregion
|
|
165
178
|
export { runAgentTooling, runToolingSteps };
|
package/dist/init/wizard.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { CONFIG_INIT_SERVICES } from "../config_template.js";
|
|
2
|
-
import { 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, CONFIG_CONFIRM_HINT, CONFIG_CONFIRM_MESSAGE, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, 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, PACKAGE_MANAGER_MESSAGE, 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 } from "./copy.js";
|
|
2
|
+
import { 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, CONFIG_CONFIRM_HINT, CONFIG_CONFIRM_MESSAGE, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, 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, PACKAGE_MANAGER_MESSAGE, 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, agentMissingCommandDescription } from "./copy.js";
|
|
3
3
|
import { installedPackageManagers } from "../utils/package_manager.js";
|
|
4
4
|
import { POSTGRES_SERVICE_CHOICE, keepPostgresSelected } from "../utils/service_picker.js";
|
|
5
5
|
import { getAgentDisplayName, listMcpAgentIds } from "../mcp/agents.js";
|
|
6
6
|
import { INIT_NEEDS_YES_OR_TERMINAL } from "./plan.js";
|
|
7
|
-
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
7
|
+
import { canPickAgentsInteractively, pickAgentsInteractively } from "../utils/agent_picker.js";
|
|
8
8
|
import { NEON_SKILL_CATALOG, listSkillIds } from "../skills/catalog.js";
|
|
9
9
|
import { InitCancelled, restoreCursorVisibilityOnAbort } from "./cancelled.js";
|
|
10
10
|
import chalk from "chalk";
|
|
@@ -71,24 +71,23 @@ const pickAgentSetupInteractively = async () => {
|
|
|
71
71
|
};
|
|
72
72
|
const pickInitAgentsInteractively = async (input) => {
|
|
73
73
|
requireInteractive();
|
|
74
|
-
if (input.available.length === 0) throw new Error("No coding agents are available to pick.");
|
|
75
74
|
const detected = new Set(input.detected);
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
75
|
+
const missing = new Map((input.missingCommands ?? []).map((row) => [row.agent, row.command]));
|
|
76
|
+
const selected = await pickAgentsInteractively({
|
|
77
|
+
onCancel: aborted,
|
|
78
|
+
message: input.setup === "skills" ? AGENT_SKILLS_PICKER_MESSAGE : input.setup === "mcp" ? AGENT_MCP_PICKER_MESSAGE : AGENT_PICKER_MESSAGE,
|
|
79
|
+
skipMessage: input.setup === "plugin" ? AGENT_PLUGIN_SKIP_MESSAGE : input.setup === "skills" ? AGENT_SKILLS_SKIP_MESSAGE : AGENT_MCP_SKIP_MESSAGE,
|
|
80
|
+
selected: input.preselected ?? input.detected,
|
|
81
|
+
choices: input.available.map((id) => {
|
|
82
|
+
const command = missing.get(id);
|
|
83
|
+
return {
|
|
84
|
+
id,
|
|
85
|
+
title: `${getAgentDisplayName(id)} (${id})`,
|
|
86
|
+
description: command !== void 0 ? agentMissingCommandDescription(command) : detected.has(id) ? AGENT_DETECTED_DESCRIPTION : AGENT_OTHER_DESCRIPTION
|
|
87
|
+
};
|
|
88
|
+
})
|
|
89
89
|
});
|
|
90
|
-
|
|
91
|
-
return agents.filter((id) => typeof id === "string");
|
|
90
|
+
return selected.length > 0 ? selected : void 0;
|
|
92
91
|
};
|
|
93
92
|
const pickInitSkillsInteractively = async () => {
|
|
94
93
|
requireInteractive();
|
package/dist/plugins/plan.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { getAgentDisplayName, tryResolveAddMcpAgentId } from "../mcp/agents.js";
|
|
2
2
|
import "../init/agents.js";
|
|
3
|
-
import { detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, pluginsInstallableAgents } from "./targets.js";
|
|
3
|
+
import { detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, missingPluginsCommands, pluginsInstallableAgents } from "./targets.js";
|
|
4
4
|
import { collectYesAgents, noDetectedAgentsMessage } from "../init/plan.js";
|
|
5
5
|
import { detectAgent } from "../init/detect_host.js";
|
|
6
6
|
import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
|
|
7
7
|
//#region src/plugins/plan.ts
|
|
8
|
+
const missingPluginsCommandsError = (missing) => new Error(missing.map(({ agent, command }) => `Cannot install the Neon plugin for ${getAgentDisplayName(agent)}: "${command}" was not found on PATH. Install it or add it to PATH, then retry.`).join("\n"));
|
|
8
9
|
const assertPluginsCanRun = (options) => {
|
|
9
10
|
if (options.yes || options.interactive || options.hasAgents) return;
|
|
10
11
|
throw new Error("No interactive terminal. Pass -y to install into detected agents, or --agent <name> to name them.");
|
|
11
12
|
};
|
|
13
|
+
const commandLookup = (options) => ({
|
|
14
|
+
cwd: options.cwd,
|
|
15
|
+
...options.commandPath !== void 0 ? { path: options.commandPath } : {}
|
|
16
|
+
});
|
|
12
17
|
async function resolvePluginsPlan(options) {
|
|
13
18
|
assertPluginsCanRun({
|
|
14
19
|
yes: options.yes,
|
|
@@ -29,10 +34,18 @@ async function resolvePluginsPlan(options) {
|
|
|
29
34
|
detectAgent: options.detectAgent ?? detectAgent,
|
|
30
35
|
acceptHost: (id) => availableSet.has(id) || getPluginsTargetName(id) !== void 0
|
|
31
36
|
}) : scoped;
|
|
37
|
+
const missingDetected = new Map((prompt ? missingPluginsCommands(detected, commandLookup(options)) : []).map((row) => [row.agent, row.command]));
|
|
38
|
+
const choices = agentChoicesFrom(available, detected).map((choice) => {
|
|
39
|
+
const command = missingDetected.get(choice.id);
|
|
40
|
+
return command === void 0 ? choice : {
|
|
41
|
+
...choice,
|
|
42
|
+
description: `detected, but "${command}" is not on PATH`
|
|
43
|
+
};
|
|
44
|
+
});
|
|
32
45
|
const selected = await resolveAgentSelection({
|
|
33
46
|
specified: options.agents,
|
|
34
|
-
choices
|
|
35
|
-
detected,
|
|
47
|
+
choices,
|
|
48
|
+
detected: detected.filter((id) => !missingDetected.has(id)),
|
|
36
49
|
message: "Which coding agents should get the Neon plugin? (space to toggle, enter to confirm)",
|
|
37
50
|
nonInteractiveMessage: noDetectedAgentsMessage({
|
|
38
51
|
scope,
|
|
@@ -64,6 +77,20 @@ async function resolvePluginsPlan(options) {
|
|
|
64
77
|
}
|
|
65
78
|
agents.push(id);
|
|
66
79
|
}
|
|
80
|
+
const missingCommands = missingPluginsCommands(agents, commandLookup(options));
|
|
81
|
+
if (missingCommands.length > 0) {
|
|
82
|
+
if (!(options.agents.length === 0 && !prompt)) throw missingPluginsCommandsError(missingCommands);
|
|
83
|
+
const missingAgents = new Set(missingCommands.map((row) => row.agent));
|
|
84
|
+
const installable = agents.filter((id) => !missingAgents.has(id));
|
|
85
|
+
return {
|
|
86
|
+
scope,
|
|
87
|
+
agents: installable,
|
|
88
|
+
skipped,
|
|
89
|
+
userScopeSkipped,
|
|
90
|
+
missingCommands,
|
|
91
|
+
targets: installable.length === 0 ? [] : mappedPluginsTargets(installable, scope)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
67
94
|
if (agents.length === 0) {
|
|
68
95
|
if (userScopeSkipped.length > 0 && skipped.length === 0) {
|
|
69
96
|
const names = userScopeSkipped.map((id) => getAgentDisplayName(id)).join(", ");
|
|
@@ -76,8 +103,9 @@ async function resolvePluginsPlan(options) {
|
|
|
76
103
|
agents,
|
|
77
104
|
skipped,
|
|
78
105
|
userScopeSkipped,
|
|
106
|
+
missingCommands,
|
|
79
107
|
targets: mappedPluginsTargets(agents, scope)
|
|
80
108
|
};
|
|
81
109
|
}
|
|
82
110
|
//#endregion
|
|
83
|
-
export { assertPluginsCanRun, resolvePluginsPlan };
|
|
111
|
+
export { assertPluginsCanRun, missingPluginsCommandsError, resolvePluginsPlan };
|
package/dist/plugins/targets.js
CHANGED
|
@@ -1,27 +1,67 @@
|
|
|
1
1
|
import { detectInstalledAgents, listMcpAgentIds, tryResolveAddMcpAgentId, uniqueAgentIds } from "../mcp/agents.js";
|
|
2
2
|
import "../init/agents.js";
|
|
3
|
+
import { delimiter, dirname, join, resolve } from "node:path";
|
|
4
|
+
import which from "which";
|
|
3
5
|
import { detectProjectAgents } from "add-mcp";
|
|
4
6
|
//#region src/plugins/targets.ts
|
|
7
|
+
const CLAUDE_CODE = { target: "claude-code" };
|
|
5
8
|
const PLUGINS_TARGET_BY_TYPE = {
|
|
6
|
-
cursor: "cursor",
|
|
7
|
-
vscode: "vscode",
|
|
8
|
-
"claude-code":
|
|
9
|
-
"claude-desktop":
|
|
10
|
-
codex: "codex",
|
|
11
|
-
"github-copilot-cli":
|
|
12
|
-
|
|
9
|
+
cursor: { target: "cursor" },
|
|
10
|
+
vscode: { target: "vscode" },
|
|
11
|
+
"claude-code": CLAUDE_CODE,
|
|
12
|
+
"claude-desktop": CLAUDE_CODE,
|
|
13
|
+
codex: { target: "codex" },
|
|
14
|
+
"github-copilot-cli": {
|
|
15
|
+
target: "github-copilot",
|
|
16
|
+
command: "copilot"
|
|
17
|
+
},
|
|
18
|
+
"grok-build": {
|
|
19
|
+
target: "grok",
|
|
20
|
+
command: "grok"
|
|
21
|
+
}
|
|
13
22
|
};
|
|
23
|
+
const pluginsTargets = () => [...new Set(Object.values(PLUGINS_TARGET_BY_TYPE))];
|
|
14
24
|
const USER_SCOPE_ONLY_TARGETS = /* @__PURE__ */ new Set([
|
|
15
25
|
"vscode",
|
|
16
26
|
"github-copilot",
|
|
17
27
|
"grok"
|
|
18
28
|
]);
|
|
19
|
-
|
|
29
|
+
/** npx runs the plugins CLI with every `node_modules/.bin` from `cwd` up to the root ahead of PATH. */
|
|
30
|
+
const pluginsCommandSearchPath = (cwd, path) => {
|
|
31
|
+
const bins = [];
|
|
32
|
+
let dir = resolve(cwd);
|
|
33
|
+
for (;;) {
|
|
34
|
+
bins.push(join(dir, "node_modules", ".bin"));
|
|
35
|
+
const parent = dirname(dir);
|
|
36
|
+
if (parent === dir) break;
|
|
37
|
+
dir = parent;
|
|
38
|
+
}
|
|
39
|
+
return [...bins, path].join(delimiter);
|
|
40
|
+
};
|
|
41
|
+
const missingPluginsCommands = (agents, options) => {
|
|
42
|
+
const searchPath = pluginsCommandSearchPath(options.cwd, options.path ?? process.env.PATH ?? "");
|
|
43
|
+
const missing = [];
|
|
44
|
+
for (const agent of uniqueAgentIds(agents)) {
|
|
45
|
+
const command = getPluginsTarget(agent)?.command;
|
|
46
|
+
if (command !== void 0 && which.sync(command, {
|
|
47
|
+
nothrow: true,
|
|
48
|
+
path: searchPath
|
|
49
|
+
}) === null) missing.push({
|
|
50
|
+
agent,
|
|
51
|
+
command
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return missing;
|
|
55
|
+
};
|
|
56
|
+
function getPluginsTarget(agent) {
|
|
20
57
|
if (Object.prototype.hasOwnProperty.call(PLUGINS_TARGET_BY_TYPE, agent)) return PLUGINS_TARGET_BY_TYPE[agent];
|
|
21
58
|
const id = tryResolveAddMcpAgentId(agent);
|
|
22
59
|
if (!id) return void 0;
|
|
23
60
|
return PLUGINS_TARGET_BY_TYPE[id];
|
|
24
61
|
}
|
|
62
|
+
function getPluginsTargetName(agent) {
|
|
63
|
+
return getPluginsTarget(agent)?.target;
|
|
64
|
+
}
|
|
25
65
|
function supportsPlugins(agent) {
|
|
26
66
|
return getPluginsTargetName(agent) !== void 0;
|
|
27
67
|
}
|
|
@@ -65,4 +105,4 @@ const mappedPluginsTargets = (agents, scope) => {
|
|
|
65
105
|
return mapped;
|
|
66
106
|
};
|
|
67
107
|
//#endregion
|
|
68
|
-
export { detectInstallablePluginsAgents, detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, pluginsInstallableAgents, pluginsMappedAgents, supportsPlugins };
|
|
108
|
+
export { detectInstallablePluginsAgents, detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, missingPluginsCommands, pluginsInstallableAgents, pluginsMappedAgents, pluginsTargets, supportsPlugins };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { isCi } from "./env.js";
|
|
2
|
+
import { log } from "./log.js";
|
|
3
|
+
import { codeFromBody } from "./api.js";
|
|
4
|
+
import open from "open";
|
|
5
|
+
import prompts from "prompts";
|
|
6
|
+
//#region src/sso_recovery.ts
|
|
7
|
+
/**
|
|
8
|
+
* Extract the SSO step-up URL from the error message.
|
|
9
|
+
*
|
|
10
|
+
* The Neon public API carries the recovery URL in the human-readable `message`
|
|
11
|
+
* ("...Open <url> in your browser to authorize, then retry."), not a structured field — so this
|
|
12
|
+
* works against the API's standard error shape without a bespoke contract.
|
|
13
|
+
*/
|
|
14
|
+
function extractStepUpUrl(message) {
|
|
15
|
+
if (typeof message !== "string") return;
|
|
16
|
+
const match = message.match(/(https?:\/\/[^\s)]+)/);
|
|
17
|
+
return match ? match[1] : void 0;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Whether the invocation opted out of prompts via --yes/-y.
|
|
21
|
+
*
|
|
22
|
+
* The skip-prompts flag is per-command in this CLI (link, projects update, init, …), not a global
|
|
23
|
+
* option, so — exactly like the --help/-h check in the error handler — detect it from argv rather than
|
|
24
|
+
* a parsed global. A run that documented itself as non-interactive (`neon link -y`) must take the
|
|
25
|
+
* unattended path, never open a browser or block on a confirm.
|
|
26
|
+
*/
|
|
27
|
+
function promptsSkipped() {
|
|
28
|
+
return process.argv.includes("--yes") || process.argv.includes("-y");
|
|
29
|
+
}
|
|
30
|
+
/** Whether this process can drive an interactive open-browser-then-confirm-and-retry flow. */
|
|
31
|
+
function canInteract() {
|
|
32
|
+
return !isCi() && !promptsSkipped() && Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Recover from an SSO authorization error.
|
|
36
|
+
*
|
|
37
|
+
* Detection is by the machine `code` in the response body, NOT the HTTP status: Neon masks org
|
|
38
|
+
* access-denials as 404 (existence-hiding), so the error arrives as 403 or 404 with the code in the
|
|
39
|
+
* body.
|
|
40
|
+
* - SSO_AUTHORIZATION_REQUIRED: the user must complete a one-time SSO authorization in a browser.
|
|
41
|
+
* On an interactive terminal we open the step-up URL and wait for the user to confirm they've
|
|
42
|
+
* finished before signalling a retry. Unattended (CI, no TTY, or an explicit --yes/-y that opted
|
|
43
|
+
* out of prompts) we print the URL and fail, since no browser round-trip is possible.
|
|
44
|
+
* - SSO_ORG_CREDS_ONLY: terminal — no browser step resolves it; the user must use an
|
|
45
|
+
* organization-scoped API key. Return false (no retry).
|
|
46
|
+
*
|
|
47
|
+
* @param error the API error
|
|
48
|
+
* @param canRetry whether the caller is willing to retry (false once we've already retried)
|
|
49
|
+
* @returns true if the caller should retry the command, false otherwise
|
|
50
|
+
*/
|
|
51
|
+
const recoverFromSSO = async (error, canRetry) => {
|
|
52
|
+
const code = codeFromBody(error.data);
|
|
53
|
+
if (code === "SSO_ORG_CREDS_ONLY") {
|
|
54
|
+
log.error(error.message);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (code !== "SSO_AUTHORIZATION_REQUIRED") return false;
|
|
58
|
+
if (!canRetry) {
|
|
59
|
+
log.error(error.message);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const stepUpUrl = extractStepUpUrl(error.message);
|
|
63
|
+
if (!stepUpUrl) {
|
|
64
|
+
log.error(error.message);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (!canInteract()) {
|
|
68
|
+
log.error("This organization requires a one-time SSO authorization, which needs an interactive browser session. Open this URL in a browser to authorize, then re-run the command:\n" + stepUpUrl);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
log.info(`This organization requires SSO authorization. Opening your browser to authorize; if it doesn't open, visit this URL manually:\n${stepUpUrl}`);
|
|
72
|
+
try {
|
|
73
|
+
(await open(stepUpUrl)).once("error", (err) => {
|
|
74
|
+
log.warning(`Could not launch a browser automatically: ${err.message}`);
|
|
75
|
+
});
|
|
76
|
+
} catch {
|
|
77
|
+
log.warning("Could not launch a browser automatically; open the URL above manually.");
|
|
78
|
+
}
|
|
79
|
+
const { confirmed } = await prompts({
|
|
80
|
+
type: "confirm",
|
|
81
|
+
name: "confirmed",
|
|
82
|
+
message: "Press Enter once you've completed authorization in the browser to retry (or 'n' to cancel)",
|
|
83
|
+
initial: true
|
|
84
|
+
});
|
|
85
|
+
if (confirmed !== true) {
|
|
86
|
+
log.error(`SSO authorization was not completed. Re-run the command after authorizing at:\n${stepUpUrl}`);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
};
|
|
91
|
+
//#endregion
|
|
92
|
+
export { recoverFromSSO };
|
|
@@ -2,40 +2,66 @@ import { isCi } from "../env.js";
|
|
|
2
2
|
import { getAgentDisplayName, resolveAddMcpAgentId, uniqueAgentIds } from "../mcp/agents.js";
|
|
3
3
|
import prompts from "prompts";
|
|
4
4
|
//#region src/utils/agent_picker.ts
|
|
5
|
+
var AgentSelectionSkipped = class extends Error {};
|
|
5
6
|
const canPickAgentsInteractively = () => !isCi() && Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY);
|
|
7
|
+
const restoreCursorOnAbort = (exit) => (state) => {
|
|
8
|
+
if (state.aborted) {
|
|
9
|
+
process.stdout.write("\x1B[?25h");
|
|
10
|
+
process.stdout.write("\n");
|
|
11
|
+
if (exit) process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
6
14
|
const pickAgentsInteractively = async (options) => {
|
|
7
15
|
if (!canPickAgentsInteractively()) throw new Error("No interactive terminal. Pass -y, or run this command in a terminal to pick agents.");
|
|
8
16
|
if (options.choices.length === 0) throw new Error("No coding agents are available to pick.");
|
|
17
|
+
const cancel = options.onCancel ?? (() => {
|
|
18
|
+
throw new Error("Aborted: no agents selected.");
|
|
19
|
+
});
|
|
20
|
+
const onState = restoreCursorOnAbort(options.onCancel === void 0);
|
|
9
21
|
const selected = new Set(options.selected ?? []);
|
|
10
|
-
const
|
|
11
|
-
onState
|
|
12
|
-
if (state.aborted) {
|
|
13
|
-
process.stdout.write("\x1B[?25h");
|
|
14
|
-
process.stdout.write("\n");
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
},
|
|
22
|
+
const question = {
|
|
23
|
+
onState,
|
|
18
24
|
type: "multiselect",
|
|
19
25
|
name: "agents",
|
|
20
26
|
message: options.message,
|
|
21
27
|
instructions: false,
|
|
22
|
-
min: 1,
|
|
28
|
+
min: options.skipMessage ? 0 : 1,
|
|
23
29
|
choices: options.choices.map((choice) => ({
|
|
24
30
|
value: choice.id,
|
|
25
31
|
title: choice.title,
|
|
26
32
|
description: choice.description,
|
|
27
33
|
selected: selected.has(choice.id)
|
|
28
34
|
}))
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
};
|
|
36
|
+
while (true) {
|
|
37
|
+
const { agents } = await prompts(question);
|
|
38
|
+
if (!Array.isArray(agents)) return cancel();
|
|
39
|
+
const picked = [];
|
|
40
|
+
for (const value of agents) {
|
|
41
|
+
const id = agentIdInChoices(value, options.choices);
|
|
42
|
+
if (id === void 0) throw new Error(`Unknown agent: "${String(value)}".`);
|
|
43
|
+
picked.push(id);
|
|
44
|
+
}
|
|
45
|
+
if (picked.length > 0 || !options.skipMessage) return uniqueAgentIds(picked);
|
|
46
|
+
const { skip } = await prompts({
|
|
47
|
+
onState,
|
|
48
|
+
type: "confirm",
|
|
49
|
+
name: "skip",
|
|
50
|
+
message: options.skipMessage,
|
|
51
|
+
initial: true
|
|
52
|
+
});
|
|
53
|
+
if (typeof skip !== "boolean") return cancel();
|
|
54
|
+
if (skip) return [];
|
|
36
55
|
}
|
|
37
|
-
return uniqueAgentIds(picked);
|
|
38
56
|
};
|
|
57
|
+
const skippableAgentPicker = (allowed, message) => allowed ? async (options) => {
|
|
58
|
+
const selected = await pickAgentsInteractively({
|
|
59
|
+
...options,
|
|
60
|
+
skipMessage: message
|
|
61
|
+
});
|
|
62
|
+
if (selected.length === 0) throw new AgentSelectionSkipped();
|
|
63
|
+
return selected;
|
|
64
|
+
} : void 0;
|
|
39
65
|
const resolveAgentSelection = async (options) => {
|
|
40
66
|
if (options.specified.length > 0) {
|
|
41
67
|
const resolve = options.resolveSpecified ?? resolveAddMcpAgentId;
|
|
@@ -67,4 +93,4 @@ const agentIdInChoices = (value, choices) => {
|
|
|
67
93
|
for (const choice of choices) if (choice.id === value) return choice.id;
|
|
68
94
|
};
|
|
69
95
|
//#endregion
|
|
70
|
-
export { agentChoicesFrom, canPickAgentsInteractively, pickAgentsInteractively, resolveAgentSelection };
|
|
96
|
+
export { AgentSelectionSkipped, agentChoicesFrom, canPickAgentsInteractively, pickAgentsInteractively, resolveAgentSelection, skippableAgentPicker };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
"yaml": "^2.9.0",
|
|
65
65
|
"yargs": "17.7.2",
|
|
66
66
|
"yoctocolors": "^2.1.2",
|
|
67
|
+
"@neon/config": "1.8.2",
|
|
67
68
|
"@neon/config-runtime": "1.7.2",
|
|
68
|
-
"@neon/sdk": "6.1.2"
|
|
69
|
-
"@neon/config": "1.8.2"
|
|
69
|
+
"@neon/sdk": "6.1.2"
|
|
70
70
|
},
|
|
71
71
|
"optionalDependencies": {
|
|
72
72
|
"@napi-rs/keyring": "1.3.0",
|