neon 8.14.0 → 8.15.1
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 +4 -2
- package/dist/_chunks/{auth-DPSDYYtG.js → auth-rup6nemJ.js} +38 -1
- package/dist/_chunks/{checkout-C6VMMgY6.js → checkout-BpOcFaAi.js} +2 -2
- package/dist/_chunks/{config-7SIE_C5O.js → config-BjCm5cfr.js} +56 -4
- package/dist/_chunks/{deploy-DmKZcAnR.js → deploy-DmEEhux0.js} +1 -1
- package/dist/_chunks/{dev-Dv0HKcFF.js → dev-DlUJFzRs.js} +1 -1
- package/dist/_chunks/{docs-CClmgj_Z.js → docs-BpLiSTmm.js} +1 -1
- package/dist/_chunks/{git-Dxw9w9dK.js → git-j6xSVo3e.js} +2 -2
- package/dist/_chunks/{init-BOOGv-mD.js → init-CcVJb492.js} +1 -1
- package/dist/_chunks/{link-CcwTXRny.js → link-nAiuWKfg.js} +1 -1
- package/dist/_chunks/{plugins-CKOcBuuP.js → plugins-B2Gym8Lw.js} +2 -2
- package/dist/_chunks/{profile-H0d7gFrX.js → profile-BAlM625e.js} +1 -1
- package/dist/_chunks/{status-DS_yUXFo.js → status-BC4XXuuE.js} +1 -1
- package/dist/analytics.js +4 -2
- package/dist/api.js +2 -2
- package/dist/auth_recovery.js +1 -1
- package/dist/commands/auth.js +1 -1
- package/dist/commands/checkout.js +1 -1
- package/dist/commands/config.js +1 -1
- package/dist/commands/deploy.js +1 -1
- package/dist/commands/dev.js +1 -1
- package/dist/commands/docs.js +1 -1
- package/dist/commands/functions_new.js +3 -4
- package/dist/commands/git.js +1 -1
- package/dist/commands/index.js +12 -12
- package/dist/commands/init.js +1 -1
- package/dist/commands/link.js +1 -1
- package/dist/commands/plugins.js +1 -1
- package/dist/commands/profile.js +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/config_format.js +9 -1
- package/dist/config_template.js +36 -9
- package/dist/env.js +19 -4
- package/dist/index.js +1 -1
- package/dist/init/auth.js +23 -2
- package/dist/init/copy.js +3 -2
- package/dist/init/link.js +2 -2
- package/dist/init/run.js +49 -18
- package/dist/init/tooling.js +1 -1
- package/dist/init/wizard.js +5 -5
- package/dist/plugins/child_env.js +10 -0
- package/dist/plugins/plan.js +14 -14
- package/dist/plugins/run.js +2 -8
- package/dist/plugins/targets.js +129 -11
- package/dist/update_notifier.js +7 -4
- package/dist/utils/service_picker.js +1 -1
- package/package.json +6 -6
package/dist/env.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isatty } from "node:tty";
|
|
1
2
|
// src/env.ts
|
|
2
3
|
const isCi = () => {
|
|
3
4
|
return process.env.CI !== "false" && Boolean(process.env.CI);
|
|
@@ -6,16 +7,30 @@ const isInteractiveTerminal = () => !isCi() && Boolean(process.stdin.isTTY) && B
|
|
|
6
7
|
const isDebug = () => {
|
|
7
8
|
return Boolean(process.env.DEBUG);
|
|
8
9
|
};
|
|
10
|
+
const getTty = () => ({
|
|
11
|
+
stdin: isatty(0),
|
|
12
|
+
stdout: isatty(1)
|
|
13
|
+
});
|
|
14
|
+
const AI_AGENT_NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
15
|
+
const isNonEmpty = (value) => Boolean(value?.trim());
|
|
9
16
|
const getCliAgent = (env) => {
|
|
10
17
|
const active = [
|
|
11
18
|
["claude-code", env.CLAUDE_CODE_CHILD_SESSION === "1"],
|
|
12
19
|
["codex", env.CODEX_CI === "1" && (Boolean(env.CODEX_THREAD_ID?.trim()) || Boolean(env.CODEX_SESSION_ID?.trim()))],
|
|
13
20
|
["cursor", env.CURSOR_AGENT === "1"],
|
|
14
|
-
["opencode", env.OPENCODE === "1"],
|
|
21
|
+
["opencode", env.OPENCODE === "1" && env.KILO !== "1"],
|
|
22
|
+
["kilo", env.KILO === "1"],
|
|
15
23
|
["gemini-cli", env.GEMINI_CLI === "1"],
|
|
16
|
-
["
|
|
24
|
+
["qwen-code", env.QWEN_CODE === "1"],
|
|
25
|
+
["github-copilot-cli", env.COPILOT_CLI === "1"],
|
|
26
|
+
["antigravity", isNonEmpty(env.ANTIGRAVITY_AGENT)],
|
|
27
|
+
["amp", isNonEmpty(env.AMP_CURRENT_THREAD_ID)],
|
|
28
|
+
["cline", env.CLINE_ACTIVE === "true"],
|
|
29
|
+
["openclaw", env.OPENCLAW_SHELL === "exec"]
|
|
17
30
|
].filter(([, present]) => present);
|
|
18
|
-
return active.length === 1 ? active[0][0] : void 0;
|
|
31
|
+
if (active.length > 0) return active.length === 1 ? active[0][0] : void 0;
|
|
32
|
+
const declared = env.AI_AGENT?.trim().toLowerCase();
|
|
33
|
+
return declared && AI_AGENT_NAME.test(declared) ? declared : void 0;
|
|
19
34
|
};
|
|
20
35
|
const getGithubEnvVars = (env) => {
|
|
21
36
|
const vars = [
|
|
@@ -38,4 +53,4 @@ const getGithubEnvVars = (env) => {
|
|
|
38
53
|
});
|
|
39
54
|
return Object.fromEntries(map);
|
|
40
55
|
};
|
|
41
|
-
export { getCliAgent, getGithubEnvVars, isCi, isDebug, isInteractiveTerminal };
|
|
56
|
+
export { getCliAgent, getGithubEnvVars, getTty, isCi, isDebug, isInteractiveTerminal };
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { NETWORK_ERROR_MESSAGE, isNetworkError, matchErrorCode } from "./errors.
|
|
|
9
9
|
import { analyticsMiddleware, closeAnalytics, initAnalyticsClientMiddleware, isAnalyticsActive, recordCommandSuccessExtras, sendError, takeCommandSuccessExtras, trackCommandSuccess } from "./analytics.js";
|
|
10
10
|
import { AuthRefreshError, defaultClientID } from "./auth.js";
|
|
11
11
|
import { getCliName } from "./utils/cli_name.js";
|
|
12
|
-
import { c as ensureAuth } from "./_chunks/auth-
|
|
12
|
+
import { c as ensureAuth } from "./_chunks/auth-rup6nemJ.js";
|
|
13
13
|
import { recoverFrom401 } from "./auth_recovery.js";
|
|
14
14
|
import { UsageError, failOnUsageError, isBareParentCommand, showHelp, showUsageErrorHelp } from "./help.js";
|
|
15
15
|
import { lazyCommands, preloadCommand } from "./commands/manifest.js";
|
package/dist/init/auth.js
CHANGED
|
@@ -2,7 +2,7 @@ import { isNeonApiError } from "../api.js";
|
|
|
2
2
|
import { r as defaultDir } from "../_chunks/paths-B55ZfOg1.js";
|
|
3
3
|
import "../config.js";
|
|
4
4
|
import "../auth.js";
|
|
5
|
-
import { c as ensureAuth } from "../_chunks/auth-
|
|
5
|
+
import { c as ensureAuth, l as existingApiClient } from "../_chunks/auth-rup6nemJ.js";
|
|
6
6
|
import { recoverFrom401 } from "../auth_recovery.js";
|
|
7
7
|
import { c as pull } from "../_chunks/env-BOt9AZ1j.js";
|
|
8
8
|
import { c as setupNeonMcp } from "../_chunks/mcp-BznTRK3w.js";
|
|
@@ -54,6 +54,27 @@ const runAuthenticatedMcp = async (options) => {
|
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
/**
|
|
57
|
+
* The client and key `neon config init` may read the linked branch with. Init calls this only
|
|
58
|
+
* after a link is verified, but a pin that was already in place can have no credentials on
|
|
59
|
+
* disk, so this never starts a login: with nothing usable it returns an empty result and the
|
|
60
|
+
* scaffold declares the Neon Auth default with a warning, as a failed read does.
|
|
61
|
+
*/
|
|
62
|
+
const resolveInitConfigApi = async (options) => {
|
|
63
|
+
const access = await existingApiClient({
|
|
64
|
+
configDir: options.configDir ?? defaultDir,
|
|
65
|
+
apiHost: options.apiHost ?? "https://console.neon.tech/api/v2",
|
|
66
|
+
...options.profile !== void 0 ? { profile: options.profile } : {},
|
|
67
|
+
...options.oauthHost !== void 0 ? { oauthHost: options.oauthHost } : {},
|
|
68
|
+
...options.clientId !== void 0 ? { clientId: options.clientId } : {}
|
|
69
|
+
});
|
|
70
|
+
if (access === null) return {};
|
|
71
|
+
return {
|
|
72
|
+
apiClient: access.apiClient,
|
|
73
|
+
apiKey: access.apiKey,
|
|
74
|
+
apiHost: options.apiHost
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
57
78
|
* Resolves Neon auth the same way a standalone `neon env pull` invocation would, then runs
|
|
58
79
|
* `pull` — the same function `link`/`checkout` already call directly via
|
|
59
80
|
* `autoPullEnvAfterPin` (see `commands/env.ts`).
|
|
@@ -77,4 +98,4 @@ const pullInitEnv = async (props) => {
|
|
|
77
98
|
throw authenticationFailed();
|
|
78
99
|
}
|
|
79
100
|
};
|
|
80
|
-
export { pullInitEnv, runAuthenticatedMcp };
|
|
101
|
+
export { pullInitEnv, resolveInitConfigApi, runAuthenticatedMcp };
|
package/dist/init/copy.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getCliName } from "../utils/cli_name.js";
|
|
2
|
+
import { pluginsCommandProblemText } from "../plugins/targets.js";
|
|
2
3
|
// src/init/copy.ts
|
|
3
4
|
const INIT_SUBTITLE = "Set up coding agents and this directory for Neon.";
|
|
4
5
|
const MODE_MESSAGE = "How would you like to set up Neon?";
|
|
@@ -16,7 +17,7 @@ const AGENT_SETUP_SKIP_DESCRIPTION = "Continue to project setup without changing
|
|
|
16
17
|
const AGENT_PICKER_MESSAGE = "Which coding agents should receive Neon tooling? (space to toggle, enter to confirm)";
|
|
17
18
|
const AGENT_DETECTED_DESCRIPTION = "Detected on this machine or in this directory.";
|
|
18
19
|
const AGENT_OTHER_DESCRIPTION = "Install for this agent.";
|
|
19
|
-
const
|
|
20
|
+
const agentUnusableCommandDescription = (row) => `Detected, but "${row.command.name}" ${pluginsCommandProblemText(row)}. Its plugin cannot install.`;
|
|
20
21
|
const AGENT_PLUGIN_SKIP_MESSAGE = "No agents selected. Continue without installing the Neon plugin?";
|
|
21
22
|
const AGENT_SKILLS_PICKER_MESSAGE = "Which coding agents should receive Neon skills? (space to toggle, enter to confirm)";
|
|
22
23
|
const AGENT_MCP_PICKER_MESSAGE = "Which coding agents should receive the Neon MCP server? (space to toggle, enter to confirm)";
|
|
@@ -184,4 +185,4 @@ const headingForOutcome = (outcome) => {
|
|
|
184
185
|
default: return outcome;
|
|
185
186
|
}
|
|
186
187
|
};
|
|
187
|
-
export { AGENTS_NOT_INSTALLED, 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_NEEDS_YES, INIT_SUBTITLE, MCP_API_KEY_NEEDS_AUTH, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, MCP_OAUTH_FALLBACK, 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_PINNED_OFFLINE, 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,
|
|
188
|
+
export { AGENTS_NOT_INSTALLED, 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_NEEDS_YES, INIT_SUBTITLE, MCP_API_KEY_NEEDS_AUTH, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, MCP_OAUTH_FALLBACK, 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_PINNED_OFFLINE, 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, agentUnusableCommandDescription, claimableNext, claimableThenServicesNext, configInitNext, envPullFailedNext, existingConfigNext, headingForOutcome, installFailedNext, mcpAfterLinkNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, mcpKeyFailedNext, namedAgentsUnavailable, newConfigNext, offlinePinNext, skippedLinkNext, toolingFailedNext, unattendedUnauthedNext };
|
package/dist/init/link.js
CHANGED
|
@@ -2,9 +2,9 @@ import { isNeonApiError } from "../api.js";
|
|
|
2
2
|
import { r as defaultDir } from "../_chunks/paths-B55ZfOg1.js";
|
|
3
3
|
import "../config.js";
|
|
4
4
|
import "../auth.js";
|
|
5
|
-
import { c as ensureAuth } from "../_chunks/auth-
|
|
5
|
+
import { c as ensureAuth } from "../_chunks/auth-rup6nemJ.js";
|
|
6
6
|
import { recoverFrom401 } from "../auth_recovery.js";
|
|
7
|
-
import { s as runLink } from "../_chunks/link-
|
|
7
|
+
import { s as runLink } from "../_chunks/link-nAiuWKfg.js";
|
|
8
8
|
// src/init/link.ts
|
|
9
9
|
const isUnauthorized = (error) => isNeonApiError(error) && error.status === 401;
|
|
10
10
|
const authenticationFailed = () => /* @__PURE__ */ new Error("Authentication failed.");
|
package/dist/init/run.js
CHANGED
|
@@ -11,17 +11,17 @@ import { CONFIG_INIT_SERVICES, parseConfigInitServices } from "../config_templat
|
|
|
11
11
|
import { hasNeonConfigFile, neonConfigFilename } from "../project.js";
|
|
12
12
|
import { getAgentDisplayName } from "../mcp/agents.js";
|
|
13
13
|
import { mcpInstallableAgents } from "../mcp/targets.js";
|
|
14
|
-
import { missingPluginsCommands, pluginsInstallableAgents } from "../plugins/targets.js";
|
|
14
|
+
import { missingPluginsCommands, pluginsCommandProblemText, pluginsInstallableAgents, unusablePluginsCommands } from "../plugins/targets.js";
|
|
15
15
|
import { skillsInstallableAgents } from "../skills/targets.js";
|
|
16
16
|
import { FALLBACK_SKILLS_AGENTS, assertNamedAgentTooling, funnelAgentSetup, planInitToolingSteps, recommendedTooling, resolveNamedAgents, splitInitTooling } from "./plan.js";
|
|
17
17
|
import { inferPackageManager, resolvePackageManager } from "../utils/package_manager.js";
|
|
18
18
|
import { AGENTS_NOT_INSTALLED, CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, MCP_API_KEY_NEEDS_AUTH, MCP_SCOPED_NEEDS_PROJECT, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, PROGRESS, PROJECT_PINNED_OFFLINE, claimableNext, claimableThenServicesNext, configInitNext, envPullFailedNext, existingConfigNext, installFailedNext, mcpAfterLinkNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, mcpKeyFailedNext, namedAgentsUnavailable, newConfigNext, offlinePinNext, skippedLinkNext, toolingFailedNext, unattendedUnauthedNext } from "./copy.js";
|
|
19
19
|
import { agentsRowValue, configRowValue, formatInitDone, headingForKind, printInitBanner, printInitDone, printInitProgress, projectRowValue, shouldPrintInitBanner } from "./chrome.js";
|
|
20
|
-
import { h as initCmd, t as ConfigInstallFailed } from "../_chunks/config-
|
|
21
|
-
import { o as quoteFlagValue, s as runLink } from "../_chunks/link-
|
|
20
|
+
import { h as initCmd, t as ConfigInstallFailed } from "../_chunks/config-BjCm5cfr.js";
|
|
21
|
+
import { o as quoteFlagValue, s as runLink } from "../_chunks/link-nAiuWKfg.js";
|
|
22
22
|
import { runAuthenticatedLink } from "./link.js";
|
|
23
23
|
import { CannotMintApiKeyError } from "../mcp/mint.js";
|
|
24
|
-
import { pullInitEnv } from "./auth.js";
|
|
24
|
+
import { pullInitEnv, resolveInitConfigApi } from "./auth.js";
|
|
25
25
|
import { InitCancelled, raceSigint, restoreCursor } from "./cancelled.js";
|
|
26
26
|
import { pickAgentSetupInteractively, pickInitAgentsInteractively, pickInitConfigInteractively, pickInitMcpAuthInteractively, pickInitMcpConfigLocationInteractively, pickInitModeInteractively, pickInitPackageManagerInteractively, pickInitProjectSetupInteractively, pickInitServicesInteractively, pickInitSkillsInteractively } from "./wizard.js";
|
|
27
27
|
import { runToolingSteps, toolingStepCommandText } from "./tooling.js";
|
|
@@ -67,10 +67,10 @@ const pluginScopeFor = (agents, preferred) => {
|
|
|
67
67
|
const project = new Set(pluginsInstallableAgents("project"));
|
|
68
68
|
return agents.some((id) => !project.has(id) && pluginsInstallableAgents("global").includes(id)) ? "global" : "project";
|
|
69
69
|
};
|
|
70
|
-
const
|
|
70
|
+
const warnUnusablePluginCommands = (unusable, tooling) => {
|
|
71
71
|
if (tooling.setup !== "skills-mcp" && tooling.setup !== "mixed") return;
|
|
72
72
|
const routed = /* @__PURE__ */ new Set([...tooling.skillsAgents, ...tooling.mcpAgents]);
|
|
73
|
-
for (const
|
|
73
|
+
for (const row of unusable) if (routed.has(row.agent)) log.warning("%s: \"%s\" %s, so its plugin cannot install. Installing Neon skills and MCP instead.", getAgentDisplayName(row.agent), row.command.name, pluginsCommandProblemText(row));
|
|
74
74
|
};
|
|
75
75
|
const nonEmptyAgents = (ids, fallback) => {
|
|
76
76
|
const [first, ...rest] = ids;
|
|
@@ -124,19 +124,34 @@ const pickOrDetectAgents = async (input) => {
|
|
|
124
124
|
available: [...input.available],
|
|
125
125
|
detected,
|
|
126
126
|
preselected,
|
|
127
|
-
...input.
|
|
127
|
+
...input.unusableCommands !== void 0 ? { unusableCommands: input.unusableCommands } : {},
|
|
128
128
|
setup: input.setup
|
|
129
129
|
});
|
|
130
130
|
return detected;
|
|
131
131
|
};
|
|
132
132
|
const defaultInitConfig = async (input) => {
|
|
133
|
+
let project = {};
|
|
134
|
+
if (input.project) try {
|
|
135
|
+
project = {
|
|
136
|
+
...await resolveInitConfigApi(input.project.auth),
|
|
137
|
+
projectId: input.project.projectId,
|
|
138
|
+
...input.project.branch !== void 0 ? { branch: input.project.branch } : {},
|
|
139
|
+
...input.project.auth.configDir !== void 0 ? { configDir: input.project.auth.configDir } : {},
|
|
140
|
+
...input.project.auth.profile !== void 0 ? { profile: input.project.auth.profile } : {},
|
|
141
|
+
...input.project.auth.oauthHost !== void 0 ? { oauthHost: input.project.auth.oauthHost } : {},
|
|
142
|
+
...input.project.auth.clientId !== void 0 ? { clientId: input.project.auth.clientId } : {}
|
|
143
|
+
};
|
|
144
|
+
} catch (error) {
|
|
145
|
+
log.warning("Couldn't authenticate to read the Data API on the linked branch (%s); declaring it with Neon Auth.", error instanceof Error ? error.message : String(error));
|
|
146
|
+
}
|
|
133
147
|
await initCmd({
|
|
134
148
|
cwd: input.cwd,
|
|
135
149
|
install: input.install,
|
|
136
150
|
silent: true,
|
|
137
151
|
requireInstall: true,
|
|
138
152
|
...input.services !== void 0 ? { services: input.services } : {},
|
|
139
|
-
...input.packageManager !== void 0 ? { packageManager: input.packageManager } : {}
|
|
153
|
+
...input.packageManager !== void 0 ? { packageManager: input.packageManager } : {},
|
|
154
|
+
...project
|
|
140
155
|
});
|
|
141
156
|
};
|
|
142
157
|
const defaultCreateClaimable = async (input) => {
|
|
@@ -250,9 +265,18 @@ const runInit = async (props) => {
|
|
|
250
265
|
}
|
|
251
266
|
const recommended = mode === "recommended";
|
|
252
267
|
const targets = named.length > 0 ? named : detection.detectedAgents;
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
268
|
+
let probedPluginCommands;
|
|
269
|
+
const unusablePluginCommands = () => {
|
|
270
|
+
probedPluginCommands ??= unusablePluginsCommands(targets, { cwd });
|
|
271
|
+
return probedPluginCommands;
|
|
272
|
+
};
|
|
273
|
+
let missingCommands;
|
|
274
|
+
const missingPluginCommands = () => {
|
|
275
|
+
missingCommands ??= missingPluginsCommands(targets, { cwd });
|
|
276
|
+
return missingCommands;
|
|
277
|
+
};
|
|
278
|
+
const unavailablePluginAgents = () => unusablePluginCommands().map((row) => row.agent);
|
|
279
|
+
const pluginReady = (agents) => agents.filter((id) => !unavailablePluginAgents().includes(id));
|
|
256
280
|
let tooling = { setup: "skip" };
|
|
257
281
|
let mcpAuth = props.mcpAuth;
|
|
258
282
|
let mcpAuthChosen = props.mcpAuth !== void 0;
|
|
@@ -262,9 +286,9 @@ const runInit = async (props) => {
|
|
|
262
286
|
let pluginScope = recommended ? "global" : "project";
|
|
263
287
|
if (recommended) {
|
|
264
288
|
pluginScope = "global";
|
|
265
|
-
tooling = named.length > 0 ? splitInitTooling(named, "global", "global", unavailablePluginAgents) : recommendedTooling(detection.detectedAgents, unavailablePluginAgents);
|
|
289
|
+
tooling = named.length > 0 ? splitInitTooling(named, "global", "global", unavailablePluginAgents()) : recommendedTooling(detection.detectedAgents, unavailablePluginAgents());
|
|
266
290
|
if (named.length === 0 && detection.detectedAgents.length === 0) printInitProgress(NO_AGENTS_FALLBACK_STATUS);
|
|
267
|
-
|
|
291
|
+
warnUnusablePluginCommands(unusablePluginCommands(), tooling);
|
|
268
292
|
mcpAuth = !detection.authenticated && (tooling.setup === "skills-mcp" || tooling.setup === "mixed") ? "oauth" : mcpAuth;
|
|
269
293
|
} else {
|
|
270
294
|
const inferred = inferInitAgentSetup({
|
|
@@ -279,8 +303,8 @@ const runInit = async (props) => {
|
|
|
279
303
|
else if (inferred.kind === "auto") {
|
|
280
304
|
const autoAgents = named.length > 0 ? named : detection.detectedAgents;
|
|
281
305
|
pluginScope = pluginScopeFor(pluginReady(autoAgents), "project");
|
|
282
|
-
tooling = splitInitTooling(autoAgents, pluginScope, mcpConfigLocation, unavailablePluginAgents);
|
|
283
|
-
|
|
306
|
+
tooling = splitInitTooling(autoAgents, pluginScope, mcpConfigLocation, unavailablePluginAgents());
|
|
307
|
+
warnUnusablePluginCommands(unusablePluginCommands(), tooling);
|
|
284
308
|
if (tooling.setup === "skip" && yes && named.length === 0) {
|
|
285
309
|
tooling = recommendedTooling([]);
|
|
286
310
|
printInitProgress(NO_AGENTS_FALLBACK_STATUS);
|
|
@@ -295,8 +319,8 @@ const runInit = async (props) => {
|
|
|
295
319
|
available,
|
|
296
320
|
detected: detection.detectedAgents,
|
|
297
321
|
...setup === "plugin" ? {
|
|
298
|
-
preselected:
|
|
299
|
-
|
|
322
|
+
preselected: detection.detectedAgents.filter((id) => !missingPluginCommands().some((row) => row.agent === id)),
|
|
323
|
+
unusableCommands: missingPluginCommands()
|
|
300
324
|
} : {},
|
|
301
325
|
interactive: detection.interactive,
|
|
302
326
|
setup,
|
|
@@ -557,12 +581,19 @@ const runInit = async (props) => {
|
|
|
557
581
|
}
|
|
558
582
|
if (!existingConfig) printInitProgress(PROGRESS.config);
|
|
559
583
|
printInitProgress(PROGRESS.install(pm));
|
|
584
|
+
const readableProject = funnel.link !== "skipped" && !pinnedOffline && declared.includes("data-api") ? readContextFile(contextFile) : void 0;
|
|
585
|
+
const readableBranch = readableProject !== void 0 ? contextBranch(readableProject) : void 0;
|
|
560
586
|
try {
|
|
561
587
|
await (props.initConfig ?? defaultInitConfig)({
|
|
562
588
|
cwd,
|
|
563
589
|
install: true,
|
|
564
590
|
packageManager: pm,
|
|
565
|
-
...planned !== void 0 ? { services: planned } : {}
|
|
591
|
+
...planned !== void 0 ? { services: planned } : {},
|
|
592
|
+
...typeof readableProject?.projectId === "string" ? { project: {
|
|
593
|
+
projectId: readableProject.projectId,
|
|
594
|
+
...readableBranch !== void 0 ? { branch: readableBranch } : {},
|
|
595
|
+
auth
|
|
596
|
+
} } : {}
|
|
566
597
|
});
|
|
567
598
|
} catch (error) {
|
|
568
599
|
if (error instanceof ConfigInstallFailed) {
|
package/dist/init/tooling.js
CHANGED
|
@@ -6,7 +6,7 @@ import { CannotMintApiKeyError } from "../mcp/mint.js";
|
|
|
6
6
|
import { detectAgent } from "./detect_host.js";
|
|
7
7
|
import { AgentSelectionSkipped, canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
8
8
|
import { a as mcpInstallError, s as reportMcpInstall } from "../_chunks/mcp-BznTRK3w.js";
|
|
9
|
-
import { l as reportPluginsInstall, o as installPlugins, s as pluginsInstallError } from "../_chunks/plugins-
|
|
9
|
+
import { l as reportPluginsInstall, o as installPlugins, s as pluginsInstallError } from "../_chunks/plugins-B2Gym8Lw.js";
|
|
10
10
|
import { c as skillsInstallError, o as installSkills, s as reportSkillsInstall } from "../_chunks/skills-DKgafm65.js";
|
|
11
11
|
import { runAuthenticatedMcp } from "./auth.js";
|
|
12
12
|
import { raceSigint } from "./cancelled.js";
|
package/dist/init/wizard.js
CHANGED
|
@@ -2,7 +2,7 @@ import { CONFIG_INIT_SERVICES } from "../config_template.js";
|
|
|
2
2
|
import { getAgentDisplayName, listMcpAgentIds } from "../mcp/agents.js";
|
|
3
3
|
import { INIT_NEEDS_YES_OR_TERMINAL } from "./plan.js";
|
|
4
4
|
import { installedPackageManagers } from "../utils/package_manager.js";
|
|
5
|
-
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,
|
|
5
|
+
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, agentUnusableCommandDescription } from "./copy.js";
|
|
6
6
|
import { POSTGRES_SERVICE_CHOICE, keepPostgresSelected } from "../utils/service_picker.js";
|
|
7
7
|
import { canPickAgentsInteractively, pickAgentsInteractively } from "../utils/agent_picker.js";
|
|
8
8
|
import { NEON_SKILL_CATALOG, listSkillIds } from "../skills/catalog.js";
|
|
@@ -72,18 +72,18 @@ const pickAgentSetupInteractively = async () => {
|
|
|
72
72
|
const pickInitAgentsInteractively = async (input) => {
|
|
73
73
|
requireInteractive();
|
|
74
74
|
const detected = new Set(input.detected);
|
|
75
|
-
const
|
|
75
|
+
const unusable = new Map((input.unusableCommands ?? []).map((row) => [row.agent, row]));
|
|
76
76
|
const selected = await pickAgentsInteractively({
|
|
77
77
|
onCancel: aborted,
|
|
78
78
|
message: input.setup === "skills" ? AGENT_SKILLS_PICKER_MESSAGE : input.setup === "mcp" ? AGENT_MCP_PICKER_MESSAGE : AGENT_PICKER_MESSAGE,
|
|
79
79
|
skipMessage: input.setup === "plugin" ? AGENT_PLUGIN_SKIP_MESSAGE : input.setup === "skills" ? AGENT_SKILLS_SKIP_MESSAGE : AGENT_MCP_SKIP_MESSAGE,
|
|
80
80
|
selected: input.preselected ?? input.detected,
|
|
81
81
|
choices: input.available.map((id) => {
|
|
82
|
-
const
|
|
82
|
+
const row = unusable.get(id);
|
|
83
83
|
return {
|
|
84
84
|
id,
|
|
85
85
|
title: `${getAgentDisplayName(id)} (${id})`,
|
|
86
|
-
description:
|
|
86
|
+
description: row !== void 0 ? agentUnusableCommandDescription(row) : detected.has(id) ? AGENT_DETECTED_DESCRIPTION : AGENT_OTHER_DESCRIPTION
|
|
87
87
|
};
|
|
88
88
|
})
|
|
89
89
|
});
|
|
@@ -207,7 +207,7 @@ const pickInitServicesInteractively = async () => {
|
|
|
207
207
|
{
|
|
208
208
|
value: "data-api",
|
|
209
209
|
title: "Data API",
|
|
210
|
-
description: "Declare an HTTP API for Postgres. Also declares Managed Better Auth."
|
|
210
|
+
description: "Declare an HTTP API for Postgres. Also declares Managed Better Auth, unless the branch's Data API already uses an external identity provider."
|
|
211
211
|
},
|
|
212
212
|
{
|
|
213
213
|
value: "functions",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/plugins/child_env.ts
|
|
2
|
+
const TELEMETRY_BLOCKERS = ["DISABLE_TELEMETRY", "DO_NOT_TRACK"];
|
|
3
|
+
/** The env the plugins CLI and the agent CLIs it calls run with. */
|
|
4
|
+
const pluginsChildEnv = (base = process.env) => {
|
|
5
|
+
const env = { ...base };
|
|
6
|
+
for (const key of TELEMETRY_BLOCKERS) delete env[key];
|
|
7
|
+
for (const key of Object.keys(env)) if (key.toUpperCase() === "NEON_API_KEY") delete env[key];
|
|
8
|
+
return env;
|
|
9
|
+
};
|
|
10
|
+
export { pluginsChildEnv };
|
package/dist/plugins/plan.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { getAgentDisplayName, tryResolveAddMcpAgentId } from "../mcp/agents.js";
|
|
2
2
|
import "../init/agents.js";
|
|
3
|
-
import { detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, missingPluginsCommands, pluginsInstallableAgents } from "./targets.js";
|
|
3
|
+
import { detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, missingPluginsCommands, pluginsCommandFix, pluginsCommandProblemText, pluginsInstallableAgents, unusablePluginsCommands } 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
|
// src/plugins/plan.ts
|
|
8
|
-
const
|
|
8
|
+
const unusablePluginsCommandsError = (unusable) => new Error(unusable.map((row) => `Cannot install the Neon plugin for ${getAgentDisplayName(row.agent)}: "${row.command.name}" ${pluginsCommandProblemText(row)}. ${pluginsCommandFix(row)}, then retry.`).join("\n"));
|
|
9
9
|
const assertPluginsCanRun = (options) => {
|
|
10
10
|
if (options.yes || options.interactive || options.hasAgents) return;
|
|
11
11
|
throw new Error("No interactive terminal. Pass -y to install into detected agents, or --agent <name> to name them.");
|
|
@@ -34,18 +34,18 @@ async function resolvePluginsPlan(options) {
|
|
|
34
34
|
detectAgent: options.detectAgent ?? detectAgent,
|
|
35
35
|
acceptHost: (id) => availableSet.has(id) || getPluginsTargetName(id) !== void 0
|
|
36
36
|
}) : scoped;
|
|
37
|
-
const
|
|
37
|
+
const unusableDetected = new Map((prompt && options.agents.length === 0 ? missingPluginsCommands(detected, commandLookup(options)) : []).map((row) => [row.agent, row]));
|
|
38
38
|
const choices = agentChoicesFrom(available, detected).map((choice) => {
|
|
39
|
-
const
|
|
40
|
-
return
|
|
39
|
+
const row = unusableDetected.get(choice.id);
|
|
40
|
+
return row === void 0 ? choice : {
|
|
41
41
|
...choice,
|
|
42
|
-
description: `detected, but "${command}"
|
|
42
|
+
description: `detected, but "${row.command.name}" ${pluginsCommandProblemText(row)}`
|
|
43
43
|
};
|
|
44
44
|
});
|
|
45
45
|
const selected = await resolveAgentSelection({
|
|
46
46
|
specified: options.agents,
|
|
47
47
|
choices,
|
|
48
|
-
detected: detected.filter((id) => !
|
|
48
|
+
detected: detected.filter((id) => !unusableDetected.has(id)),
|
|
49
49
|
message: "Which coding agents should get the Neon plugin? (space to toggle, enter to confirm)",
|
|
50
50
|
nonInteractiveMessage: noDetectedAgentsMessage({
|
|
51
51
|
scope,
|
|
@@ -77,17 +77,17 @@ async function resolvePluginsPlan(options) {
|
|
|
77
77
|
}
|
|
78
78
|
agents.push(id);
|
|
79
79
|
}
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
if (!(options.agents.length === 0 && !prompt)) throw
|
|
83
|
-
const missingAgents = new Set(
|
|
80
|
+
const unusableCommands = unusablePluginsCommands(agents, commandLookup(options));
|
|
81
|
+
if (unusableCommands.length > 0) {
|
|
82
|
+
if (!(options.agents.length === 0 && !prompt)) throw unusablePluginsCommandsError(unusableCommands);
|
|
83
|
+
const missingAgents = new Set(unusableCommands.map((row) => row.agent));
|
|
84
84
|
const installable = agents.filter((id) => !missingAgents.has(id));
|
|
85
85
|
return {
|
|
86
86
|
scope,
|
|
87
87
|
agents: installable,
|
|
88
88
|
skipped,
|
|
89
89
|
userScopeSkipped,
|
|
90
|
-
|
|
90
|
+
unusableCommands,
|
|
91
91
|
targets: installable.length === 0 ? [] : mappedPluginsTargets(installable, scope)
|
|
92
92
|
};
|
|
93
93
|
}
|
|
@@ -103,8 +103,8 @@ async function resolvePluginsPlan(options) {
|
|
|
103
103
|
agents,
|
|
104
104
|
skipped,
|
|
105
105
|
userScopeSkipped,
|
|
106
|
-
|
|
106
|
+
unusableCommands,
|
|
107
107
|
targets: mappedPluginsTargets(agents, scope)
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
|
-
export { assertPluginsCanRun,
|
|
110
|
+
export { assertPluginsCanRun, resolvePluginsPlan, unusablePluginsCommandsError };
|
package/dist/plugins/run.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { pluginsChildEnv } from "./child_env.js";
|
|
1
2
|
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { execa } from "execa";
|
|
3
4
|
// src/plugins/run.ts
|
|
4
|
-
const TELEMETRY_BLOCKERS = ["DISABLE_TELEMETRY", "DO_NOT_TRACK"];
|
|
5
5
|
const PLUGIN_SOURCE = "neondatabase/agent-skills";
|
|
6
6
|
const NEON_PLUGIN_NAME = "neon-postgres";
|
|
7
7
|
const PLUGIN_SKILLS = [
|
|
@@ -16,12 +16,6 @@ const PLUGIN_SKILLS = [
|
|
|
16
16
|
"neon-realtime"
|
|
17
17
|
];
|
|
18
18
|
const PLUGINS_CLI_TIMEOUT_MS = 12e4;
|
|
19
|
-
const pluginsChildEnv = (base = process.env) => {
|
|
20
|
-
const env = { ...base };
|
|
21
|
-
for (const key of TELEMETRY_BLOCKERS) delete env[key];
|
|
22
|
-
for (const key of Object.keys(env)) if (key.toUpperCase() === "NEON_API_KEY") delete env[key];
|
|
23
|
-
return env;
|
|
24
|
-
};
|
|
25
19
|
const pluginsScopeFlag = (global) => global ? "user" : "project";
|
|
26
20
|
const pluginsAddArgs = (options) => {
|
|
27
21
|
if (options.target.length === 0) throw new Error("plugins add needs a -t target.");
|
|
@@ -130,4 +124,4 @@ const killProcessTree = (pid) => {
|
|
|
130
124
|
};
|
|
131
125
|
const isCommandMissing = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
132
126
|
const isExecaFailure = (error) => typeof error === "object" && error !== null && "shortMessage" in error && typeof error.shortMessage === "string";
|
|
133
|
-
export { NEON_PLUGIN_NAME, PLUGINS_CLI_TIMEOUT_MS, PLUGIN_SKILLS, PLUGIN_SOURCE, neonPluginsRetryCommand, pluginsAddArgs,
|
|
127
|
+
export { NEON_PLUGIN_NAME, PLUGINS_CLI_TIMEOUT_MS, PLUGIN_SKILLS, PLUGIN_SOURCE, neonPluginsRetryCommand, pluginsAddArgs, pluginsCliFailureMessage, pluginsScopeFlag, quoteNpxArg, runPluginsCli };
|
package/dist/plugins/targets.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { detectInstalledAgents, listMcpAgentIds, tryResolveAddMcpAgentId, uniqueAgentIds } from "../mcp/agents.js";
|
|
2
2
|
import "../init/agents.js";
|
|
3
|
+
import { pluginsChildEnv } from "./child_env.js";
|
|
3
4
|
import { delimiter, dirname, join, resolve } from "node:path";
|
|
4
5
|
import { detectProjectAgents } from "add-mcp";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
5
7
|
import which from "which";
|
|
6
8
|
// src/plugins/targets.ts
|
|
7
9
|
const CLAUDE_CODE = { target: "claude-code" };
|
|
@@ -13,11 +15,20 @@ const PLUGINS_TARGET_BY_TYPE = {
|
|
|
13
15
|
codex: { target: "codex" },
|
|
14
16
|
"github-copilot-cli": {
|
|
15
17
|
target: "github-copilot",
|
|
16
|
-
command:
|
|
18
|
+
command: {
|
|
19
|
+
name: "copilot",
|
|
20
|
+
product: "GitHub Copilot CLI",
|
|
21
|
+
versionPrefix: "GitHub Copilot CLI",
|
|
22
|
+
minVersion: "0.0.400"
|
|
23
|
+
}
|
|
17
24
|
},
|
|
18
25
|
"grok-build": {
|
|
19
26
|
target: "grok",
|
|
20
|
-
command:
|
|
27
|
+
command: {
|
|
28
|
+
name: "grok",
|
|
29
|
+
product: "Grok Build",
|
|
30
|
+
versionPrefix: "grok"
|
|
31
|
+
}
|
|
21
32
|
}
|
|
22
33
|
};
|
|
23
34
|
const pluginsTargets = () => [...new Set(Object.values(PLUGINS_TARGET_BY_TYPE))];
|
|
@@ -38,20 +49,127 @@ const pluginsCommandSearchPath = (cwd, path) => {
|
|
|
38
49
|
}
|
|
39
50
|
return [...bins, path].join(delimiter);
|
|
40
51
|
};
|
|
41
|
-
const
|
|
52
|
+
const VERSION_TIMEOUT_MS = 1e4;
|
|
53
|
+
const versionParts = (version) => version.split(".").map((part) => Number.parseInt(part, 10));
|
|
54
|
+
/** Exported for tests. */
|
|
55
|
+
const versionAtLeast = (version, minimum) => {
|
|
56
|
+
const have = versionParts(version);
|
|
57
|
+
const need = versionParts(minimum);
|
|
58
|
+
for (let i = 0; i < 3; i++) {
|
|
59
|
+
const a = have[i] ?? 0;
|
|
60
|
+
const b = need[i] ?? 0;
|
|
61
|
+
if (a !== b) return a > b;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
};
|
|
65
|
+
/** Exported for tests. */
|
|
66
|
+
const pluginsCommandVersionProblem = (command, output) => {
|
|
67
|
+
const label = `${command.versionPrefix} `;
|
|
68
|
+
const at = output.indexOf(label);
|
|
69
|
+
const version = at === -1 ? void 0 : output.slice(at + label.length).match(/^\d+\.\d+\.\d+/)?.[0];
|
|
70
|
+
if (version === void 0) return { kind: "unrecognized" };
|
|
71
|
+
if (command.minVersion !== void 0 && !versionAtLeast(version, command.minVersion)) return {
|
|
72
|
+
kind: "outdated",
|
|
73
|
+
version
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
const firstLine = (text) => text.split("\n").map((line) => line.trim()).find((line) => line.length > 0) ?? "";
|
|
77
|
+
/** The child sees the same PATH the command was resolved on, as under npx. */
|
|
78
|
+
const withPath = (env, path) => {
|
|
79
|
+
const key = Object.keys(env).find((name) => name.toUpperCase() === "PATH");
|
|
80
|
+
return {
|
|
81
|
+
...env,
|
|
82
|
+
[key ?? "PATH"]: path
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Runs `<command> --version` the way the plugins CLI spawns the command (no shell, the npx
|
|
87
|
+
* search PATH, its child env), so a command it could not run is caught before it runs.
|
|
88
|
+
*/
|
|
89
|
+
const probePluginsCommand = (command, options) => {
|
|
90
|
+
const resolved = which.sync(command.name, {
|
|
91
|
+
nothrow: true,
|
|
92
|
+
path: options.searchPath
|
|
93
|
+
});
|
|
94
|
+
if (resolved === null) return { kind: "missing" };
|
|
95
|
+
const result = spawnSync(resolved, ["--version"], {
|
|
96
|
+
cwd: options.cwd,
|
|
97
|
+
env: withPath(pluginsChildEnv(), options.searchPath),
|
|
98
|
+
encoding: "utf8",
|
|
99
|
+
stdio: [
|
|
100
|
+
"ignore",
|
|
101
|
+
"pipe",
|
|
102
|
+
"pipe"
|
|
103
|
+
],
|
|
104
|
+
timeout: VERSION_TIMEOUT_MS,
|
|
105
|
+
windowsHide: true
|
|
106
|
+
});
|
|
107
|
+
if (result.error !== void 0) {
|
|
108
|
+
const code = "code" in result.error && typeof result.error.code === "string" ? result.error.code : void 0;
|
|
109
|
+
return {
|
|
110
|
+
kind: "broken",
|
|
111
|
+
detail: code === "ETIMEDOUT" ? `timed out after ${VERSION_TIMEOUT_MS / 1e3} seconds` : code ?? result.error.message
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
115
|
+
if (result.status !== 0) {
|
|
116
|
+
const line = firstLine(result.stderr) || firstLine(result.stdout);
|
|
117
|
+
return {
|
|
118
|
+
kind: "broken",
|
|
119
|
+
detail: line.length > 0 ? line.slice(0, 160).replace(/\.$/, "") : result.signal !== null ? `killed by ${result.signal}` : `exit code ${result.status}`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return pluginsCommandVersionProblem(command, output);
|
|
123
|
+
};
|
|
124
|
+
const commandRows = (agents, options, check) => {
|
|
42
125
|
const searchPath = pluginsCommandSearchPath(options.cwd, options.path ?? process.env.PATH ?? "");
|
|
43
|
-
const
|
|
126
|
+
const rows = [];
|
|
44
127
|
for (const agent of uniqueAgentIds(agents)) {
|
|
45
128
|
const command = getPluginsTarget(agent)?.command;
|
|
46
|
-
if (command
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}) === null) missing.push({
|
|
129
|
+
if (command === void 0) continue;
|
|
130
|
+
const problem = check(command, searchPath);
|
|
131
|
+
if (problem !== void 0) rows.push({
|
|
50
132
|
agent,
|
|
51
|
-
command
|
|
133
|
+
command,
|
|
134
|
+
problem
|
|
52
135
|
});
|
|
53
136
|
}
|
|
54
|
-
return
|
|
137
|
+
return rows;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Agents whose plugin command is not on the search PATH. Runs nothing, so it is safe for
|
|
141
|
+
* agents that were only detected and may not be installed.
|
|
142
|
+
*/
|
|
143
|
+
const missingPluginsCommands = (agents, options) => commandRows(agents, options, (command, searchPath) => which.sync(command.name, {
|
|
144
|
+
nothrow: true,
|
|
145
|
+
path: searchPath
|
|
146
|
+
}) === null ? { kind: "missing" } : void 0);
|
|
147
|
+
/**
|
|
148
|
+
* Agents whose plugin installs through a CLI that is missing, does not run, is not the
|
|
149
|
+
* expected product, or is too old. Runs `<command> --version`, so pass only agents the
|
|
150
|
+
* plugin is about to install into; file-based targets never spawn anything.
|
|
151
|
+
*/
|
|
152
|
+
const unusablePluginsCommands = (agents, options) => commandRows(agents, options, (command, searchPath) => probePluginsCommand(command, {
|
|
153
|
+
cwd: options.cwd,
|
|
154
|
+
searchPath
|
|
155
|
+
}));
|
|
156
|
+
/** Completes `"<command>" …` in a warning or error. */
|
|
157
|
+
const pluginsCommandProblemText = (row) => {
|
|
158
|
+
const { command, problem } = row;
|
|
159
|
+
const product = command.product;
|
|
160
|
+
switch (problem.kind) {
|
|
161
|
+
case "missing": return "was not found on PATH";
|
|
162
|
+
case "broken": return `did not run ("${command.name} --version" failed: ${problem.detail})`;
|
|
163
|
+
case "unrecognized": return `is not ${product} ("${command.name} --version" printed no ${product} version)`;
|
|
164
|
+
case "outdated": return `is ${product} ${problem.version}, older than the ${command.minVersion} its plugin needs`;
|
|
165
|
+
default: return problem;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
/** What to do about the problem, as an imperative clause. */
|
|
169
|
+
const pluginsCommandFix = (row) => {
|
|
170
|
+
const { command, problem } = row;
|
|
171
|
+
if (problem.kind === "missing") return "Install it or add it to PATH";
|
|
172
|
+
return `Install or update ${command.product}`;
|
|
55
173
|
};
|
|
56
174
|
function getPluginsTarget(agent) {
|
|
57
175
|
if (Object.prototype.hasOwnProperty.call(PLUGINS_TARGET_BY_TYPE, agent)) return PLUGINS_TARGET_BY_TYPE[agent];
|
|
@@ -104,4 +222,4 @@ const mappedPluginsTargets = (agents, scope) => {
|
|
|
104
222
|
if (mapped.length === 0) throw new Error(`None of the selected agents can install plugins. Supported agents: ${pluginsInstallableAgents(scope).join(", ")}`);
|
|
105
223
|
return mapped;
|
|
106
224
|
};
|
|
107
|
-
export { detectInstallablePluginsAgents, detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, missingPluginsCommands, pluginsInstallableAgents, pluginsMappedAgents, pluginsTargets, supportsPlugins };
|
|
225
|
+
export { detectInstallablePluginsAgents, detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, missingPluginsCommands, pluginsCommandFix, pluginsCommandProblemText, pluginsCommandVersionProblem, pluginsInstallableAgents, pluginsMappedAgents, pluginsTargets, supportsPlugins, unusablePluginsCommands, versionAtLeast };
|