neon-init 0.14.0 → 0.15.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.
Files changed (111) hide show
  1. package/dist/cli.js +366 -30
  2. package/dist/cli.js.map +1 -1
  3. package/dist/index.d.ts +15 -3
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +207 -13
  6. package/dist/index.js.map +1 -1
  7. package/dist/interactive.d.ts +12 -0
  8. package/dist/interactive.d.ts.map +1 -0
  9. package/dist/interactive.js +495 -0
  10. package/dist/interactive.js.map +1 -0
  11. package/dist/lib/agents.d.ts +6 -1
  12. package/dist/lib/agents.d.ts.map +1 -1
  13. package/dist/lib/agents.js +62 -1
  14. package/dist/lib/agents.js.map +1 -1
  15. package/dist/lib/auth.d.ts +10 -3
  16. package/dist/lib/auth.d.ts.map +1 -1
  17. package/dist/lib/auth.js +19 -11
  18. package/dist/lib/auth.js.map +1 -1
  19. package/dist/lib/bootstrap.d.ts +30 -0
  20. package/dist/lib/bootstrap.d.ts.map +1 -0
  21. package/dist/lib/bootstrap.js +61 -0
  22. package/dist/lib/bootstrap.js.map +1 -0
  23. package/dist/lib/build-config.d.ts +5 -0
  24. package/dist/lib/build-config.d.ts.map +1 -0
  25. package/dist/lib/build-config.js +6 -0
  26. package/dist/lib/build-config.js.map +1 -0
  27. package/dist/lib/detect-agent.d.ts +22 -0
  28. package/dist/lib/detect-agent.d.ts.map +1 -0
  29. package/dist/lib/detect-agent.js +65 -0
  30. package/dist/lib/detect-agent.js.map +1 -0
  31. package/dist/lib/editors.d.ts.map +1 -1
  32. package/dist/lib/editors.js.map +1 -1
  33. package/dist/lib/extension.d.ts +11 -3
  34. package/dist/lib/extension.d.ts.map +1 -1
  35. package/dist/lib/extension.js +28 -7
  36. package/dist/lib/extension.js.map +1 -1
  37. package/dist/lib/inspect.d.ts +28 -0
  38. package/dist/lib/inspect.d.ts.map +1 -0
  39. package/dist/lib/inspect.js +190 -0
  40. package/dist/lib/inspect.js.map +1 -0
  41. package/dist/lib/install.d.ts +10 -4
  42. package/dist/lib/install.d.ts.map +1 -1
  43. package/dist/lib/install.js +37 -20
  44. package/dist/lib/install.js.map +1 -1
  45. package/dist/lib/neonctl.d.ts +32 -0
  46. package/dist/lib/neonctl.d.ts.map +1 -0
  47. package/dist/lib/neonctl.js +149 -0
  48. package/dist/lib/neonctl.js.map +1 -0
  49. package/dist/lib/phases/auth.d.ts +12 -0
  50. package/dist/lib/phases/auth.d.ts.map +1 -0
  51. package/dist/lib/phases/auth.js +188 -0
  52. package/dist/lib/phases/auth.js.map +1 -0
  53. package/dist/lib/phases/cleanup.d.ts +12 -0
  54. package/dist/lib/phases/cleanup.d.ts.map +1 -0
  55. package/dist/lib/phases/cleanup.js +29 -0
  56. package/dist/lib/phases/cleanup.js.map +1 -0
  57. package/dist/lib/phases/db.d.ts +17 -0
  58. package/dist/lib/phases/db.d.ts.map +1 -0
  59. package/dist/lib/phases/db.js +258 -0
  60. package/dist/lib/phases/db.js.map +1 -0
  61. package/dist/lib/phases/getting-started.d.ts +26 -0
  62. package/dist/lib/phases/getting-started.d.ts.map +1 -0
  63. package/dist/lib/phases/getting-started.js +195 -0
  64. package/dist/lib/phases/getting-started.js.map +1 -0
  65. package/dist/lib/phases/mcp.d.ts +15 -0
  66. package/dist/lib/phases/mcp.d.ts.map +1 -0
  67. package/dist/lib/phases/mcp.js +179 -0
  68. package/dist/lib/phases/mcp.js.map +1 -0
  69. package/dist/lib/phases/migrations.d.ts +14 -0
  70. package/dist/lib/phases/migrations.d.ts.map +1 -0
  71. package/dist/lib/phases/migrations.js +239 -0
  72. package/dist/lib/phases/migrations.js.map +1 -0
  73. package/dist/lib/phases/neon-auth.d.ts +13 -0
  74. package/dist/lib/phases/neon-auth.d.ts.map +1 -0
  75. package/dist/lib/phases/neon-auth.js +117 -0
  76. package/dist/lib/phases/neon-auth.js.map +1 -0
  77. package/dist/lib/phases/setup.d.ts +41 -0
  78. package/dist/lib/phases/setup.d.ts.map +1 -0
  79. package/dist/lib/phases/setup.js +689 -0
  80. package/dist/lib/phases/setup.js.map +1 -0
  81. package/dist/lib/phases/skills.d.ts +14 -0
  82. package/dist/lib/phases/skills.d.ts.map +1 -0
  83. package/dist/lib/phases/skills.js +80 -0
  84. package/dist/lib/phases/skills.js.map +1 -0
  85. package/dist/lib/phases/status.d.ts +10 -0
  86. package/dist/lib/phases/status.d.ts.map +1 -0
  87. package/dist/lib/phases/status.js +65 -0
  88. package/dist/lib/phases/status.js.map +1 -0
  89. package/dist/lib/resolve-context.d.ts +19 -0
  90. package/dist/lib/resolve-context.d.ts.map +1 -0
  91. package/dist/lib/resolve-context.js +112 -0
  92. package/dist/lib/resolve-context.js.map +1 -0
  93. package/dist/lib/route-command.d.ts +8 -0
  94. package/dist/lib/route-command.d.ts.map +1 -0
  95. package/dist/lib/route-command.js +195 -0
  96. package/dist/lib/route-command.js.map +1 -0
  97. package/dist/lib/skills.d.ts +20 -3
  98. package/dist/lib/skills.d.ts.map +1 -1
  99. package/dist/lib/skills.js +116 -12
  100. package/dist/lib/skills.js.map +1 -1
  101. package/dist/lib/types.d.ts +150 -1
  102. package/dist/lib/types.d.ts.map +1 -1
  103. package/dist/lib/vsix.d.ts +15 -0
  104. package/dist/lib/vsix.d.ts.map +1 -0
  105. package/dist/lib/vsix.js +91 -0
  106. package/dist/lib/vsix.js.map +1 -0
  107. package/dist/v2.d.ts +31 -0
  108. package/dist/v2.d.ts.map +1 -0
  109. package/dist/v2.js +147 -0
  110. package/dist/v2.js.map +1 -0
  111. package/package.json +7 -3
@@ -0,0 +1,689 @@
1
+ import { resolveAddMcpAgentId } from "../agents.js";
2
+ import { FALLBACK_TEMPLATES, fetchTemplates } from "../bootstrap.js";
3
+ import { detectIde, isCursorInstalled, isVSCodeInstalled } from "../detect-agent.js";
4
+ import { NEON_EXTENSION_ID, downloadVsix } from "../vsix.js";
5
+ import { findEditorCommand } from "../extension.js";
6
+ import { inspectProject } from "../inspect.js";
7
+ import { ensureNeonctl } from "../neonctl.js";
8
+ import { ensureSkillsUpToDate } from "../skills.js";
9
+ import { writeFileSync } from "node:fs";
10
+ import { resolve } from "node:path";
11
+ import { execa } from "execa";
12
+ import { unlink } from "node:fs/promises";
13
+ //#region src/lib/phases/setup.ts
14
+ /**
15
+ * Comprehensive setup phase: inspects repo state, collects user preferences,
16
+ * then batches all installation commands together.
17
+ *
18
+ * With --data JSON, the agent sends inspection results AND user preferences
19
+ * in a single call, so the CLI can go straight to installation.
20
+ */
21
+ async function handleSetupPhase(options) {
22
+ if (typeof options.features === "string") options.features = options.features.split(",").map((f) => f.trim());
23
+ if (options.template === "none") options.template = void 0;
24
+ if (options.template && !options.templateRequires) {
25
+ const selected = (await fetchTemplates()).find((t) => t.id === options.template);
26
+ if (selected) options.templateRequires = selected.requires;
27
+ }
28
+ if (options.execute) return executeBatchedInstallation(await mergeCliInspection(options));
29
+ if (options.mode === "defaults") {
30
+ const merged = await mergeCliInspection(options);
31
+ const shouldInstallExt = merged.installExtension ?? isVscodeBasedIde(merged);
32
+ return executeBatchedInstallation({
33
+ ...merged,
34
+ mcpScope: merged.mcpScope ?? "global",
35
+ skillsScope: merged.skillsScope ?? "project",
36
+ installExtension: shouldInstallExt
37
+ });
38
+ }
39
+ if (options.mode === "customize") {
40
+ if (options.mcpScope !== void 0 || options.skillsScope !== void 0) {
41
+ const merged = await mergeCliInspection(options);
42
+ const shouldInstallExt = merged.installExtension ?? isVscodeBasedIde(merged);
43
+ return executeBatchedInstallation({
44
+ ...merged,
45
+ mcpScope: merged.mcpScope ?? "global",
46
+ skillsScope: merged.skillsScope ?? "project",
47
+ installExtension: shouldInstallExt
48
+ });
49
+ }
50
+ return buildCustomizeQuestions(options);
51
+ }
52
+ if (options.mcpConfigured !== null && options.mcpConfigured !== void 0) return buildModeQuestion(options);
53
+ return buildBulkInspection(options);
54
+ }
55
+ function buildTemplatePreference(templates) {
56
+ return [{
57
+ id: "template",
58
+ question: "No application was detected in this directory. Would you like to scaffold a new project from a template?",
59
+ phase: "before_checks",
60
+ options: [...templates.map((t) => ({
61
+ value: t.id,
62
+ label: `${t.title} — ${t.description}`
63
+ })), {
64
+ value: "none",
65
+ label: "No thanks — continue without scaffolding"
66
+ }],
67
+ default: "none"
68
+ }];
69
+ }
70
+ async function buildBulkInspection(options) {
71
+ const hasApp = options.hasApp !== false;
72
+ const detectedIde = detectIde();
73
+ const installedEditors = [];
74
+ if (!detectedIde) {
75
+ if (isCursorInstalled()) installedEditors.push("cursor");
76
+ if (isVSCodeInstalled()) installedEditors.push("vscode");
77
+ }
78
+ let templatePreferences = [];
79
+ if (!hasApp) {
80
+ let templates = FALLBACK_TEMPLATES;
81
+ try {
82
+ const fetched = await fetchTemplates();
83
+ if (fetched && fetched.length > 0) templates = fetched;
84
+ } catch {}
85
+ templatePreferences = buildTemplatePreference(templates);
86
+ }
87
+ return {
88
+ phase: "setup",
89
+ status: hasApp ? "pending" : "bootstrap_needed",
90
+ detectedIde: detectedIde?.toLowerCase() ?? null,
91
+ installedEditors: installedEditors.length > 0 ? installedEditors : null,
92
+ nextAction: {
93
+ type: "agent_check",
94
+ instructions: [
95
+ "IMPORTANT: Do NOT summarize this response or ask the user for consent/confirmation before starting. Proceed IMMEDIATELY with the first userPreference question.",
96
+ "",
97
+ hasApp ? "Perform the agent checks listed above (MCP server status and your agent identity), then present each userPreference question to the user ONE AT A TIME, in order. Wait for the user's answer before showing the next question. Respect the `condition` field — only show a question if its condition is met." : "No application was detected in this directory. Ask the user if they'd like to scaffold a new project from a template (the `template` preference). Present ALL template options and the 'No thanks' option — do NOT auto-select even if there is only one template. If the user selects a template, the scaffolded template includes agent skills so skills installation will be skipped. If the user chooses 'none', continue with the remaining setup preferences normally. Then perform the agent checks and present the remaining preferences ONE AT A TIME.",
98
+ "",
99
+ "If the MCP server is already configured, tell the user and note that it will be kept up to date. IMPORTANT: If you find neon-postgres in skills-lock.json, you MUST verify the actual SKILL.md file exists on disk (e.g. .agents/skills/neon-postgres/SKILL.md or .cursor/skills/neon-postgres/SKILL.md). If the lock file references it but the file is missing, report skills as NOT installed. Only ask about scope/options for components that are NOT already configured.",
100
+ "",
101
+ "IMPORTANT (Cursor users): Cursor disables project-level MCP servers by default as a security measure. If the user is in Cursor and chooses project-level MCP scope, warn them that they will need to manually enable the Neon server in Cursor Settings > MCP after installation. Recommend global scope for Cursor to avoid this extra step.",
102
+ "",
103
+ "GROUPING: Preferences that share the same `group` field should be presented together in a single message (e.g. list all customize options at once and let the user answer them together). Preferences without a `group` must be asked individually.",
104
+ "",
105
+ detectedIde ? `The CLI has detected the IDE as: ${detectedIde.toLowerCase()}. Include this as the "ide" field in your reportBack data. IMPORTANT: The IDE and the agent are different — you may be Claude Code (agent) running inside Cursor (IDE). The extension installs into the IDE, so if the IDE is Cursor/VS Code/Windsurf, the extension IS applicable even if you are Claude Code.` : installedEditors.length > 0 ? `No IDE detected, but the following editors are installed: ${installedEditors.join(", ")}. The "installedEditors" field in this response lists them. If the user wants the extension installed, ask which editor to install it for and include that as the "ide" field in your reportBack data. If not, set "ide" to "none".` : `No IDE or supported editors detected. Set "ide" to "none" in your reportBack data.`,
106
+ "",
107
+ "After all questions are answered, call reportBack with a single --data JSON containing: agent, ide, mcpConfigured, and all preference answers. The CLI will inspect the project and merge results automatically."
108
+ ].join("\n"),
109
+ checks: [
110
+ {
111
+ id: "neonctl",
112
+ description: "The neonctl CLI will be installed or updated automatically (no action needed from the agent)",
113
+ lookFor: []
114
+ },
115
+ {
116
+ id: "mcp_server",
117
+ description: "Check if the Neon MCP server is already configured in your MCP server list",
118
+ lookFor: ["An MCP server entry named 'Neon' or with URL containing 'mcp.neon.tech'"]
119
+ },
120
+ {
121
+ id: "agent_type",
122
+ description: "Identify which coding agent is running this command",
123
+ lookFor: ["Determine which agent you are: cursor, claude-code, copilot, vscode, windsurf, codex, cline, gemini-cli, goose, opencode, or antigravity", "Report your own agent identifier — this is used to configure the MCP server for the correct tool"]
124
+ },
125
+ ...detectedIde ? [{
126
+ id: "extension_installed",
127
+ description: "Check if the Neon editor extension (databricks.neon-local-connect) is already installed in the IDE (NOT the agent — e.g. if you are Claude Code running inside Cursor, check Cursor's extensions)",
128
+ lookFor: ["Run the IDE's --list-extensions command or check installed extensions for 'databricks.neon-local-connect' or 'Neon Local Connect'", "If the extension is found, set installExtension to false in your reportBack data and SKIP the installExtension question"]
129
+ }] : []
130
+ ],
131
+ userPreferences: [
132
+ ...templatePreferences,
133
+ ...hasApp ? [{
134
+ id: "features",
135
+ question: "Which Neon features would you like to enable for this project?",
136
+ phase: "after_checks",
137
+ options: [{
138
+ value: "database",
139
+ label: "Database (always included)"
140
+ }, {
141
+ value: "database,auth",
142
+ label: "Database + Neon Auth (adds authentication via Neon)"
143
+ }],
144
+ default: "database",
145
+ context: "Database connectivity is always set up. Neon Auth adds user authentication powered by Neon. More features (Functions, AI Gateway, Object Storage) will be available soon."
146
+ }] : [],
147
+ {
148
+ id: "mode",
149
+ question: "Use default settings or customize?",
150
+ phase: "after_checks",
151
+ options: [{
152
+ value: "defaults",
153
+ label: hasApp ? "Use defaults (neonctl CLI, MCP: global, skills: project-level, extension if applicable — already-configured components will be skipped)" : "Use defaults (neonctl CLI, MCP: global, extension if applicable — skills included in template)"
154
+ }, {
155
+ value: "customize",
156
+ label: "Customize installation settings"
157
+ }],
158
+ default: "defaults"
159
+ },
160
+ {
161
+ id: "mcpScope",
162
+ question: "Where should the Neon MCP server be configured?",
163
+ context: "SKIP this question entirely if the mcp_server check found it is already configured. Only ask if MCP is NOT yet configured. NOTE: Cursor disables project-level MCP servers by default — if the user is in Cursor, recommend global scope or warn that they will need to manually enable the server in Cursor Settings > MCP.",
164
+ phase: "after_checks",
165
+ options: [
166
+ {
167
+ value: "global",
168
+ label: "Global (available in all projects)"
169
+ },
170
+ {
171
+ value: "project",
172
+ label: "Project-level (scoped to this project only)"
173
+ },
174
+ {
175
+ value: "none",
176
+ label: "Skip — do not install the MCP server"
177
+ }
178
+ ],
179
+ default: "global",
180
+ condition: {
181
+ preferenceId: "mode",
182
+ equals: "customize"
183
+ },
184
+ group: "customize"
185
+ },
186
+ ...hasApp ? [{
187
+ id: "skillsScope",
188
+ question: "Where should Neon agent skills be installed?",
189
+ context: "Always ask this question — the CLI handles skill detection and freshness automatically.",
190
+ phase: "after_checks",
191
+ options: [{
192
+ value: "global",
193
+ label: "Global (available in all projects)"
194
+ }, {
195
+ value: "project",
196
+ label: "Project-level (scoped to this project only)"
197
+ }],
198
+ default: "project",
199
+ condition: {
200
+ preferenceId: "mode",
201
+ equals: "customize"
202
+ },
203
+ group: "customize"
204
+ }] : [],
205
+ {
206
+ id: "installExtension",
207
+ question: "Install the Neon editor extension for local database browsing?",
208
+ phase: "after_checks",
209
+ options: [{
210
+ value: "true",
211
+ label: "Yes"
212
+ }, {
213
+ value: "false",
214
+ label: "No"
215
+ }],
216
+ default: "true",
217
+ context: "The extension installs into the IDE, NOT the agent. If the CLI detected the IDE (see detectedIde field), use that — e.g. Claude Code running inside Cursor means the IDE is Cursor and the extension IS applicable. Only applicable for VS Code-based IDEs (VS Code, Cursor, Windsurf). SKIP this question if the user is NOT in a VS Code-based IDE, or if the extension_installed check found it is already installed. Set installExtension to false in reportBack if skipped.",
218
+ condition: {
219
+ preferenceId: "mode",
220
+ equals: "customize"
221
+ },
222
+ group: "customize"
223
+ }
224
+ ],
225
+ reportBack: {
226
+ type: "run_neon_init",
227
+ args: [
228
+ "setup",
229
+ "--json",
230
+ "--data",
231
+ `<json: { agent: string, ide: string, mcpConfigured: bool, mode: string, mcpScope?: 'global'|'project'|'none', skillsScope?: string, installExtension?: bool${hasApp ? ", features?: string" : ", template: string"} }>`
232
+ ]
233
+ }
234
+ }
235
+ };
236
+ }
237
+ function buildModeQuestion(options) {
238
+ const agentArgs = options.agent ? ["--agent", options.agent] : [];
239
+ const findings = [];
240
+ if (options.mcpConfigured) findings.push("Neon MCP server is already configured (will be upgraded to evergreen)");
241
+ else findings.push("Neon MCP server is not configured");
242
+ if (options.connectionString) findings.push("A Neon connection string was found in the project");
243
+ else findings.push("No Neon connection string found — will need to add one");
244
+ if (options.framework && options.framework !== "none") findings.push(`Framework detected: ${options.framework}`);
245
+ if (options.orm && options.orm !== "none") findings.push(`ORM detected: ${options.orm}`);
246
+ if (options.migrationTool && options.migrationTool !== "none") findings.push(`Migration tool detected: ${options.migrationTool}`);
247
+ if (options.isVscodeIde) findings.push("VS Code-based IDE detected — Neon extension available");
248
+ const inspectionArgs = buildInspectionArgs(options);
249
+ const defaultsParts = ["neonctl CLI"];
250
+ if (!options.mcpConfigured) defaultsParts.push("MCP global");
251
+ defaultsParts.push("skills in project");
252
+ if (options.isVscodeIde) defaultsParts.push("install extension");
253
+ const defaultsLabel = defaultsParts.length > 0 ? `Use defaults (${defaultsParts.join(", ")})` : "Use defaults";
254
+ return {
255
+ phase: "setup",
256
+ status: "preferences_needed",
257
+ inspection: {
258
+ mcpConfigured: options.mcpConfigured,
259
+ connectionString: options.connectionString,
260
+ framework: options.framework,
261
+ orm: options.orm,
262
+ migrationTool: options.migrationTool,
263
+ migrationDir: options.migrationDir,
264
+ isVscodeIde: options.isVscodeIde
265
+ },
266
+ nextAction: {
267
+ type: "ask_user",
268
+ question: "Use default settings or customize?",
269
+ options: [{
270
+ value: "defaults",
271
+ label: defaultsLabel
272
+ }, {
273
+ value: "customize",
274
+ label: "Customize installation settings"
275
+ }],
276
+ context: `Project inspection results:\n${findings.map((f) => `- ${f}`).join("\n")}`,
277
+ responseMapping: {
278
+ defaults: { args: [
279
+ "setup",
280
+ "--json",
281
+ ...agentArgs,
282
+ ...inspectionArgs,
283
+ "--mode",
284
+ "defaults"
285
+ ] },
286
+ customize: { args: [
287
+ "setup",
288
+ "--json",
289
+ ...agentArgs,
290
+ ...inspectionArgs,
291
+ "--mode",
292
+ "customize"
293
+ ] }
294
+ }
295
+ }
296
+ };
297
+ }
298
+ function buildCustomizeQuestions(options) {
299
+ const agentArgs = options.agent ? ["--agent", options.agent] : [];
300
+ const inspectionArgs = buildInspectionArgs(options);
301
+ const mcpScopes = !options.mcpConfigured ? [
302
+ "global",
303
+ "project",
304
+ "none"
305
+ ] : ["skip"];
306
+ const skillsScopes = ["global", "project"];
307
+ const extOptions = options.isVscodeIde ? ["ext", "noext"] : ["ext"];
308
+ const customOptions = [];
309
+ for (const mcp of mcpScopes) for (const skills of skillsScopes) for (const ext of extOptions) {
310
+ const parts = [];
311
+ if (mcp === "none") parts.push("Skip MCP");
312
+ else if (mcp !== "skip") parts.push(`MCP: ${mcp}`);
313
+ if (skills !== "skip") parts.push(`Skills: ${skills === "project" ? "project-level" : skills}`);
314
+ if (options.isVscodeIde) parts.push(ext === "ext" ? "Install extension" : "Skip extension");
315
+ customOptions.push({
316
+ value: `${mcp}_${skills}_${ext}`,
317
+ label: parts.join(", ")
318
+ });
319
+ }
320
+ const responseMapping = {};
321
+ for (const opt of customOptions) {
322
+ const parts = opt.value.split("_");
323
+ const mcpScope = parts[0] === "skip" ? "global" : parts[0];
324
+ const skillsScope = parts[1] === "skip" ? "project" : parts[1];
325
+ const installExt = parts[2] === "ext";
326
+ responseMapping[opt.value] = { args: [
327
+ "setup",
328
+ "--json",
329
+ ...agentArgs,
330
+ ...inspectionArgs,
331
+ "--mode",
332
+ "customize",
333
+ "--mcp-scope",
334
+ mcpScope,
335
+ "--skills-scope",
336
+ skillsScope,
337
+ ...options.isVscodeIde ? ["--install-extension", installExt ? "true" : "false"] : [],
338
+ "--execute"
339
+ ] };
340
+ }
341
+ return {
342
+ phase: "setup",
343
+ status: "customizing",
344
+ nextAction: {
345
+ type: "ask_user",
346
+ question: "Choose your installation configuration:",
347
+ options: customOptions,
348
+ context: "Global scope means settings apply across all your projects. Project-level means settings are scoped to this project only." + (options.mcpConfigured ? "\nSince Neon tools are already installed, they will be upgraded to the latest evergreen version." : "") + (isCursorAgent(options) ? "\nNote: Cursor disables project-level MCP servers by default. If you choose project scope, you will need to manually enable the Neon server in Cursor Settings > MCP." : ""),
349
+ responseMapping
350
+ }
351
+ };
352
+ }
353
+ /**
354
+ * Executes the batched installation of MCP server, skills, and extension.
355
+ * Runs commands directly in the CLI process — the agent does NOT run these.
356
+ * Returns results and chains to the getting-started phase.
357
+ */
358
+ async function executeBatchedInstallation(options) {
359
+ const mcpScope = options.mcpScope ?? "global";
360
+ const agentId = options.agent ?? "claude-code";
361
+ const mcpAgentId = resolveAddMcpAgentId(agentId);
362
+ const installExt = options.installExtension === true;
363
+ const results = [];
364
+ const isBootstrap = !!options.template;
365
+ if (isBootstrap && options.template) try {
366
+ await execa("npx", [
367
+ "neonctl",
368
+ "bootstrap",
369
+ ".",
370
+ "--template",
371
+ options.template,
372
+ "--force"
373
+ ], {
374
+ stdio: "pipe",
375
+ timeout: 12e4
376
+ });
377
+ results.push({
378
+ id: "bootstrap",
379
+ description: `Scaffolded project from template "${options.template}"`,
380
+ status: "success"
381
+ });
382
+ if (options.templateRequires) {
383
+ const neonContextPath = resolve(process.cwd(), ".neon");
384
+ const context = { _init: { features: options.templateRequires } };
385
+ writeFileSync(neonContextPath, `${JSON.stringify(context, null, 2)}\n`);
386
+ }
387
+ } catch (err) {
388
+ results.push({
389
+ id: "bootstrap",
390
+ description: `Failed to scaffold project from template "${options.template}"`,
391
+ status: "failed",
392
+ error: err instanceof Error ? err.message : "Unknown error"
393
+ });
394
+ }
395
+ const neonctlResult = await ensureNeonctl();
396
+ switch (neonctlResult.status) {
397
+ case "already_current":
398
+ results.push({
399
+ id: "neonctl",
400
+ description: `neonctl CLI is up to date (v${neonctlResult.version})`,
401
+ status: "success"
402
+ });
403
+ break;
404
+ case "installed":
405
+ results.push({
406
+ id: "neonctl",
407
+ description: `Installed neonctl CLI (v${neonctlResult.version})`,
408
+ status: "success"
409
+ });
410
+ break;
411
+ case "updated":
412
+ results.push({
413
+ id: "neonctl",
414
+ description: `Updated neonctl CLI to v${neonctlResult.version}`,
415
+ status: "success"
416
+ });
417
+ break;
418
+ case "failed":
419
+ results.push({
420
+ id: "neonctl",
421
+ description: "Failed to install neonctl CLI",
422
+ status: "failed",
423
+ error: neonctlResult.error
424
+ });
425
+ break;
426
+ }
427
+ const isCursor = mcpAgentId === "cursor" || options.ide?.toLowerCase() === "cursor" || options.agent?.toLowerCase() === "cursor";
428
+ if (mcpScope === "none") results.push({
429
+ id: "skip_mcp",
430
+ description: "Neon MCP server installation skipped by user",
431
+ status: "success"
432
+ });
433
+ else if (options.mcpConfigured) results.push({
434
+ id: "skip_mcp",
435
+ description: "Neon MCP server already configured",
436
+ status: "success"
437
+ });
438
+ else {
439
+ const mcpArgs = [
440
+ "-y",
441
+ "add-mcp",
442
+ "https://mcp.neon.tech/mcp",
443
+ ...mcpScope === "global" ? ["-g"] : [],
444
+ "-n",
445
+ "Neon",
446
+ "-y",
447
+ "-a",
448
+ mcpAgentId
449
+ ];
450
+ try {
451
+ await execa("npx", mcpArgs, {
452
+ stdio: "pipe",
453
+ timeout: 6e4
454
+ });
455
+ results.push({
456
+ id: "install_mcp",
457
+ description: `Installed Neon MCP server (${mcpScope} scope)`,
458
+ status: "success"
459
+ });
460
+ const isClaudeCode = mcpAgentId === "claude-code" || options.agent?.toLowerCase() === "claude-code";
461
+ if (isCursor && mcpScope === "project") results.push({
462
+ id: "enable_mcp",
463
+ description: "Cursor disables project-level MCP servers by default. Open Cursor Settings > MCP and toggle the \"Neon\" server on.",
464
+ status: "success",
465
+ manualAction: true
466
+ });
467
+ else if (isClaudeCode) results.push({
468
+ id: "enable_mcp",
469
+ description: "Claude Code requires approval for newly added MCP servers. When prompted, approve the \"Neon\" MCP server to enable it. You can check MCP server status with /mcp in Claude Code.",
470
+ status: "success",
471
+ manualAction: true
472
+ });
473
+ } catch (err) {
474
+ results.push({
475
+ id: "install_mcp",
476
+ description: "Failed to install Neon MCP server",
477
+ status: "failed",
478
+ error: err instanceof Error ? err.message : "Unknown error"
479
+ });
480
+ }
481
+ }
482
+ if (isBootstrap) results.push({
483
+ id: "install_skills",
484
+ description: "Neon agent skills included in template",
485
+ status: "success"
486
+ });
487
+ else {
488
+ const skillsOk = await ensureSkillsUpToDate(agentId, options.skillsScope ?? "project");
489
+ results.push({
490
+ id: "install_skills",
491
+ description: skillsOk ? "Neon agent skills installed" : "Failed to install Neon agent skills",
492
+ status: skillsOk ? "success" : "failed"
493
+ });
494
+ }
495
+ if (installExt) {
496
+ const extResult = await installExtensionForIde(options.ide ?? agentId);
497
+ results.push(extResult);
498
+ }
499
+ if (!isBootstrap && options.features && options.features.length > 0) {
500
+ const neonContextPath = resolve(process.cwd(), ".neon");
501
+ const context = { _init: { features: options.features } };
502
+ writeFileSync(neonContextPath, `${JSON.stringify(context, null, 2)}\n`);
503
+ }
504
+ const allSucceeded = results.every((r) => r.status === "success");
505
+ const gettingStartedData = {};
506
+ if (options.connectionString) gettingStartedData.hasConnectionString = true;
507
+ if (options.framework) gettingStartedData.framework = options.framework;
508
+ if (options.orm) gettingStartedData.orm = options.orm;
509
+ if (options.migrationTool) gettingStartedData.migrationTool = options.migrationTool;
510
+ if (options.migrationDir) gettingStartedData.migrationDir = options.migrationDir;
511
+ const resolvedFeatures = options.templateRequires ?? options.features;
512
+ if (resolvedFeatures && resolvedFeatures.length > 0) gettingStartedData.features = resolvedFeatures;
513
+ if (isBootstrap) gettingStartedData.preview = true;
514
+ const gettingStartedArgs = [
515
+ "getting-started",
516
+ "--json",
517
+ "--data",
518
+ JSON.stringify(gettingStartedData)
519
+ ];
520
+ return {
521
+ phase: "setup",
522
+ status: allSucceeded ? "installed" : "partial",
523
+ results,
524
+ nextAction: {
525
+ type: "run_neon_init",
526
+ args: gettingStartedArgs
527
+ }
528
+ };
529
+ }
530
+ function buildInspectionArgs(options) {
531
+ const args = [];
532
+ if (options.mcpConfigured !== null && options.mcpConfigured !== void 0) args.push("--mcp-configured", options.mcpConfigured ? "true" : "false");
533
+ if (options.connectionString !== null && options.connectionString !== void 0) args.push("--connection-string", options.connectionString ? "true" : "false");
534
+ if (options.framework) args.push("--framework", options.framework);
535
+ if (options.orm) args.push("--orm", options.orm);
536
+ if (options.migrationTool) args.push("--migration-tool", options.migrationTool);
537
+ if (options.migrationDir) args.push("--migration-dir", options.migrationDir);
538
+ if (options.isVscodeIde !== null && options.isVscodeIde !== void 0) args.push("--is-vscode-ide", options.isVscodeIde ? "true" : "false");
539
+ return args;
540
+ }
541
+ /**
542
+ * Fills in missing filesystem inspection fields by running inspectProject().
543
+ * Agent-reported data (mcpConfigured, agent, mode, scopes) is preserved.
544
+ * CLI-detectable fields (framework, orm, migrations, connectionString, isVscodeIde)
545
+ * are filled in only if not already present.
546
+ */
547
+ async function mergeCliInspection(options) {
548
+ if (options.framework !== void 0 && options.orm !== void 0) return options;
549
+ const inspection = await inspectProject([
550
+ {
551
+ id: "connection_string",
552
+ description: "",
553
+ lookFor: []
554
+ },
555
+ {
556
+ id: "project_stack",
557
+ description: "",
558
+ lookFor: []
559
+ },
560
+ {
561
+ id: "migrations",
562
+ description: "",
563
+ lookFor: []
564
+ },
565
+ {
566
+ id: "ide_type",
567
+ description: "",
568
+ lookFor: []
569
+ }
570
+ ]);
571
+ const ide = options.ide?.toLowerCase().replace(/\s+/g, "-") || detectIde()?.toLowerCase().replace(/\s+/g, "-") || void 0;
572
+ return {
573
+ ...options,
574
+ ide,
575
+ connectionString: options.connectionString ?? inspection.connectionString,
576
+ framework: options.framework ?? inspection.framework,
577
+ orm: options.orm ?? inspection.orm,
578
+ migrationTool: options.migrationTool ?? inspection.migrationTool,
579
+ migrationDir: options.migrationDir ?? inspection.migrationDir,
580
+ isVscodeIde: options.isVscodeIde ?? inspection.isVscodeIde
581
+ };
582
+ }
583
+ /**
584
+ * Checks whether the user is in a VS Code-based IDE that supports extensions.
585
+ * Uses agent-reported `ide` field first, then falls back to `isVscodeIde` from inspection.
586
+ */
587
+ function isCursorAgent(options) {
588
+ if (options.ide?.toLowerCase() === "cursor") return true;
589
+ if (options.agent?.toLowerCase() === "cursor") return true;
590
+ return false;
591
+ }
592
+ function isVscodeBasedIde(options) {
593
+ if (options.ide) {
594
+ const ide = options.ide.toLowerCase();
595
+ return ide === "cursor" || ide === "vscode" || ide === "vs-code" || ide === "windsurf";
596
+ }
597
+ return options.isVscodeIde === true;
598
+ }
599
+ /**
600
+ * Resolves which IDE to install the extension for.
601
+ * Accepts the agent-reported IDE value (preferred), the agent ID, or
602
+ * falls back to env-var detection.
603
+ */
604
+ function resolveEditorForExtension(ideOrAgentId) {
605
+ switch (ideOrAgentId.toLowerCase()) {
606
+ case "cursor": return "Cursor";
607
+ case "vscode":
608
+ case "vs-code":
609
+ case "copilot":
610
+ case "github-copilot":
611
+ case "github-copilot-cli": return "VS Code";
612
+ default: break;
613
+ }
614
+ const ide = detectIde();
615
+ if (ide === "Cursor" || ide === "VS Code") return ide;
616
+ return null;
617
+ }
618
+ const MANUAL_INSTALL_MSG = `Search for "Neon" in the extensions panel (Cmd+Shift+X / Ctrl+Shift+X) and install "Neon Local Connect" by Databricks.`;
619
+ /**
620
+ * Installs the Neon extension for the detected IDE.
621
+ *
622
+ * Uses env-var detection to determine the IDE (not the agent identity),
623
+ * so Claude Code running in Cursor correctly installs for Cursor.
624
+ *
625
+ * Strategy:
626
+ * 1. Try `<editor> --install-extension <id>` directly (uses editor's configured marketplace)
627
+ * 2. If that fails, download .vsix (from proxy or Open VSX) and install via local file
628
+ * 3. If all else fails: return manual install instructions
629
+ */
630
+ async function installExtensionForIde(agentId) {
631
+ const editorType = resolveEditorForExtension(agentId);
632
+ if (!editorType) return {
633
+ id: "install_extension",
634
+ description: MANUAL_INSTALL_MSG,
635
+ status: "success",
636
+ manualAction: true
637
+ };
638
+ const editorCmd = await findEditorCommand(editorType);
639
+ if (!editorCmd) return {
640
+ id: "install_extension",
641
+ description: MANUAL_INSTALL_MSG,
642
+ status: "success",
643
+ manualAction: true
644
+ };
645
+ try {
646
+ await execa(editorCmd, ["--install-extension", NEON_EXTENSION_ID], {
647
+ stdio: "pipe",
648
+ timeout: 6e4
649
+ });
650
+ return {
651
+ id: "install_extension",
652
+ description: `Installed Neon extension for ${editorType}`,
653
+ status: "success"
654
+ };
655
+ } catch {}
656
+ const vsixPath = await downloadVsix();
657
+ if (!vsixPath) return {
658
+ id: "install_extension",
659
+ description: MANUAL_INSTALL_MSG,
660
+ status: "success",
661
+ manualAction: true
662
+ };
663
+ try {
664
+ await execa(editorCmd, ["--install-extension", vsixPath], {
665
+ stdio: "pipe",
666
+ timeout: 6e4
667
+ });
668
+ return {
669
+ id: "install_extension",
670
+ description: `Installed Neon extension for ${editorType}`,
671
+ status: "success"
672
+ };
673
+ } catch {
674
+ return {
675
+ id: "install_extension",
676
+ description: MANUAL_INSTALL_MSG,
677
+ status: "success",
678
+ manualAction: true
679
+ };
680
+ } finally {
681
+ try {
682
+ await unlink(vsixPath);
683
+ } catch {}
684
+ }
685
+ }
686
+ //#endregion
687
+ export { handleSetupPhase };
688
+
689
+ //# sourceMappingURL=setup.js.map