neon 5.0.1 → 6.1.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 +34 -27
- package/dist/commands/bootstrap.js +19 -17
- package/dist/commands/config.js +38 -17
- package/dist/commands/dev.js +1 -1
- package/dist/commands/index.js +3 -3
- package/dist/commands/init.js +45 -302
- package/dist/commands/mcp.js +68 -41
- package/dist/commands/plugins.js +56 -35
- package/dist/commands/skills.js +56 -35
- package/dist/commands/snapshots.js +23 -7
- package/dist/index.js +8 -0
- package/dist/init/auth.js +84 -0
- package/dist/init/cancelled.js +41 -0
- package/dist/init/choices.js +10 -21
- package/dist/init/chrome.js +59 -24
- package/dist/init/copy.js +133 -0
- package/dist/init/detect.js +58 -0
- package/dist/init/funnel.js +115 -0
- package/dist/init/mode.js +30 -0
- package/dist/init/plan.js +135 -32
- package/dist/init/run.js +645 -0
- package/dist/init/tooling.js +104 -22
- package/dist/init/wizard.js +207 -48
- package/dist/mcp/plan.js +1 -1
- package/dist/mcp/wizard.js +1 -27
- package/dist/plugins/plan.js +1 -1
- package/dist/skills/plan.js +1 -1
- package/dist/skills/wizard.js +1 -25
- package/dist/test_utils/npm_env.js +26 -0
- package/dist/update_check_worker.js +47 -0
- package/dist/update_notifier.js +145 -0
- package/dist/utils/package_manager.js +8 -1
- package/dist/utils/service_picker.js +34 -8
- package/package.json +7 -5
- package/dist/init/child.js +0 -37
- package/dist/plugins/wizard.js +0 -36
package/dist/init/tooling.js
CHANGED
|
@@ -1,23 +1,109 @@
|
|
|
1
1
|
import { log } from "../log.js";
|
|
2
2
|
import { getCliName } from "../utils/cli_name.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { assertNamedAgentTooling, childArgv, chooseYesAgentTooling, collectYesAgents, initYesSupportedAgents, noDetectedAgentsMessage, planAgentSteps, planToolingSteps, planYesAgentSteps, resolveInitAgentSetup } from "./plan.js";
|
|
6
|
-
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
3
|
+
import { PROGRESS } from "./copy.js";
|
|
4
|
+
import { assertNamedAgentTooling, chooseYesAgentTooling, collectYesAgents, initYesSupportedAgents, noDetectedAgentsMessage, planAgentSteps, planToolingSteps, planYesAgentSteps, resolveInitAgentSetup } from "./plan.js";
|
|
7
5
|
import { detectAgent } from "./detect_host.js";
|
|
6
|
+
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
7
|
+
import { mcpInstallError, reportMcpInstall } from "../commands/mcp.js";
|
|
8
|
+
import { installPlugins, pluginsInstallError, reportPluginsInstall } from "../commands/plugins.js";
|
|
9
|
+
import { installSkills, reportSkillsInstall, skillsInstallError } from "../commands/skills.js";
|
|
10
|
+
import { runAuthenticatedMcp } from "./auth.js";
|
|
11
|
+
import { raceSigint } from "./cancelled.js";
|
|
8
12
|
import { pickAgentSetupInteractively } from "./wizard.js";
|
|
9
13
|
import { detectProjectAgents } from "add-mcp";
|
|
10
14
|
//#region src/init/tooling.ts
|
|
11
15
|
const defaultProjectAgents = (cwd) => detectProjectAgents(cwd);
|
|
12
|
-
const
|
|
16
|
+
const TOOLING_STEP_LABELS = {
|
|
17
|
+
plugins: PROGRESS.plugins,
|
|
18
|
+
skills: PROGRESS.skills,
|
|
19
|
+
mcp: PROGRESS.mcp
|
|
20
|
+
};
|
|
21
|
+
/** Reconstructs a display-only, `neon <command> <flags>` style string for a step. Never executed — only ever logged. */
|
|
22
|
+
const toolingStepCommandText = (step) => {
|
|
23
|
+
const flags = [];
|
|
24
|
+
switch (step.kind) {
|
|
25
|
+
case "plugins": {
|
|
26
|
+
const { options } = step;
|
|
27
|
+
if (options.yes) flags.push("-y");
|
|
28
|
+
if (options.global) flags.push("--global");
|
|
29
|
+
for (const agent of options.agents ?? []) flags.push("--agent", agent);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case "skills": {
|
|
33
|
+
const { options } = step;
|
|
34
|
+
if (options.yes) flags.push("-y");
|
|
35
|
+
if (options.global) flags.push("--global");
|
|
36
|
+
for (const skill of options.skills ?? []) flags.push("--skill", skill);
|
|
37
|
+
for (const agent of options.agents ?? []) flags.push("--agent", agent);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
case "mcp": {
|
|
41
|
+
const { options } = step;
|
|
42
|
+
if (options.yes) flags.push("-y");
|
|
43
|
+
if (options.oauth) flags.push("--oauth");
|
|
44
|
+
if (options.project) flags.push("--project");
|
|
45
|
+
if (options.projectId !== void 0) flags.push("--project-id", options.projectId);
|
|
46
|
+
for (const agent of options.agent ?? []) flags.push("--agent", agent);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
default: return step;
|
|
50
|
+
}
|
|
51
|
+
return [step.kind, ...flags].join(" ");
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Runs plugins/skills/mcp installs in-process, in order, via {@link ToolingOperations}. This
|
|
55
|
+
* replaces re-executing the `neon` binary as a child process (`neon plugins`, `neon skills`,
|
|
56
|
+
* `neon mcp`) to reuse those commands — see `packages/cli/AGENTS.md`.
|
|
57
|
+
*/
|
|
58
|
+
const runToolingSteps = async (steps, options) => {
|
|
59
|
+
const ops = {
|
|
60
|
+
installPlugins,
|
|
61
|
+
installSkills,
|
|
62
|
+
installMcp: (stepOptions) => runAuthenticatedMcp({
|
|
63
|
+
...stepOptions,
|
|
64
|
+
...options.auth
|
|
65
|
+
}),
|
|
66
|
+
...options.operations
|
|
67
|
+
};
|
|
68
|
+
const reportProps = { output: options.output };
|
|
13
69
|
for (const step of steps) {
|
|
14
|
-
const label = options.narrate === "human" ?
|
|
15
|
-
if (label !== void 0)
|
|
16
|
-
else log.info("Running `%s %s`", getCliName(), step
|
|
17
|
-
log.debug("Running `%s %s`", getCliName(), step
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
70
|
+
const label = options.narrate === "human" ? TOOLING_STEP_LABELS[step.kind] : void 0;
|
|
71
|
+
if (label !== void 0) process.stdout.write(`${label}\n`);
|
|
72
|
+
else log.info("Running `%s %s`", getCliName(), toolingStepCommandText(step));
|
|
73
|
+
log.debug("Running `%s %s`", getCliName(), toolingStepCommandText(step));
|
|
74
|
+
switch (step.kind) {
|
|
75
|
+
case "plugins": {
|
|
76
|
+
const outcome = await raceSigint(ops.installPlugins({
|
|
77
|
+
...step.options,
|
|
78
|
+
cwd: options.cwd
|
|
79
|
+
}));
|
|
80
|
+
reportPluginsInstall(reportProps, outcome);
|
|
81
|
+
const error = pluginsInstallError(outcome);
|
|
82
|
+
if (error) throw error;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "skills": {
|
|
86
|
+
const outcome = await raceSigint(ops.installSkills({
|
|
87
|
+
...step.options,
|
|
88
|
+
cwd: options.cwd
|
|
89
|
+
}));
|
|
90
|
+
reportSkillsInstall(reportProps, outcome);
|
|
91
|
+
const error = skillsInstallError(outcome);
|
|
92
|
+
if (error) throw error;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "mcp": {
|
|
96
|
+
const outcome = await raceSigint(ops.installMcp({
|
|
97
|
+
...step.options,
|
|
98
|
+
cwd: options.cwd
|
|
99
|
+
}));
|
|
100
|
+
reportMcpInstall(reportProps, outcome);
|
|
101
|
+
const error = mcpInstallError(outcome);
|
|
102
|
+
if (error) throw error;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
default: return step;
|
|
106
|
+
}
|
|
21
107
|
}
|
|
22
108
|
};
|
|
23
109
|
const yesAgentsFromOptions = async (options) => collectYesAgents({
|
|
@@ -36,14 +122,14 @@ const runAgentTooling = async (options) => {
|
|
|
36
122
|
if (named.length > 0) {
|
|
37
123
|
assertNamedAgentTooling(named, options.command ?? "init");
|
|
38
124
|
const tooling = chooseYesAgentTooling(named);
|
|
39
|
-
await
|
|
125
|
+
await runToolingSteps(planToolingSteps(tooling, {
|
|
40
126
|
yes,
|
|
41
127
|
named: true
|
|
42
128
|
}), options);
|
|
43
129
|
return tooling.setup;
|
|
44
130
|
}
|
|
45
131
|
if (options.agentSetup !== void 0) {
|
|
46
|
-
await
|
|
132
|
+
await runToolingSteps(planAgentSteps({
|
|
47
133
|
yes,
|
|
48
134
|
agentSetup: options.agentSetup
|
|
49
135
|
}), options);
|
|
@@ -52,7 +138,7 @@ const runAgentTooling = async (options) => {
|
|
|
52
138
|
if (yes) {
|
|
53
139
|
if (options.hasProjectPlugins !== void 0) {
|
|
54
140
|
const agentSetup = await options.hasProjectPlugins(options.cwd) ? "plugin" : "skills-mcp";
|
|
55
|
-
await
|
|
141
|
+
await runToolingSteps(planAgentSteps({
|
|
56
142
|
yes: true,
|
|
57
143
|
agentSetup
|
|
58
144
|
}), options);
|
|
@@ -61,7 +147,7 @@ const runAgentTooling = async (options) => {
|
|
|
61
147
|
const agents = await yesAgentsFromOptions(options);
|
|
62
148
|
const tooling = chooseYesAgentTooling(agents);
|
|
63
149
|
if (tooling.setup === "skip") throw yesMiss();
|
|
64
|
-
await
|
|
150
|
+
await runToolingSteps(planYesAgentSteps(tooling), options);
|
|
65
151
|
return tooling.setup;
|
|
66
152
|
}
|
|
67
153
|
const interactive = options.pickAgentSetup !== void 0 || canPickAgentsInteractively();
|
|
@@ -69,15 +155,11 @@ const runAgentTooling = async (options) => {
|
|
|
69
155
|
interactive,
|
|
70
156
|
pick: options.pickAgentSetup ?? pickAgentSetupInteractively
|
|
71
157
|
});
|
|
72
|
-
await
|
|
158
|
+
await runToolingSteps(planAgentSteps({
|
|
73
159
|
yes,
|
|
74
160
|
agentSetup
|
|
75
161
|
}), options);
|
|
76
162
|
return agentSetup;
|
|
77
163
|
};
|
|
78
|
-
const runScaffoldFollowUp = async (options) => {
|
|
79
|
-
if (!options.skipAgentSetup) await runAgentTooling(options);
|
|
80
|
-
if (options.shouldLink) await runInitSteps([options.linkYes ? ["link", "--yes"] : ["link"]], options);
|
|
81
|
-
};
|
|
82
164
|
//#endregion
|
|
83
|
-
export { runAgentTooling,
|
|
165
|
+
export { runAgentTooling, runToolingSteps };
|
package/dist/init/wizard.js
CHANGED
|
@@ -1,96 +1,254 @@
|
|
|
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";
|
|
3
|
+
import { installedPackageManagers } from "../utils/package_manager.js";
|
|
4
|
+
import { POSTGRES_SERVICE_CHOICE, keepPostgresSelected } from "../utils/service_picker.js";
|
|
5
|
+
import { getAgentDisplayName, listMcpAgentIds } from "../mcp/agents.js";
|
|
1
6
|
import { INIT_NEEDS_YES_OR_TERMINAL } from "./plan.js";
|
|
2
|
-
import { SKIP_TEMPLATE_VALUE, formatTemplateTitle } from "./template_title.js";
|
|
3
7
|
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
4
|
-
import {
|
|
8
|
+
import { NEON_SKILL_CATALOG, listSkillIds } from "../skills/catalog.js";
|
|
9
|
+
import { InitCancelled, restoreCursorVisibilityOnAbort } from "./cancelled.js";
|
|
5
10
|
import chalk from "chalk";
|
|
6
11
|
import prompts from "prompts";
|
|
7
12
|
//#region src/init/wizard.ts
|
|
8
13
|
const restoreCursorOnAbort = (state) => {
|
|
9
|
-
|
|
10
|
-
process.stdout.write("\x1B[?25h");
|
|
11
|
-
process.stdout.write("\n");
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
+
restoreCursorVisibilityOnAbort(state);
|
|
14
15
|
};
|
|
15
16
|
const requireInteractive = () => {
|
|
16
17
|
if (!canPickAgentsInteractively()) throw new Error(INIT_NEEDS_YES_OR_TERMINAL);
|
|
17
18
|
};
|
|
19
|
+
const aborted = () => {
|
|
20
|
+
throw new InitCancelled();
|
|
21
|
+
};
|
|
22
|
+
const pickInitModeInteractively = async () => {
|
|
23
|
+
requireInteractive();
|
|
24
|
+
const { mode } = await prompts({
|
|
25
|
+
onState: restoreCursorOnAbort,
|
|
26
|
+
type: "select",
|
|
27
|
+
name: "mode",
|
|
28
|
+
message: MODE_MESSAGE,
|
|
29
|
+
initial: 0,
|
|
30
|
+
choices: [{
|
|
31
|
+
title: MODE_RECOMMENDED_TITLE,
|
|
32
|
+
value: "recommended",
|
|
33
|
+
description: MODE_RECOMMENDED_DESCRIPTION
|
|
34
|
+
}, {
|
|
35
|
+
title: MODE_CUSTOM_TITLE,
|
|
36
|
+
value: "custom",
|
|
37
|
+
description: MODE_CUSTOM_DESCRIPTION
|
|
38
|
+
}]
|
|
39
|
+
});
|
|
40
|
+
if (mode !== "recommended" && mode !== "custom") return aborted();
|
|
41
|
+
return mode;
|
|
42
|
+
};
|
|
18
43
|
const pickAgentSetupInteractively = async () => {
|
|
19
44
|
requireInteractive();
|
|
20
45
|
const { setup } = await prompts({
|
|
21
46
|
onState: restoreCursorOnAbort,
|
|
22
47
|
type: "select",
|
|
23
48
|
name: "setup",
|
|
24
|
-
message:
|
|
49
|
+
message: AGENT_SETUP_MESSAGE,
|
|
25
50
|
initial: 0,
|
|
26
51
|
choices: [
|
|
27
52
|
{
|
|
28
|
-
title:
|
|
53
|
+
title: AGENT_SETUP_PLUGIN_TITLE,
|
|
29
54
|
value: "plugin",
|
|
30
|
-
description:
|
|
55
|
+
description: AGENT_SETUP_PLUGIN_DESCRIPTION
|
|
31
56
|
},
|
|
32
57
|
{
|
|
33
|
-
title:
|
|
58
|
+
title: AGENT_SETUP_SKILLS_TITLE,
|
|
34
59
|
value: "skills-mcp",
|
|
35
|
-
description:
|
|
60
|
+
description: AGENT_SETUP_SKILLS_DESCRIPTION
|
|
36
61
|
},
|
|
37
62
|
{
|
|
38
|
-
title:
|
|
63
|
+
title: AGENT_SETUP_SKIP_TITLE,
|
|
39
64
|
value: "skip",
|
|
40
|
-
description:
|
|
65
|
+
description: AGENT_SETUP_SKIP_DESCRIPTION
|
|
41
66
|
}
|
|
42
67
|
]
|
|
43
68
|
});
|
|
44
|
-
if (setup !== "plugin" && setup !== "skills-mcp" && setup !== "skip")
|
|
69
|
+
if (setup !== "plugin" && setup !== "skills-mcp" && setup !== "skip") return aborted();
|
|
45
70
|
return setup;
|
|
46
71
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
const pickInitAgentsInteractively = async (input) => {
|
|
73
|
+
requireInteractive();
|
|
74
|
+
if (input.available.length === 0) throw new Error("No coding agents are available to pick.");
|
|
75
|
+
const detected = new Set(input.detected);
|
|
76
|
+
const { agents } = await prompts({
|
|
77
|
+
onState: restoreCursorOnAbort,
|
|
78
|
+
type: "multiselect",
|
|
79
|
+
name: "agents",
|
|
80
|
+
message: AGENT_PICKER_MESSAGE,
|
|
81
|
+
instructions: false,
|
|
82
|
+
min: 1,
|
|
83
|
+
choices: input.available.map((id) => ({
|
|
84
|
+
value: id,
|
|
85
|
+
title: `${getAgentDisplayName(id)} (${id})`,
|
|
86
|
+
description: detected.has(id) ? AGENT_DETECTED_DESCRIPTION : AGENT_OTHER_DESCRIPTION,
|
|
87
|
+
selected: detected.has(id)
|
|
88
|
+
}))
|
|
89
|
+
});
|
|
90
|
+
if (!Array.isArray(agents)) return aborted();
|
|
91
|
+
return agents.filter((id) => typeof id === "string");
|
|
92
|
+
};
|
|
93
|
+
const pickInitSkillsInteractively = async () => {
|
|
94
|
+
requireInteractive();
|
|
95
|
+
const { skills } = await prompts({
|
|
96
|
+
onState: restoreCursorOnAbort,
|
|
97
|
+
type: "multiselect",
|
|
98
|
+
name: "skills",
|
|
99
|
+
message: SKILLS_PICKER_MESSAGE,
|
|
100
|
+
instructions: false,
|
|
101
|
+
min: 1,
|
|
102
|
+
choices: NEON_SKILL_CATALOG.map((entry) => ({
|
|
103
|
+
value: entry.skill,
|
|
104
|
+
title: entry.skill,
|
|
105
|
+
selected: entry.defaultSelected
|
|
106
|
+
}))
|
|
107
|
+
});
|
|
108
|
+
if (!Array.isArray(skills)) return aborted();
|
|
109
|
+
const allowed = new Set(listSkillIds());
|
|
110
|
+
return skills.filter((skill) => typeof skill === "string" && allowed.has(skill));
|
|
111
|
+
};
|
|
112
|
+
const pickInitMcpConfigLocationInteractively = async () => {
|
|
113
|
+
requireInteractive();
|
|
114
|
+
const { scope } = await prompts({
|
|
115
|
+
onState: restoreCursorOnAbort,
|
|
116
|
+
type: "select",
|
|
117
|
+
name: "scope",
|
|
118
|
+
message: MCP_SCOPE_MESSAGE,
|
|
119
|
+
initial: 0,
|
|
120
|
+
choices: [{
|
|
121
|
+
title: MCP_SCOPE_GLOBAL_TITLE,
|
|
122
|
+
value: "global",
|
|
123
|
+
description: MCP_SCOPE_GLOBAL_DESCRIPTION
|
|
124
|
+
}, {
|
|
125
|
+
title: MCP_SCOPE_PROJECT_TITLE,
|
|
126
|
+
value: "project",
|
|
127
|
+
description: MCP_SCOPE_PROJECT_DESCRIPTION
|
|
128
|
+
}]
|
|
129
|
+
});
|
|
130
|
+
if (scope !== "global" && scope !== "project") return aborted();
|
|
131
|
+
return scope;
|
|
132
|
+
};
|
|
133
|
+
const pickInitMcpAuthInteractively = async (input) => {
|
|
134
|
+
requireInteractive();
|
|
135
|
+
const { auth } = await prompts({
|
|
136
|
+
onState: restoreCursorOnAbort,
|
|
137
|
+
type: "select",
|
|
138
|
+
name: "auth",
|
|
139
|
+
message: MCP_AUTH_MESSAGE,
|
|
140
|
+
initial: input.authenticated ? 1 : 0,
|
|
141
|
+
choices: [{
|
|
142
|
+
title: MCP_AUTH_OAUTH_TITLE,
|
|
143
|
+
value: "oauth",
|
|
144
|
+
description: MCP_AUTH_OAUTH_DESCRIPTION
|
|
145
|
+
}, {
|
|
146
|
+
title: MCP_AUTH_API_KEY_TITLE,
|
|
147
|
+
value: "api-key",
|
|
148
|
+
description: MCP_AUTH_API_KEY_DESCRIPTION
|
|
149
|
+
}]
|
|
150
|
+
});
|
|
151
|
+
if (auth !== "oauth" && auth !== "api-key") return aborted();
|
|
152
|
+
return auth;
|
|
60
153
|
};
|
|
61
|
-
const
|
|
154
|
+
const pickInitProjectSetupInteractively = async () => {
|
|
62
155
|
requireInteractive();
|
|
63
|
-
const
|
|
64
|
-
const { id } = await prompts({
|
|
156
|
+
const { setup } = await prompts({
|
|
65
157
|
onState: restoreCursorOnAbort,
|
|
66
158
|
type: "select",
|
|
67
|
-
name: "
|
|
68
|
-
message:
|
|
69
|
-
initial:
|
|
70
|
-
choices
|
|
159
|
+
name: "setup",
|
|
160
|
+
message: PROJECT_SETUP_MESSAGE,
|
|
161
|
+
initial: 0,
|
|
162
|
+
choices: [{
|
|
163
|
+
title: PROJECT_SETUP_LINK_TITLE,
|
|
164
|
+
value: "link",
|
|
165
|
+
description: PROJECT_SETUP_LINK_DESCRIPTION
|
|
166
|
+
}, {
|
|
167
|
+
title: PROJECT_SETUP_CLAIMABLE_TITLE,
|
|
168
|
+
value: "claimable",
|
|
169
|
+
description: PROJECT_SETUP_CLAIMABLE_DESCRIPTION
|
|
170
|
+
}]
|
|
71
171
|
});
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
const selected = templates.find((template) => template.id === id);
|
|
75
|
-
if (selected === void 0) throw new Error("Aborted.");
|
|
76
|
-
return {
|
|
77
|
-
kind: "template",
|
|
78
|
-
template: selected
|
|
79
|
-
};
|
|
172
|
+
if (setup !== "link" && setup !== "claimable") return aborted();
|
|
173
|
+
return setup;
|
|
80
174
|
};
|
|
81
175
|
const pickInitConfigInteractively = async () => {
|
|
82
176
|
requireInteractive();
|
|
83
|
-
process.stdout.write(`${chalk.dim(
|
|
177
|
+
process.stdout.write(`${chalk.dim(CONFIG_CONFIRM_HINT)}\n`);
|
|
84
178
|
const { value } = await prompts({
|
|
85
179
|
onState: restoreCursorOnAbort,
|
|
86
180
|
type: "confirm",
|
|
87
181
|
name: "value",
|
|
88
|
-
message:
|
|
182
|
+
message: CONFIG_CONFIRM_MESSAGE,
|
|
89
183
|
initial: true
|
|
90
184
|
});
|
|
91
|
-
if (value === void 0)
|
|
185
|
+
if (value === void 0) return aborted();
|
|
92
186
|
return value === true;
|
|
93
187
|
};
|
|
188
|
+
const pickInitServicesInteractively = async () => {
|
|
189
|
+
requireInteractive();
|
|
190
|
+
process.stdout.write(`${chalk.dim(SERVICES_HINT)}\n`);
|
|
191
|
+
const { services } = await prompts({
|
|
192
|
+
onState: restoreCursorOnAbort,
|
|
193
|
+
onRender() {
|
|
194
|
+
keepPostgresSelected(this);
|
|
195
|
+
},
|
|
196
|
+
type: "multiselect",
|
|
197
|
+
name: "services",
|
|
198
|
+
message: SERVICES_MESSAGE,
|
|
199
|
+
instructions: false,
|
|
200
|
+
cursor: 1,
|
|
201
|
+
choices: [
|
|
202
|
+
POSTGRES_SERVICE_CHOICE,
|
|
203
|
+
{
|
|
204
|
+
value: "auth",
|
|
205
|
+
title: "Managed Better Auth",
|
|
206
|
+
description: "Declare managed authentication for users and sessions."
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
value: "data-api",
|
|
210
|
+
title: "Data API",
|
|
211
|
+
description: "Declare an HTTP API for Postgres. Also declares Managed Better Auth."
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
value: "functions",
|
|
215
|
+
title: "Functions",
|
|
216
|
+
description: "Declare a hello function and add its source file."
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
value: "object-storage",
|
|
220
|
+
title: "Object Storage",
|
|
221
|
+
description: "Declare a private S3-compatible bucket named assets."
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
value: "ai-gateway",
|
|
225
|
+
title: "AI Gateway",
|
|
226
|
+
description: "Declare AI Gateway access. Requires an eligible Neon plan."
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
});
|
|
230
|
+
if (!Array.isArray(services)) return aborted();
|
|
231
|
+
return CONFIG_INIT_SERVICES.filter((service) => services.includes(service));
|
|
232
|
+
};
|
|
233
|
+
const pickInitPackageManagerInteractively = async () => {
|
|
234
|
+
requireInteractive();
|
|
235
|
+
const available = installedPackageManagers();
|
|
236
|
+
if (available.length <= 1) return available[0];
|
|
237
|
+
const initial = Math.max(0, available.indexOf("npm"));
|
|
238
|
+
const { pm } = await prompts({
|
|
239
|
+
onState: restoreCursorOnAbort,
|
|
240
|
+
type: "select",
|
|
241
|
+
name: "pm",
|
|
242
|
+
message: PACKAGE_MANAGER_MESSAGE,
|
|
243
|
+
initial,
|
|
244
|
+
choices: available.map((id) => ({
|
|
245
|
+
title: id,
|
|
246
|
+
value: id
|
|
247
|
+
}))
|
|
248
|
+
});
|
|
249
|
+
if (pm !== "npm" && pm !== "pnpm" && pm !== "yarn" && pm !== "bun") return aborted();
|
|
250
|
+
return pm;
|
|
251
|
+
};
|
|
94
252
|
const pickInitLinkInteractively = async () => {
|
|
95
253
|
requireInteractive();
|
|
96
254
|
const { value } = await prompts({
|
|
@@ -100,8 +258,9 @@ const pickInitLinkInteractively = async () => {
|
|
|
100
258
|
message: "Link this project to a Neon project now?",
|
|
101
259
|
initial: true
|
|
102
260
|
});
|
|
103
|
-
if (value === void 0)
|
|
261
|
+
if (value === void 0) return aborted();
|
|
104
262
|
return value === true;
|
|
105
263
|
};
|
|
264
|
+
const initAgentIds = () => listMcpAgentIds();
|
|
106
265
|
//#endregion
|
|
107
|
-
export {
|
|
266
|
+
export { initAgentIds, pickAgentSetupInteractively, pickInitAgentsInteractively, pickInitConfigInteractively, pickInitLinkInteractively, pickInitMcpAuthInteractively, pickInitMcpConfigLocationInteractively, pickInitModeInteractively, pickInitPackageManagerInteractively, pickInitProjectSetupInteractively, pickInitServicesInteractively, pickInitSkillsInteractively };
|
package/dist/mcp/plan.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { tryResolveAddMcpAgentId } from "./agents.js";
|
|
2
2
|
import { detectMcpAgents, mcpInstallableAgents } from "./targets.js";
|
|
3
3
|
import { collectYesAgents, noDetectedAgentsMessage } from "../init/plan.js";
|
|
4
|
-
import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
|
|
5
4
|
import { detectAgent } from "../init/detect_host.js";
|
|
5
|
+
import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
|
|
6
6
|
import { pickMcpAuth, pickMcpProjectPin, pickMcpScope } from "./wizard.js";
|
|
7
7
|
//#region src/mcp/plan.ts
|
|
8
8
|
async function resolveMcpPlan(options) {
|
package/dist/mcp/wizard.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getAgentDisplayName } from "./agents.js";
|
|
2
1
|
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
3
2
|
import prompts from "prompts";
|
|
4
3
|
//#region src/mcp/wizard.ts
|
|
@@ -62,30 +61,5 @@ const pickMcpProjectPin = async (linkedProjectId, willMintKey = false) => {
|
|
|
62
61
|
});
|
|
63
62
|
return pin === true;
|
|
64
63
|
};
|
|
65
|
-
const mcpInstallSummary = (options) => {
|
|
66
|
-
const agents = options.install.map(getAgentDisplayName).join(", ");
|
|
67
|
-
const auth = options.auth === "oauth" ? "OAuth, agent signs in on first use" : options.reuse ? "reuse the API key already in agent config" : options.mintProjectId ? `mint an API key limited to ${options.mintProjectId}` : "mint an account-wide API key that reaches every organization";
|
|
68
|
-
const rows = [
|
|
69
|
-
["Config", options.scope === "project" ? "this directory" : "user-level"],
|
|
70
|
-
["Agents", agents],
|
|
71
|
-
["Auth", auth],
|
|
72
|
-
["URL", options.url]
|
|
73
|
-
];
|
|
74
|
-
if (options.skipped.length > 0) rows.push(["Skipped", options.skipped.map((row) => `${getAgentDisplayName(row.agent)} (${row.error})`).join("; ")]);
|
|
75
|
-
const labelWidth = Math.max(...rows.map(([label]) => label.length));
|
|
76
|
-
return rows.map(([label, value]) => `${label.padEnd(labelWidth)} ${value}`).join("\n");
|
|
77
|
-
};
|
|
78
|
-
const confirmMcpInstall = async (options) => {
|
|
79
|
-
if (!canPickAgentsInteractively()) return true;
|
|
80
|
-
process.stdout.write(`\n${mcpInstallSummary(options)}\n\n`);
|
|
81
|
-
const { ok } = await prompts({
|
|
82
|
-
onState: restoreCursorOnAbort,
|
|
83
|
-
type: "confirm",
|
|
84
|
-
name: "ok",
|
|
85
|
-
message: "Write the Neon MCP server into these agents?",
|
|
86
|
-
initial: true
|
|
87
|
-
});
|
|
88
|
-
return ok === true;
|
|
89
|
-
};
|
|
90
64
|
//#endregion
|
|
91
|
-
export {
|
|
65
|
+
export { pickMcpAuth, pickMcpProjectPin, pickMcpScope };
|
package/dist/plugins/plan.js
CHANGED
|
@@ -2,8 +2,8 @@ import { getAgentDisplayName, tryResolveAddMcpAgentId } from "../mcp/agents.js";
|
|
|
2
2
|
import "../init/agents.js";
|
|
3
3
|
import { detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, pluginsInstallableAgents } from "./targets.js";
|
|
4
4
|
import { collectYesAgents, noDetectedAgentsMessage } from "../init/plan.js";
|
|
5
|
-
import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
|
|
6
5
|
import { detectAgent } from "../init/detect_host.js";
|
|
6
|
+
import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
|
|
7
7
|
//#region src/plugins/plan.ts
|
|
8
8
|
const assertPluginsCanRun = (options) => {
|
|
9
9
|
if (options.yes || options.interactive || options.hasAgents) return;
|
package/dist/skills/plan.js
CHANGED
|
@@ -2,8 +2,8 @@ import { tryResolveAddMcpAgentId } from "../mcp/agents.js";
|
|
|
2
2
|
import "../init/agents.js";
|
|
3
3
|
import { detectSkillsAgents, skillsInstallableAgents } from "./targets.js";
|
|
4
4
|
import { collectYesAgents, noDetectedAgentsMessage } from "../init/plan.js";
|
|
5
|
-
import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
|
|
6
5
|
import { detectAgent } from "../init/detect_host.js";
|
|
6
|
+
import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
|
|
7
7
|
import { invocationsForSelection, resolveSkillId, uniqueSkillEntries, yesInstallInvocations } from "./catalog.js";
|
|
8
8
|
import { pickSkillsInteractively } from "./wizard.js";
|
|
9
9
|
//#region src/skills/plan.ts
|
package/dist/skills/wizard.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { getAgentDisplayName } from "../mcp/agents.js";
|
|
2
|
-
import "../init/agents.js";
|
|
3
1
|
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
4
2
|
import { NEON_SKILL_CATALOG } from "./catalog.js";
|
|
5
3
|
import prompts from "prompts";
|
|
@@ -34,28 +32,6 @@ const isSkillEntry = (value) => {
|
|
|
34
32
|
if (!("source" in value) || !("skill" in value)) return false;
|
|
35
33
|
return typeof value.source === "string" && typeof value.skill === "string" && value.source.length > 0 && value.skill.length > 0;
|
|
36
34
|
};
|
|
37
|
-
const skillsInstallSummary = (options) => {
|
|
38
|
-
const skills = options.invocations.flatMap((invocation) => invocation.skills).join(", ");
|
|
39
|
-
const rows = [
|
|
40
|
-
["Config", options.scope === "project" ? "this directory" : "user-level"],
|
|
41
|
-
["Agents", options.agents.map(getAgentDisplayName).join(", ")],
|
|
42
|
-
["Skills", skills]
|
|
43
|
-
];
|
|
44
|
-
const labelWidth = Math.max(...rows.map(([label]) => label.length));
|
|
45
|
-
return rows.map(([label, value]) => `${label.padEnd(labelWidth)} ${value}`).join("\n");
|
|
46
|
-
};
|
|
47
|
-
const confirmSkillsInstall = async (options) => {
|
|
48
|
-
if (!canPickAgentsInteractively()) return true;
|
|
49
|
-
process.stdout.write(`\n${skillsInstallSummary(options)}\n\n`);
|
|
50
|
-
const { ok } = await prompts({
|
|
51
|
-
onState: restoreCursorOnAbort,
|
|
52
|
-
type: "confirm",
|
|
53
|
-
name: "ok",
|
|
54
|
-
message: "Write Neon agent skills into these agents?",
|
|
55
|
-
initial: true
|
|
56
|
-
});
|
|
57
|
-
return ok === true;
|
|
58
|
-
};
|
|
59
35
|
const confirmSkillsUpdate = async (options) => {
|
|
60
36
|
if (!canPickAgentsInteractively()) return true;
|
|
61
37
|
const config = options.scope === "project" ? "this directory" : "user-level";
|
|
@@ -70,4 +46,4 @@ const confirmSkillsUpdate = async (options) => {
|
|
|
70
46
|
return ok === true;
|
|
71
47
|
};
|
|
72
48
|
//#endregion
|
|
73
|
-
export {
|
|
49
|
+
export { confirmSkillsUpdate, pickSkillsInteractively };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
//#region src/test_utils/npm_env.ts
|
|
5
|
+
const isNpmEnvKey = (key) => {
|
|
6
|
+
const lower = key.toLowerCase();
|
|
7
|
+
return lower.startsWith("npm_config_") || lower === "node_auth_token" || lower === "npm_token";
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Keep registry auth when a test replaces HOME. Isolated agent detection
|
|
11
|
+
* otherwise drops ~/.npmrc and `npx`/skills fail with E401 or hang.
|
|
12
|
+
*/
|
|
13
|
+
const npmEnvForIsolatedHome = () => {
|
|
14
|
+
const env = {};
|
|
15
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
16
|
+
if (value === void 0 || !isNpmEnvKey(key)) continue;
|
|
17
|
+
env[key] = value;
|
|
18
|
+
}
|
|
19
|
+
if (env.npm_config_userconfig === void 0 && env.NPM_CONFIG_USERCONFIG === void 0) {
|
|
20
|
+
const src = join(homedir(), ".npmrc");
|
|
21
|
+
if (existsSync(src)) env.npm_config_userconfig = src;
|
|
22
|
+
}
|
|
23
|
+
return env;
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { npmEnvForIsolatedHome };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { recordLatestVersion } from "./update_notifier.js";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
//#region src/update_check_worker.ts
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const LOOKUP_TIMEOUT_MS = 5e3;
|
|
8
|
+
const npmViewInvocation = (platform) => ({
|
|
9
|
+
args: [
|
|
10
|
+
"view",
|
|
11
|
+
"neon",
|
|
12
|
+
"version",
|
|
13
|
+
"--json"
|
|
14
|
+
],
|
|
15
|
+
command: platform === "win32" ? "npm.cmd" : "npm",
|
|
16
|
+
shell: platform === "win32"
|
|
17
|
+
});
|
|
18
|
+
const npmLatestVersion = async () => {
|
|
19
|
+
const invocation = npmViewInvocation(process.platform);
|
|
20
|
+
const { stdout } = await execFileAsync(invocation.command, invocation.args, {
|
|
21
|
+
encoding: "utf8",
|
|
22
|
+
shell: invocation.shell,
|
|
23
|
+
timeout: LOOKUP_TIMEOUT_MS,
|
|
24
|
+
windowsHide: true
|
|
25
|
+
});
|
|
26
|
+
const value = JSON.parse(stdout);
|
|
27
|
+
return typeof value === "string" ? value : void 0;
|
|
28
|
+
};
|
|
29
|
+
const homebrewLatestVersion = async () => {
|
|
30
|
+
const response = await fetch("https://formulae.brew.sh/api/formula/neonctl.json", { signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS) });
|
|
31
|
+
if (!response.ok) return void 0;
|
|
32
|
+
const value = await response.json();
|
|
33
|
+
if (typeof value !== "object" || value === null || !("versions" in value) || typeof value.versions !== "object" || value.versions === null || !("stable" in value.versions) || typeof value.versions.stable !== "string") return;
|
|
34
|
+
return value.versions.stable;
|
|
35
|
+
};
|
|
36
|
+
const run = async () => {
|
|
37
|
+
const [cachePath, source] = process.argv.slice(2);
|
|
38
|
+
if (cachePath === void 0 || source !== "homebrew" && source !== "npm") return;
|
|
39
|
+
try {
|
|
40
|
+
const latestVersion = source === "homebrew" ? await homebrewLatestVersion() : await npmLatestVersion();
|
|
41
|
+
if (latestVersion !== void 0) recordLatestVersion(cachePath, source, latestVersion);
|
|
42
|
+
} catch {}
|
|
43
|
+
};
|
|
44
|
+
const entry = process.argv[1];
|
|
45
|
+
if (entry !== void 0 && import.meta.url === pathToFileURL(entry).href) run();
|
|
46
|
+
//#endregion
|
|
47
|
+
export { npmViewInvocation };
|