neon 3.4.0 → 3.5.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/dist/init/agents.js +73 -119
- package/dist/init/editors.js +3 -30
- package/dist/init/inspect.js +69 -20
- package/dist/init/install_mcp.js +35 -0
- package/dist/init/interactive.js +95 -107
- package/dist/init/neonctl.js +6 -2
- package/dist/init/orchestrate.js +20 -6
- package/dist/init/phases/mcp.js +91 -50
- package/dist/init/phases/setup.js +43 -34
- package/dist/init/phases/skills.js +10 -2
- package/dist/init/phases/status.js +4 -2
- package/dist/init/skills.js +55 -53
- package/package.json +3 -2
package/dist/init/interactive.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { FALLBACK_TEMPLATES, fetchTemplates, scaffoldTemplate } from "./bootstrap.js";
|
|
2
2
|
import { detectAgent, detectIde } from "./detect_agent.js";
|
|
3
|
-
import {
|
|
3
|
+
import { agentSupportsProjectMcp, detectInstalledAgents, getAgentDisplayName, getSkillsAgentName, mcpPickerOptions, tryResolveAddMcpAgentId } from "./agents.js";
|
|
4
4
|
import { ensureNeonctlAuth, isAuthenticated } from "./auth.js";
|
|
5
|
-
import {
|
|
6
|
-
import { installExtension, isExtensionInstalled, usesExtension } from "./extension.js";
|
|
5
|
+
import { installExtension, isExtensionInstalled } from "./extension.js";
|
|
7
6
|
import { inspectProject } from "./inspect.js";
|
|
7
|
+
import { installNeonMcpServer } from "./install_mcp.js";
|
|
8
8
|
import { ensureNeonctl } from "./neonctl.js";
|
|
9
|
-
import { ensureSkillsUpToDate, installAgentSkills } from "./skills.js";
|
|
9
|
+
import { ensureSkillsUpToDate, installAgentSkills, skillsInstalledForAgent } from "./skills.js";
|
|
10
10
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { resolve } from "node:path";
|
|
12
12
|
import which from "which";
|
|
13
13
|
import { SelectPrompt } from "@clack/core";
|
|
14
14
|
import { confirm, isCancel, log, multiselect, outro, select, spinner } from "@clack/prompts";
|
|
15
|
-
import { execa } from "execa";
|
|
16
15
|
import { bold, dim, gray, italic } from "yoctocolors";
|
|
17
16
|
import picocolors from "picocolors";
|
|
18
17
|
//#region src/init/interactive.ts
|
|
@@ -114,8 +113,10 @@ async function interactiveInitInner(options) {
|
|
|
114
113
|
" ╚═╝ ╚══╝╚═════╝ ╚═════╝ ╚═╝ ╚══╝"
|
|
115
114
|
].join("\n")}\x1b[0m\n`);
|
|
116
115
|
process.stdout.write(`${dim(wordWrap("\nLet's get your project set up with Neon. We'll install the MCP server, agent skills, and IDE extension, then connect your app to a database.\n", process.stdout.columns || 80))}\n`);
|
|
117
|
-
const
|
|
118
|
-
|
|
116
|
+
const detectedAgent = (() => {
|
|
117
|
+
const raw = detectAgent();
|
|
118
|
+
return raw ? tryResolveAddMcpAgentId(raw) : void 0;
|
|
119
|
+
})();
|
|
119
120
|
const inspectSpinner = spinner();
|
|
120
121
|
inspectSpinner.start("Checking existing configuration...");
|
|
121
122
|
const inspection = await inspectProject([
|
|
@@ -216,12 +217,12 @@ async function interactiveInitInner(options) {
|
|
|
216
217
|
existing._init = { features: selectedFeatures };
|
|
217
218
|
writeFileSync(neonPath, `${JSON.stringify(existing, null, 2)}\n`);
|
|
218
219
|
}
|
|
219
|
-
const
|
|
220
|
-
const
|
|
220
|
+
const mcpHits = inspection.mcpAgents ?? [];
|
|
221
|
+
const detectedHasMcp = detectedAgent ? mcpHits.some((hit) => hit.agent === detectedAgent) : false;
|
|
222
|
+
const mcpAlready = detectedAgent ? detectedHasMcp : inspection.mcpConfigured === true;
|
|
223
|
+
const skillsAlready = selectedTemplate !== null || (detectedAgent ? skillsInstalledForAgent(detectedAgent, process.cwd(), options.preview) : inspection.skillsInstalled === true);
|
|
221
224
|
const hasNeonConnection = inspection.connectionString === true;
|
|
222
|
-
|
|
223
|
-
const needsSkills = !skillsAlready;
|
|
224
|
-
const needsInstall = needsMcp || needsSkills;
|
|
225
|
+
const needsInstall = !mcpAlready || !skillsAlready;
|
|
225
226
|
const neonContextPath = resolve(process.cwd(), ".neon");
|
|
226
227
|
const hasNeonContext = existsSync(neonContextPath) && (() => {
|
|
227
228
|
try {
|
|
@@ -241,7 +242,8 @@ async function interactiveInitInner(options) {
|
|
|
241
242
|
return false;
|
|
242
243
|
})();
|
|
243
244
|
let extensionAlready = false;
|
|
244
|
-
|
|
245
|
+
const detectedIdeEditor = extensionEditorForAgent(detectedAgent);
|
|
246
|
+
if (detectedIdeEditor) extensionAlready = await isExtensionInstalled(detectedIdeEditor);
|
|
245
247
|
if (mcpAlready && skillsAlready && hasNeonConnection && hasNeonContext) {
|
|
246
248
|
log.step(dim(`Neon MCP server already configured (${inspection.mcpScope || "detected"}) ✓`));
|
|
247
249
|
log.step(dim(`Neon agent skills already installed (${inspection.skillsScope || "detected"}) ✓`));
|
|
@@ -282,55 +284,39 @@ async function interactiveInitInner(options) {
|
|
|
282
284
|
if (mcpAlready) log.step(dim(`Neon MCP server already configured (${inspection.mcpScope || "detected"}) ✓`));
|
|
283
285
|
if (skillsAlready) log.step(dim(`Neon agent skills already installed (${inspection.skillsScope || "detected"}) ✓`));
|
|
284
286
|
if (needsInstall) {
|
|
285
|
-
|
|
286
|
-
if (!homeDir) {
|
|
287
|
+
if (!(process.env.HOME || process.env.USERPROFILE)) {
|
|
287
288
|
log.error("Could not determine home directory.");
|
|
288
289
|
outro("Setup failed.");
|
|
289
290
|
return;
|
|
290
291
|
}
|
|
291
|
-
let
|
|
292
|
-
if (
|
|
292
|
+
let selectedAgents;
|
|
293
|
+
if (detectedAgent) selectedAgents = [detectedAgent];
|
|
293
294
|
else {
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
message: "Which editor(s) would you like to configure?",
|
|
297
|
-
options: ALL_CONFIGURABLE_AGENTS.map((agent) => ({
|
|
298
|
-
value: agent.editor,
|
|
299
|
-
label: agent.editor,
|
|
300
|
-
hint: agent.hint
|
|
301
|
-
})),
|
|
302
|
-
initialValues: availableEditors,
|
|
303
|
-
required: true
|
|
304
|
-
});
|
|
305
|
-
if (isCancel(editorResponse)) {
|
|
306
|
-
outro("Setup cancelled.");
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
selectedEditors = editorResponse;
|
|
310
|
-
if (selectedEditors.length === 0) {
|
|
311
|
-
log.warn("No editors selected.");
|
|
295
|
+
const picked = await pickAgents();
|
|
296
|
+
if (!picked) {
|
|
312
297
|
outro("Setup cancelled.");
|
|
313
298
|
return;
|
|
314
299
|
}
|
|
300
|
+
selectedAgents = picked;
|
|
315
301
|
}
|
|
316
|
-
|
|
302
|
+
let vscodeEditors = extensionEditorsFor(selectedAgents);
|
|
317
303
|
let extensionAlreadyInstalled = false;
|
|
318
304
|
if (vscodeEditors.length > 0) {
|
|
319
305
|
extensionAlreadyInstalled = (await Promise.all(vscodeEditors.map((e) => isExtensionInstalled(e)))).every(Boolean);
|
|
320
306
|
if (extensionAlreadyInstalled) log.step(dim("Neon editor extension already installed ✓"));
|
|
321
307
|
}
|
|
322
|
-
|
|
308
|
+
let canInstallExtension = vscodeEditors.length > 0 && !extensionAlreadyInstalled;
|
|
323
309
|
let doInstallExtension = false;
|
|
324
|
-
const hintParts = [];
|
|
325
|
-
if (needsMcp) hintParts.push("MCP server (global)");
|
|
326
|
-
if (needsSkills) hintParts.push("agent skills (project)");
|
|
327
310
|
let mcpScope = "global";
|
|
328
311
|
let skillsScope = "project";
|
|
329
312
|
let modeResult;
|
|
330
313
|
while (true) {
|
|
331
|
-
const
|
|
314
|
+
const hintParts = [];
|
|
315
|
+
if (selectedAgents.some((agent) => !mcpHits.some((hit) => hit.agent === agent))) hintParts.push("MCP server (global)");
|
|
316
|
+
if (selectedTemplate === null && selectedAgents.some((agent) => getSkillsAgentName(agent) && !skillsInstalledForAgent(agent, process.cwd(), options.preview))) hintParts.push("agent skills (project)");
|
|
317
|
+
const agentNames = selectedAgents.map((id) => getAgentDisplayName(id)).join(", ");
|
|
332
318
|
const result = await select({
|
|
333
|
-
message: `Configure ${
|
|
319
|
+
message: `Configure ${agentNames} for Neon:`,
|
|
334
320
|
options: [
|
|
335
321
|
{
|
|
336
322
|
value: "defaults",
|
|
@@ -344,7 +330,7 @@ async function interactiveInitInner(options) {
|
|
|
344
330
|
},
|
|
345
331
|
{
|
|
346
332
|
value: "change_editor",
|
|
347
|
-
label: "Configure a different
|
|
333
|
+
label: "Configure a different agent"
|
|
348
334
|
}
|
|
349
335
|
],
|
|
350
336
|
initialValue: "defaults"
|
|
@@ -354,33 +340,21 @@ async function interactiveInitInner(options) {
|
|
|
354
340
|
return;
|
|
355
341
|
}
|
|
356
342
|
if (result === "change_editor") {
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
message: "Which editor(s) would you like to configure?",
|
|
360
|
-
options: ALL_CONFIGURABLE_AGENTS.map((agent) => ({
|
|
361
|
-
value: agent.editor,
|
|
362
|
-
label: agent.editor,
|
|
363
|
-
hint: agent.hint
|
|
364
|
-
})),
|
|
365
|
-
initialValues: availableEditors,
|
|
366
|
-
required: true
|
|
367
|
-
});
|
|
368
|
-
if (isCancel(editorResponse)) {
|
|
369
|
-
outro("Setup cancelled.");
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
selectedEditors = editorResponse;
|
|
373
|
-
if (selectedEditors.length === 0) {
|
|
343
|
+
const picked = await pickAgents();
|
|
344
|
+
if (!picked) {
|
|
374
345
|
outro("Setup cancelled.");
|
|
375
346
|
return;
|
|
376
347
|
}
|
|
348
|
+
selectedAgents = picked;
|
|
349
|
+
vscodeEditors = extensionEditorsFor(selectedAgents);
|
|
350
|
+
canInstallExtension = vscodeEditors.length > 0 && !extensionAlreadyInstalled;
|
|
377
351
|
continue;
|
|
378
352
|
}
|
|
379
353
|
modeResult = result;
|
|
380
354
|
break;
|
|
381
355
|
}
|
|
382
356
|
if (modeResult === "customize") {
|
|
383
|
-
if (
|
|
357
|
+
if (selectedAgents.some((agent) => !mcpHits.some((hit) => hit.agent === agent))) {
|
|
384
358
|
const scopeResult = await select({
|
|
385
359
|
message: "Where should the Neon MCP server be configured?",
|
|
386
360
|
options: [
|
|
@@ -388,10 +362,10 @@ async function interactiveInitInner(options) {
|
|
|
388
362
|
value: "global",
|
|
389
363
|
label: "Global (available in all projects)"
|
|
390
364
|
},
|
|
391
|
-
{
|
|
365
|
+
...selectedAgents.every(agentSupportsProjectMcp) ? [{
|
|
392
366
|
value: "project",
|
|
393
367
|
label: "Project-level (this project only)"
|
|
394
|
-
},
|
|
368
|
+
}] : [],
|
|
395
369
|
{
|
|
396
370
|
value: "none",
|
|
397
371
|
label: "Skip — do not install the MCP server"
|
|
@@ -403,9 +377,8 @@ async function interactiveInitInner(options) {
|
|
|
403
377
|
return;
|
|
404
378
|
}
|
|
405
379
|
mcpScope = scopeResult;
|
|
406
|
-
if (mcpScope === "none") needsMcp = false;
|
|
407
380
|
}
|
|
408
|
-
if (
|
|
381
|
+
if (selectedTemplate === null && selectedAgents.some((agent) => getSkillsAgentName(agent) && !skillsInstalledForAgent(agent, process.cwd(), options.preview))) {
|
|
409
382
|
const skillsScopeResult = await select({
|
|
410
383
|
message: "Where should Neon agent skills be installed?",
|
|
411
384
|
options: [{
|
|
@@ -444,7 +417,9 @@ async function interactiveInitInner(options) {
|
|
|
444
417
|
}
|
|
445
418
|
const nctlS = spinner();
|
|
446
419
|
nctlS.start("Checking Neon CLI...");
|
|
447
|
-
const nctlResult = await ensureNeonctl()
|
|
420
|
+
const nctlResult = await ensureNeonctl((phase) => {
|
|
421
|
+
nctlS.message(phase === "installing" ? "Installing Neon CLI..." : "Updating Neon CLI...");
|
|
422
|
+
});
|
|
448
423
|
switch (nctlResult.status) {
|
|
449
424
|
case "already_current":
|
|
450
425
|
nctlS.stop(dim(`Neon CLI is up to date (v${nctlResult.version}) ✓`));
|
|
@@ -456,47 +431,39 @@ async function interactiveInitInner(options) {
|
|
|
456
431
|
nctlS.stop(dim(`Updated Neon CLI to v${nctlResult.version} ✓`));
|
|
457
432
|
break;
|
|
458
433
|
case "failed":
|
|
459
|
-
nctlS.stop("Failed to install Neon CLI");
|
|
434
|
+
nctlS.stop(nctlResult.action === "updating" ? "Failed to update Neon CLI" : "Failed to install Neon CLI");
|
|
460
435
|
log.warn(nctlResult.error ?? "The Neon CLI could not be installed automatically.");
|
|
461
436
|
if (which.sync("npx", { nothrow: true })) log.warn("Setup will continue using npx.");
|
|
462
437
|
}
|
|
463
|
-
for (const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
"-y",
|
|
467
|
-
"add-mcp",
|
|
468
|
-
"https://mcp.neon.tech/mcp",
|
|
469
|
-
"-n",
|
|
470
|
-
"Neon",
|
|
471
|
-
"-y",
|
|
472
|
-
"-a",
|
|
473
|
-
getAddMcpAgentId(editor)
|
|
474
|
-
];
|
|
475
|
-
if (mcpScope === "global") mcpArgs.splice(5, 0, "-g");
|
|
438
|
+
for (const agent of selectedAgents) {
|
|
439
|
+
const label = getAgentDisplayName(agent);
|
|
440
|
+
if (mcpScope !== "none" && !mcpHits.some((hit) => hit.agent === agent)) {
|
|
476
441
|
const mcpS = spinner();
|
|
477
|
-
mcpS.start(`Installing Neon MCP server for ${
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
442
|
+
mcpS.start(`Installing Neon MCP server for ${label}...`);
|
|
443
|
+
const installed = installNeonMcpServer({
|
|
444
|
+
agent,
|
|
445
|
+
scope: mcpScope === "project" ? "project" : "global",
|
|
446
|
+
cwd: process.cwd()
|
|
447
|
+
});
|
|
448
|
+
if (installed.ok) mcpS.stop(dim(`Neon MCP server configured for ${label} (${mcpScope}) ✓`));
|
|
449
|
+
else if (installed.unsupported) {
|
|
450
|
+
mcpS.stop(`Could not write MCP config for ${label}`);
|
|
451
|
+
log.warn(installed.error);
|
|
452
|
+
} else {
|
|
453
|
+
mcpS.stop(`Failed to configure MCP server for ${label}`);
|
|
454
|
+
log.error(installed.error);
|
|
488
455
|
}
|
|
489
456
|
}
|
|
490
|
-
if (
|
|
457
|
+
if (selectedTemplate === null && getSkillsAgentName(agent) && !skillsInstalledForAgent(agent, process.cwd(), options.preview)) await installAgentSkills([agent], {
|
|
491
458
|
scope: skillsScope,
|
|
492
459
|
preview: options.preview
|
|
493
460
|
});
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
461
|
+
}
|
|
462
|
+
if (doInstallExtension) for (const editor of vscodeEditors) {
|
|
463
|
+
const extS = spinner();
|
|
464
|
+
extS.start(`Installing Neon extension for ${editor}...`);
|
|
465
|
+
if (await installExtension(editor)) extS.stop(dim(`Neon extension installed for ${editor} ✓`));
|
|
466
|
+
else extS.stop(`Extension install failed — install manually from the extensions panel.`);
|
|
500
467
|
}
|
|
501
468
|
}
|
|
502
469
|
const ide = detectIde();
|
|
@@ -524,16 +491,37 @@ async function interactiveInitInner(options) {
|
|
|
524
491
|
log.message(wordWrap(promptText, cols).split("\n").map((line) => bold(neonGreenFn(line))).join("\n"));
|
|
525
492
|
outro(dim("Have feedback? Email us at feedback@neon.tech"));
|
|
526
493
|
}
|
|
527
|
-
function
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
494
|
+
function extensionEditorForAgent(agent) {
|
|
495
|
+
if (agent === "cursor") return "Cursor";
|
|
496
|
+
if (agent === "vscode") return "VS Code";
|
|
497
|
+
const ide = detectIde();
|
|
498
|
+
if (ide === "Cursor" || ide === "VS Code") return ide;
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
function extensionEditorsFor(selected) {
|
|
502
|
+
const out = [];
|
|
503
|
+
if (selected.includes("cursor")) out.push("Cursor");
|
|
504
|
+
if (selected.includes("vscode")) out.push("VS Code");
|
|
505
|
+
const ide = detectIde();
|
|
506
|
+
if ((ide === "Cursor" || ide === "VS Code") && !out.includes(ide)) out.push(ide);
|
|
507
|
+
return out;
|
|
508
|
+
}
|
|
509
|
+
async function pickAgents() {
|
|
510
|
+
const options = mcpPickerOptions();
|
|
511
|
+
const installed = await detectInstalledAgents();
|
|
512
|
+
const response = await multiselect({
|
|
513
|
+
message: "Which agent(s) would you like to configure?",
|
|
514
|
+
options,
|
|
515
|
+
initialValues: installed.filter((id) => options.some((option) => option.value === id)),
|
|
516
|
+
required: true
|
|
517
|
+
});
|
|
518
|
+
if (isCancel(response)) return null;
|
|
519
|
+
const selected = response;
|
|
520
|
+
if (selected.length === 0) {
|
|
521
|
+
log.warn("No agents selected.");
|
|
522
|
+
return null;
|
|
536
523
|
}
|
|
524
|
+
return selected;
|
|
537
525
|
}
|
|
538
526
|
//#endregion
|
|
539
527
|
export { interactiveInit };
|
package/dist/init/neonctl.js
CHANGED
|
@@ -104,7 +104,7 @@ function isLocalDevSymlink() {
|
|
|
104
104
|
* Ensures neonctl is globally installed and up to date.
|
|
105
105
|
* Uses the same package manager that invoked the init command.
|
|
106
106
|
*/
|
|
107
|
-
async function ensureNeonctl() {
|
|
107
|
+
async function ensureNeonctl(onProgress) {
|
|
108
108
|
if (isLocalDevSymlink()) return {
|
|
109
109
|
status: "already_current",
|
|
110
110
|
version: await getNeonCliVersion() ?? "dev"
|
|
@@ -116,11 +116,14 @@ async function ensureNeonctl() {
|
|
|
116
116
|
};
|
|
117
117
|
const pm = resolveInvokingPackageManager();
|
|
118
118
|
const install = globalInstallCommand(pm, "neon");
|
|
119
|
+
const action = check.installed ? "updating" : "installing";
|
|
119
120
|
if (!install) return {
|
|
120
121
|
status: "failed",
|
|
121
|
-
|
|
122
|
+
action,
|
|
123
|
+
error: `Could not ${action === "updating" ? "update" : "install"} the Neon CLI: this machine has no package manager that can perform a global install. npm, pnpm and bun are not on PATH (yarn Berry has no global install). Install npm, pnpm or bun, then run this again.`
|
|
122
124
|
};
|
|
123
125
|
const { command, args } = install;
|
|
126
|
+
onProgress?.(action);
|
|
124
127
|
try {
|
|
125
128
|
await execa(command, args, {
|
|
126
129
|
stdio: "pipe",
|
|
@@ -134,6 +137,7 @@ async function ensureNeonctl() {
|
|
|
134
137
|
} catch (err) {
|
|
135
138
|
return {
|
|
136
139
|
status: "failed",
|
|
140
|
+
action,
|
|
137
141
|
error: err instanceof Error ? err.message : "Unknown error"
|
|
138
142
|
};
|
|
139
143
|
}
|
package/dist/init/orchestrate.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { supportsSkills, tryResolveAddMcpAgentId } from "./agents.js";
|
|
1
2
|
import { isAuthenticated } from "./auth.js";
|
|
2
3
|
import { inspectProject } from "./inspect.js";
|
|
4
|
+
import { getSkillList, missingSkillsForAgent, skillsInstalledForAgent } from "./skills.js";
|
|
3
5
|
import { handleAuthPhase } from "./phases/auth.js";
|
|
4
6
|
import { handleGettingStartedPhase } from "./phases/getting_started.js";
|
|
5
7
|
import { handleMigrationsPhase } from "./phases/migrations.js";
|
|
@@ -62,9 +64,19 @@ async function orchestrate(options) {
|
|
|
62
64
|
}
|
|
63
65
|
]);
|
|
64
66
|
const hasApp = options.preview ? inspection.hasApp === true : true;
|
|
67
|
+
const requestedAgent = options.agent ? tryResolveAddMcpAgentId(options.agent) : void 0;
|
|
65
68
|
let skillsInstalled = inspection.skillsInstalled;
|
|
66
|
-
if (options.preview &&
|
|
67
|
-
const
|
|
69
|
+
if (options.preview && requestedAgent && supportsSkills(requestedAgent)) {
|
|
70
|
+
const missing = missingSkillsForAgent(requestedAgent, {
|
|
71
|
+
cwd,
|
|
72
|
+
preview: true
|
|
73
|
+
});
|
|
74
|
+
const required = getSkillList(true);
|
|
75
|
+
if (missing.length > 0) {
|
|
76
|
+
skillsInstalled = false;
|
|
77
|
+
if (missing.length < required.length) inspection.skillsScope = `${inspection.skillsScope && !String(inspection.skillsScope).includes("partial") ? inspection.skillsScope : "project"}-partial`;
|
|
78
|
+
}
|
|
79
|
+
} else if (options.preview && skillsInstalled && inspection.skillsScope) {
|
|
68
80
|
const previewSkills = getSkillList(true);
|
|
69
81
|
const { existsSync: exists } = await import("node:fs");
|
|
70
82
|
const { resolve: resolvePath } = await import("node:path");
|
|
@@ -83,7 +95,9 @@ async function orchestrate(options) {
|
|
|
83
95
|
inspection.skillsScope = `${inspection.skillsScope}-partial`;
|
|
84
96
|
}
|
|
85
97
|
}
|
|
86
|
-
const
|
|
98
|
+
const mcpForThisAgent = requestedAgent ? (inspection.mcpAgents ?? []).some((hit) => hit.agent === requestedAgent) : inspection.mcpConfigured === true;
|
|
99
|
+
const skillsReady = requestedAgent && !supportsSkills(requestedAgent) ? true : requestedAgent ? skillsInstalledForAgent(requestedAgent, cwd, options.preview) : skillsInstalled === true;
|
|
100
|
+
const toolingInstalled = mcpForThisAgent && skillsReady;
|
|
87
101
|
const hasNeonConnection = inspection.connectionString === true;
|
|
88
102
|
if (!hasApp || !toolingInstalled) {
|
|
89
103
|
cleanupInitState(resolve(cwd, ".neon"));
|
|
@@ -91,9 +105,9 @@ async function orchestrate(options) {
|
|
|
91
105
|
agent: options.agent,
|
|
92
106
|
preview: options.preview,
|
|
93
107
|
hasApp,
|
|
94
|
-
mcpConfigured: inspection.mcpConfigured ?? null,
|
|
108
|
+
mcpConfigured: requestedAgent ? mcpForThisAgent : inspection.mcpConfigured ?? null,
|
|
95
109
|
mcpScope: inspection.mcpScope || void 0,
|
|
96
|
-
skillsInstalled: skillsInstalled ?? null,
|
|
110
|
+
skillsInstalled: requestedAgent ? skillsReady : skillsInstalled ?? null,
|
|
97
111
|
skillsScope: inspection.skillsScope || void 0
|
|
98
112
|
});
|
|
99
113
|
}
|
|
@@ -137,7 +151,7 @@ async function orchestrate(options) {
|
|
|
137
151
|
status: "complete",
|
|
138
152
|
nextAction: {
|
|
139
153
|
type: "complete",
|
|
140
|
-
message: "Neon setup is complete. Your database is configured and your agent has the Neon MCP server and skills available."
|
|
154
|
+
message: requestedAgent && !supportsSkills(requestedAgent) ? "Neon setup is complete. Your database is configured and your agent has the Neon MCP server available." : "Neon setup is complete. Your database is configured and your agent has the Neon MCP server and skills available."
|
|
141
155
|
}
|
|
142
156
|
};
|
|
143
157
|
}
|
package/dist/init/phases/mcp.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
import { resolveAddMcpAgentId } from "../agents.js";
|
|
1
|
+
import { agentSupportsHttpMcp, agentSupportsProjectMcp, getAgentDisplayName, getSkillsAgentName, resolveAddMcpAgentId, tryResolveAddMcpAgentId } from "../agents.js";
|
|
2
2
|
import { isAuthenticated } from "../auth.js";
|
|
3
|
+
import { installNeonMcpServer } from "../install_mcp.js";
|
|
3
4
|
//#region src/init/phases/mcp.ts
|
|
5
|
+
function skillsFollowUp(agent) {
|
|
6
|
+
if (agent && !getSkillsAgentName(agent)) return agent ? [
|
|
7
|
+
"--agent",
|
|
8
|
+
agent,
|
|
9
|
+
"--json"
|
|
10
|
+
] : ["--json"];
|
|
11
|
+
return [
|
|
12
|
+
"skills",
|
|
13
|
+
"--json",
|
|
14
|
+
...agent ? ["--agent", agent] : [],
|
|
15
|
+
"--install"
|
|
16
|
+
];
|
|
17
|
+
}
|
|
4
18
|
async function handleMcpPhase(options) {
|
|
5
19
|
const agentArgs = options.agent ? [
|
|
6
20
|
"--agent",
|
|
@@ -40,36 +54,53 @@ async function handleMcpPhase(options) {
|
|
|
40
54
|
};
|
|
41
55
|
const scope = options.scope ?? "global";
|
|
42
56
|
const mcpAgentId = resolveAddMcpAgentId(options.agent ?? "claude-code");
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
57
|
+
const installed = installNeonMcpServer({
|
|
58
|
+
agent: mcpAgentId,
|
|
59
|
+
scope,
|
|
60
|
+
cwd: process.cwd()
|
|
61
|
+
});
|
|
62
|
+
if (!installed.ok) {
|
|
63
|
+
if (installed.unsupported) {
|
|
64
|
+
if (scope === "project" && !agentSupportsProjectMcp(mcpAgentId) && agentSupportsHttpMcp(mcpAgentId)) return {
|
|
65
|
+
phase: "tooling",
|
|
66
|
+
status: "unsupported",
|
|
67
|
+
error: installed.error,
|
|
68
|
+
nextAction: {
|
|
69
|
+
type: "ask_user",
|
|
70
|
+
question: `${getAgentDisplayName(mcpAgentId)} does not support project-level MCP. Install the Neon MCP server globally instead?`,
|
|
71
|
+
options: [{
|
|
72
|
+
value: "global",
|
|
73
|
+
label: "Install globally"
|
|
74
|
+
}, {
|
|
75
|
+
value: "skip",
|
|
76
|
+
label: "Skip MCP install"
|
|
77
|
+
}],
|
|
78
|
+
responseMapping: {
|
|
79
|
+
global: { args: [
|
|
80
|
+
"mcp",
|
|
81
|
+
"--json",
|
|
82
|
+
...options.agent ? ["--agent", options.agent] : [],
|
|
83
|
+
"--install"
|
|
84
|
+
] },
|
|
85
|
+
skip: { args: skillsFollowUp(options.agent) }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
phase: "tooling",
|
|
91
|
+
status: "unsupported",
|
|
92
|
+
error: installed.error,
|
|
93
|
+
nextAction: {
|
|
94
|
+
type: "run_neon_init",
|
|
95
|
+
args: skillsFollowUp(options.agent)
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
phase: "tooling",
|
|
101
|
+
status: "failed",
|
|
102
|
+
error: installed.error,
|
|
103
|
+
nextAction: {
|
|
73
104
|
type: "ask_user",
|
|
74
105
|
question: "Failed to install the Neon MCP server automatically. Would you like to try again or configure it manually?",
|
|
75
106
|
options: [{
|
|
@@ -84,12 +115,28 @@ async function handleMcpPhase(options) {
|
|
|
84
115
|
"mcp",
|
|
85
116
|
"--json",
|
|
86
117
|
...options.agent ? ["--agent", options.agent] : [],
|
|
87
|
-
"--install"
|
|
118
|
+
"--install",
|
|
119
|
+
...scope === "project" ? ["--scope", "project"] : []
|
|
88
120
|
] },
|
|
89
121
|
manual: { args: agentArgs }
|
|
90
122
|
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const isCursor = mcpAgentId === "cursor" || options.agent?.toLowerCase() === "cursor";
|
|
127
|
+
const isClaudeCode = mcpAgentId === "claude-code" || options.agent?.toLowerCase() === "claude-code";
|
|
128
|
+
let enableNote = "";
|
|
129
|
+
if (isCursor && scope === "project") enableNote = " Cursor disables project-level MCP servers by default — open Cursor Settings > MCP and toggle the \"Neon\" server on.";
|
|
130
|
+
else if (isClaudeCode) enableNote = " Claude Code requires approval for newly added MCP servers. When prompted, approve the \"Neon\" server to enable it.";
|
|
131
|
+
return {
|
|
132
|
+
phase: "tooling",
|
|
133
|
+
status: "installed",
|
|
134
|
+
path: installed.path,
|
|
135
|
+
nextAction: {
|
|
136
|
+
type: "run_neon_init",
|
|
137
|
+
args: skillsFollowUp(options.agent)
|
|
138
|
+
},
|
|
139
|
+
message: `Installed Neon MCP server (${scope} scope) for ${mcpAgentId}.${enableNote}`
|
|
93
140
|
};
|
|
94
141
|
}
|
|
95
142
|
if (options.mcpConfigured === true) return {
|
|
@@ -97,12 +144,7 @@ async function handleMcpPhase(options) {
|
|
|
97
144
|
status: "mcp_configured",
|
|
98
145
|
nextAction: {
|
|
99
146
|
type: "run_neon_init",
|
|
100
|
-
args:
|
|
101
|
-
"skills",
|
|
102
|
-
"--json",
|
|
103
|
-
...options.agent ? ["--agent", options.agent] : [],
|
|
104
|
-
"--install"
|
|
105
|
-
]
|
|
147
|
+
args: skillsFollowUp(options.agent)
|
|
106
148
|
}
|
|
107
149
|
};
|
|
108
150
|
if (options.mcpConfigured === false) return {
|
|
@@ -116,10 +158,14 @@ async function handleMcpPhase(options) {
|
|
|
116
158
|
value: "defaults",
|
|
117
159
|
label: "Yes, install with default settings"
|
|
118
160
|
},
|
|
119
|
-
{
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
161
|
+
...(() => {
|
|
162
|
+
const known = options.agent ? tryResolveAddMcpAgentId(options.agent) : void 0;
|
|
163
|
+
if (known && !agentSupportsProjectMcp(known)) return [];
|
|
164
|
+
return [{
|
|
165
|
+
value: "project_scope",
|
|
166
|
+
label: "Yes, install for this project only"
|
|
167
|
+
}];
|
|
168
|
+
})(),
|
|
123
169
|
{
|
|
124
170
|
value: "skip",
|
|
125
171
|
label: "Skip for now"
|
|
@@ -141,12 +187,7 @@ async function handleMcpPhase(options) {
|
|
|
141
187
|
"--scope",
|
|
142
188
|
"project"
|
|
143
189
|
] },
|
|
144
|
-
skip: { args:
|
|
145
|
-
"skills",
|
|
146
|
-
"--json",
|
|
147
|
-
...options.agent ? ["--agent", options.agent] : [],
|
|
148
|
-
"--install"
|
|
149
|
-
] }
|
|
190
|
+
skip: { args: skillsFollowUp(options.agent) }
|
|
150
191
|
}
|
|
151
192
|
}
|
|
152
193
|
};
|