neon 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -5
- package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
- package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
- package/dist/_chunks/credentials-MYdHdKah.js +188 -0
- package/dist/_chunks/env-NbA61JR3.js +585 -0
- package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
- package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
- package/dist/_chunks/profiles-Ir29rqns.js +217 -0
- package/dist/_chunks/psql-DWH-kc69.js +2169 -0
- package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +255 -305
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +615 -846
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +2 -446
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +2 -51
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +728 -984
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +2 -2193
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +2 -1148
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +260 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +2 -1253
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1303
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +2 -846
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +2 -2030
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +8 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +39 -45
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +11 -6
- package/dist/_shared/auth_selection.js +0 -86
- package/dist/_shared/credentials.js +0 -209
- package/dist/_shared/env-core/env.js +0 -558
- package/dist/_shared/env-core/reuse-secrets.js +0 -223
- package/dist/_shared/paths.js +0 -148
- package/dist/_shared/profiles.js +0 -276
- package/dist/_shared/secure_file.js +0 -43
|
@@ -1,729 +1,572 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
import { execa } from "execa";
|
|
1
|
+
import { FALLBACK_TEMPLATES, fetchTemplates, findTemplate, scaffoldTemplate } from "../bootstrap.js";
|
|
2
|
+
import { detectIde, isCursorInstalled, isVSCodeInstalled } from "../detect_agent.js";
|
|
5
3
|
import { resolveAddMcpAgentId } from "../agents.js";
|
|
6
|
-
import {
|
|
7
|
-
import { detectIde, isCursorInstalled, isVSCodeInstalled, } from "../detect_agent.js";
|
|
4
|
+
import { NEON_EXTENSION_ID, downloadVsix } from "../vsix.js";
|
|
8
5
|
import { findEditorCommand } from "../extension.js";
|
|
9
6
|
import { inspectProject } from "../inspect.js";
|
|
10
7
|
import { ensureNeonctl } from "../neonctl.js";
|
|
11
8
|
import { ensureSkillsUpToDate } from "../skills.js";
|
|
12
|
-
import {
|
|
9
|
+
import { writeFileSync } from "node:fs";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { unlink } from "node:fs/promises";
|
|
12
|
+
import { execa } from "execa";
|
|
13
|
+
//#region src/init/phases/setup.ts
|
|
13
14
|
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
options.mode !== "customize" &&
|
|
50
|
-
options.mode !== "custom") {
|
|
51
|
-
const merged = await mergeCliInspection(options);
|
|
52
|
-
return executeBatchedInstallation({
|
|
53
|
-
...merged,
|
|
54
|
-
mcpScope: merged.mcpScope ?? "global",
|
|
55
|
-
skillsScope: merged.skillsScope ?? "project",
|
|
56
|
-
installExtension: false,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
// User chose "customize" (also accept "custom" — agents sometimes truncate)
|
|
60
|
-
if (options.mode === "customize" || options.mode === "custom") {
|
|
61
|
-
const merged = await mergeCliInspection(options);
|
|
62
|
-
const shouldInstallExt = merged.installExtension ?? isVscodeBasedIde(merged);
|
|
63
|
-
return executeBatchedInstallation({
|
|
64
|
-
...merged,
|
|
65
|
-
mcpScope: merged.mcpScope ?? "global",
|
|
66
|
-
skillsScope: merged.skillsScope ?? "project",
|
|
67
|
-
installExtension: shouldInstallExt,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
// Default: send inspection checks with user preferences (all in one response)
|
|
71
|
-
return buildBulkInspection(options);
|
|
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
|
+
const templateWasAnswered = options.template !== void 0;
|
|
24
|
+
if (options.template === "none") options.template = void 0;
|
|
25
|
+
if (options.template && !options.templateRequires) {
|
|
26
|
+
const selected = (await fetchTemplates()).find((t) => t.id === options.template);
|
|
27
|
+
if (selected) options.templateRequires = selected.requires;
|
|
28
|
+
}
|
|
29
|
+
if (options.execute) return executeBatchedInstallation(await mergeCliInspection(options));
|
|
30
|
+
if ((options.mode || templateWasAnswered) && options.mode !== "customize" && options.mode !== "custom") {
|
|
31
|
+
const merged = await mergeCliInspection(options);
|
|
32
|
+
return executeBatchedInstallation({
|
|
33
|
+
...merged,
|
|
34
|
+
mcpScope: merged.mcpScope ?? "global",
|
|
35
|
+
skillsScope: merged.skillsScope ?? "project",
|
|
36
|
+
installExtension: false
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (options.mode === "customize" || options.mode === "custom") {
|
|
40
|
+
const merged = await mergeCliInspection(options);
|
|
41
|
+
const shouldInstallExt = merged.installExtension ?? isVscodeBasedIde(merged);
|
|
42
|
+
return executeBatchedInstallation({
|
|
43
|
+
...merged,
|
|
44
|
+
mcpScope: merged.mcpScope ?? "global",
|
|
45
|
+
skillsScope: merged.skillsScope ?? "project",
|
|
46
|
+
installExtension: shouldInstallExt
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return buildBulkInspection(options);
|
|
72
50
|
}
|
|
73
51
|
function buildTemplatePreference(templates) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
value: "none",
|
|
91
|
-
label: "No thanks — continue without scaffolding",
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
default: "none",
|
|
95
|
-
},
|
|
96
|
-
];
|
|
52
|
+
return [{
|
|
53
|
+
id: "template",
|
|
54
|
+
question: "No application was detected in this directory. Would you like to scaffold a new project from a template?",
|
|
55
|
+
phase: "before_checks",
|
|
56
|
+
options: [...templates.map((t) => {
|
|
57
|
+
const tools = t.tools && t.tools.length > 0 ? ` (${t.tools.join(", ")})` : "";
|
|
58
|
+
return {
|
|
59
|
+
value: t.id,
|
|
60
|
+
label: `${t.title}${tools} — ${t.description}`
|
|
61
|
+
};
|
|
62
|
+
}), {
|
|
63
|
+
value: "none",
|
|
64
|
+
label: "No thanks — continue without scaffolding"
|
|
65
|
+
}],
|
|
66
|
+
default: "none"
|
|
67
|
+
}];
|
|
97
68
|
}
|
|
98
69
|
async function buildBulkInspection(options) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
condition: {
|
|
287
|
-
preferenceId: "mode",
|
|
288
|
-
equals: "customize",
|
|
289
|
-
},
|
|
290
|
-
group: "customize",
|
|
291
|
-
},
|
|
292
|
-
]
|
|
293
|
-
: []),
|
|
294
|
-
{
|
|
295
|
-
id: "installExtension",
|
|
296
|
-
question: "Install the Neon editor extension for local database browsing?",
|
|
297
|
-
phase: "after_checks",
|
|
298
|
-
options: [
|
|
299
|
-
{ value: "true", label: "Yes" },
|
|
300
|
-
{ value: "false", label: "No" },
|
|
301
|
-
],
|
|
302
|
-
default: "true",
|
|
303
|
-
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.",
|
|
304
|
-
condition: { preferenceId: "mode", equals: "customize" },
|
|
305
|
-
group: "customize",
|
|
306
|
-
},
|
|
307
|
-
],
|
|
308
|
-
reportBack: {
|
|
309
|
-
type: "run_neon_init",
|
|
310
|
-
args: [
|
|
311
|
-
"setup",
|
|
312
|
-
"--json",
|
|
313
|
-
"--data",
|
|
314
|
-
(() => {
|
|
315
|
-
const partialScope = String(options.skillsScope ?? "").replace("-partial", "");
|
|
316
|
-
const hasPartial = String(options.skillsScope ?? "").includes("partial");
|
|
317
|
-
const previewFlag = options.preview
|
|
318
|
-
? ", preview: true"
|
|
319
|
-
: "";
|
|
320
|
-
const needsMcpChoice = !options.mcpConfigured;
|
|
321
|
-
const needsSkillsChoice = !options.skillsInstalled && !hasPartial;
|
|
322
|
-
const hasModeQuestion = needsMcpChoice || needsSkillsChoice;
|
|
323
|
-
const modeField = hasModeQuestion
|
|
324
|
-
? ", mode: string"
|
|
325
|
-
: "";
|
|
326
|
-
const mcpField = hasModeQuestion
|
|
327
|
-
? ", mcpScope?: 'global'|'project'|'none'"
|
|
328
|
-
: "";
|
|
329
|
-
const skillsField = needsSkillsChoice
|
|
330
|
-
? ", skillsScope?: string"
|
|
331
|
-
: "";
|
|
332
|
-
const extField = hasModeQuestion
|
|
333
|
-
? ", installExtension?: bool"
|
|
334
|
-
: "";
|
|
335
|
-
const prefilledSkills = options.skillsInstalled || hasPartial
|
|
336
|
-
? `, skillsScope: "${options.skillsInstalled ? options.skillsScope || "project" : partialScope}"`
|
|
337
|
-
: skillsField;
|
|
338
|
-
return `<json: { agent: string, ide: string, mcpConfigured: bool${prefilledSkills}${previewFlag}${modeField}${mcpField}${extField}${hasApp ? ", features?: string" : ", template: string"} }>`;
|
|
339
|
-
})(),
|
|
340
|
-
],
|
|
341
|
-
},
|
|
342
|
-
},
|
|
343
|
-
};
|
|
70
|
+
const hasApp = options.hasApp !== false;
|
|
71
|
+
const detectedIde = detectIde();
|
|
72
|
+
const installedEditors = [];
|
|
73
|
+
if (!detectedIde) {
|
|
74
|
+
if (isCursorInstalled()) installedEditors.push("cursor");
|
|
75
|
+
if (isVSCodeInstalled()) installedEditors.push("vscode");
|
|
76
|
+
}
|
|
77
|
+
let templatePreferences = [];
|
|
78
|
+
if (!hasApp) {
|
|
79
|
+
let templates = FALLBACK_TEMPLATES;
|
|
80
|
+
try {
|
|
81
|
+
const fetched = await fetchTemplates();
|
|
82
|
+
if (fetched && fetched.length > 0) templates = fetched;
|
|
83
|
+
} catch {}
|
|
84
|
+
templatePreferences = buildTemplatePreference(templates);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
phase: "setup",
|
|
88
|
+
status: hasApp ? "pending" : "bootstrap_needed",
|
|
89
|
+
detectedIde: detectedIde?.toLowerCase() ?? null,
|
|
90
|
+
installedEditors: installedEditors.length > 0 ? installedEditors : null,
|
|
91
|
+
mcpConfigured: options.mcpConfigured ?? null,
|
|
92
|
+
mcpScope: options.mcpScope ?? null,
|
|
93
|
+
skillsInstalled: options.skillsInstalled ?? null,
|
|
94
|
+
skillsScope: options.skillsScope ?? null,
|
|
95
|
+
nextAction: {
|
|
96
|
+
type: "agent_check",
|
|
97
|
+
instructions: [
|
|
98
|
+
"IMPORTANT: Do NOT summarize this response or ask the user for consent/confirmation before starting. Proceed IMMEDIATELY with the first userPreference question.",
|
|
99
|
+
"",
|
|
100
|
+
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.",
|
|
101
|
+
"",
|
|
102
|
+
`The CLI has pre-detected the following from the filesystem: MCP server: ${options.mcpConfigured ? `configured (${options.mcpScope})` : "not configured"}. Agent skills: ${options.skillsInstalled ? `installed (${options.skillsScope})` : String(options.skillsScope ?? "").includes("partial") ? `partially installed (${options.skillsScope}) — missing skills will be auto-installed to the same scope` : "not installed"}. Report these findings to the user before asking preferences. Only ask about scope/options for components that are NOT already configured. Do NOT ask about skills scope if skills are partially installed — they will be completed automatically.`,
|
|
103
|
+
"",
|
|
104
|
+
"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.",
|
|
105
|
+
"",
|
|
106
|
+
"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.",
|
|
107
|
+
"",
|
|
108
|
+
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.`,
|
|
109
|
+
"",
|
|
110
|
+
"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."
|
|
111
|
+
].join("\n"),
|
|
112
|
+
checks: [
|
|
113
|
+
{
|
|
114
|
+
id: "neonctl",
|
|
115
|
+
description: "The Neon CLI will be installed or updated automatically (no action needed from the agent)",
|
|
116
|
+
lookFor: []
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "mcp_server",
|
|
120
|
+
description: "Check if the Neon MCP server is already configured in your MCP server list",
|
|
121
|
+
lookFor: ["An MCP server entry named 'Neon' or with URL containing 'mcp.neon.tech'"]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: "agent_type",
|
|
125
|
+
description: "Identify which coding agent is running this command",
|
|
126
|
+
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"]
|
|
127
|
+
},
|
|
128
|
+
...detectedIde ? [{
|
|
129
|
+
id: "extension_installed",
|
|
130
|
+
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)",
|
|
131
|
+
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"]
|
|
132
|
+
}] : []
|
|
133
|
+
],
|
|
134
|
+
userPreferences: [
|
|
135
|
+
...templatePreferences,
|
|
136
|
+
...hasApp ? [{
|
|
137
|
+
id: "features",
|
|
138
|
+
question: "Which Neon features would you like to enable for this project?",
|
|
139
|
+
phase: "after_checks",
|
|
140
|
+
options: [{
|
|
141
|
+
value: "database",
|
|
142
|
+
label: "Database (always included)"
|
|
143
|
+
}, {
|
|
144
|
+
value: "database,auth",
|
|
145
|
+
label: "Database + Neon Auth (adds authentication via Neon)"
|
|
146
|
+
}],
|
|
147
|
+
default: "database",
|
|
148
|
+
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."
|
|
149
|
+
}] : [],
|
|
150
|
+
...(() => {
|
|
151
|
+
const isPartialSkills = String(options.skillsScope ?? "").includes("partial");
|
|
152
|
+
const needsMcpChoice = !options.mcpConfigured;
|
|
153
|
+
const needsSkillsChoice = !options.skillsInstalled && !isPartialSkills;
|
|
154
|
+
if (!(needsMcpChoice || needsSkillsChoice)) return [];
|
|
155
|
+
return [{
|
|
156
|
+
id: "mode",
|
|
157
|
+
question: "Use default settings or customize?",
|
|
158
|
+
phase: "after_checks",
|
|
159
|
+
options: [{
|
|
160
|
+
value: "defaults",
|
|
161
|
+
label: hasApp ? "Use defaults (Neon CLI, MCP: global, skills: project-level — already-configured components will be skipped)" : "Use defaults (Neon CLI, MCP: global — skills included in template)"
|
|
162
|
+
}, {
|
|
163
|
+
value: "customize",
|
|
164
|
+
label: "Customize installation settings"
|
|
165
|
+
}],
|
|
166
|
+
default: "defaults"
|
|
167
|
+
}];
|
|
168
|
+
})(),
|
|
169
|
+
{
|
|
170
|
+
id: "mcpScope",
|
|
171
|
+
question: "Where should the Neon MCP server be configured?",
|
|
172
|
+
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.",
|
|
173
|
+
phase: "after_checks",
|
|
174
|
+
options: [
|
|
175
|
+
{
|
|
176
|
+
value: "global",
|
|
177
|
+
label: "Global (available in all projects)"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
value: "project",
|
|
181
|
+
label: "Project-level (scoped to this project only)"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
value: "none",
|
|
185
|
+
label: "Skip — do not install the MCP server"
|
|
186
|
+
}
|
|
187
|
+
],
|
|
188
|
+
default: "global",
|
|
189
|
+
condition: {
|
|
190
|
+
preferenceId: "mode",
|
|
191
|
+
equals: "customize"
|
|
192
|
+
},
|
|
193
|
+
group: "customize"
|
|
194
|
+
},
|
|
195
|
+
...!options.skillsInstalled && !String(options.skillsScope ?? "").includes("partial") ? [{
|
|
196
|
+
id: "skillsScope",
|
|
197
|
+
question: "Where should Neon agent skills be installed?",
|
|
198
|
+
context: "Only ask if skills are not already installed.",
|
|
199
|
+
phase: "after_checks",
|
|
200
|
+
options: [{
|
|
201
|
+
value: "global",
|
|
202
|
+
label: "Global (available in all projects)"
|
|
203
|
+
}, {
|
|
204
|
+
value: "project",
|
|
205
|
+
label: "Project-level (scoped to this project only)"
|
|
206
|
+
}],
|
|
207
|
+
default: "project",
|
|
208
|
+
condition: {
|
|
209
|
+
preferenceId: "mode",
|
|
210
|
+
equals: "customize"
|
|
211
|
+
},
|
|
212
|
+
group: "customize"
|
|
213
|
+
}] : [],
|
|
214
|
+
{
|
|
215
|
+
id: "installExtension",
|
|
216
|
+
question: "Install the Neon editor extension for local database browsing?",
|
|
217
|
+
phase: "after_checks",
|
|
218
|
+
options: [{
|
|
219
|
+
value: "true",
|
|
220
|
+
label: "Yes"
|
|
221
|
+
}, {
|
|
222
|
+
value: "false",
|
|
223
|
+
label: "No"
|
|
224
|
+
}],
|
|
225
|
+
default: "true",
|
|
226
|
+
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.",
|
|
227
|
+
condition: {
|
|
228
|
+
preferenceId: "mode",
|
|
229
|
+
equals: "customize"
|
|
230
|
+
},
|
|
231
|
+
group: "customize"
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
reportBack: {
|
|
235
|
+
type: "run_neon_init",
|
|
236
|
+
args: [
|
|
237
|
+
"setup",
|
|
238
|
+
"--json",
|
|
239
|
+
"--data",
|
|
240
|
+
(() => {
|
|
241
|
+
const partialScope = String(options.skillsScope ?? "").replace("-partial", "");
|
|
242
|
+
const hasPartial = String(options.skillsScope ?? "").includes("partial");
|
|
243
|
+
const previewFlag = options.preview ? ", preview: true" : "";
|
|
244
|
+
const needsMcpChoice = !options.mcpConfigured;
|
|
245
|
+
const needsSkillsChoice = !options.skillsInstalled && !hasPartial;
|
|
246
|
+
const hasModeQuestion = needsMcpChoice || needsSkillsChoice;
|
|
247
|
+
const modeField = hasModeQuestion ? ", mode: string" : "";
|
|
248
|
+
const mcpField = hasModeQuestion ? ", mcpScope?: 'global'|'project'|'none'" : "";
|
|
249
|
+
const skillsField = needsSkillsChoice ? ", skillsScope?: string" : "";
|
|
250
|
+
const extField = hasModeQuestion ? ", installExtension?: bool" : "";
|
|
251
|
+
return `<json: { agent: string, ide: string, mcpConfigured: bool${options.skillsInstalled || hasPartial ? `, skillsScope: "${options.skillsInstalled ? options.skillsScope || "project" : partialScope}"` : skillsField}${previewFlag}${modeField}${mcpField}${extField}${hasApp ? ", features?: string" : ", template: string"} }>`;
|
|
252
|
+
})()
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
344
257
|
}
|
|
345
258
|
/**
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
259
|
+
* Executes the batched installation of MCP server, skills, and extension.
|
|
260
|
+
* Runs commands directly in the CLI process — the agent does NOT run these.
|
|
261
|
+
* Returns results and chains to the getting-started phase.
|
|
262
|
+
*/
|
|
350
263
|
async function executeBatchedInstallation(options) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
523
|
-
// Step 4: Install editor extension if requested
|
|
524
|
-
// Use the agent-reported IDE (not agent identity) — e.g. Claude Code running in
|
|
525
|
-
// Cursor should install the extension for Cursor, not skip it.
|
|
526
|
-
if (installExt) {
|
|
527
|
-
const extResult = await installExtensionForIde(options.ide ?? agentId);
|
|
528
|
-
results.push(extResult);
|
|
529
|
-
}
|
|
530
|
-
// Step 5: Write selected features to .neon under _init for brownfield flows
|
|
531
|
-
// (Bootstrap flows already wrote _init in step 0)
|
|
532
|
-
if (!isBootstrap && options.features && options.features.length > 0) {
|
|
533
|
-
const neonContextPath = resolve(process.cwd(), ".neon");
|
|
534
|
-
const context = {
|
|
535
|
-
_init: { features: options.features },
|
|
536
|
-
};
|
|
537
|
-
writeFileSync(neonContextPath, `${JSON.stringify(context, null, 2)}\n`);
|
|
538
|
-
}
|
|
539
|
-
const allSucceeded = results.every((r) => r.status === "success");
|
|
540
|
-
// Build args to chain to the getting-started phase as a separate CLI call.
|
|
541
|
-
// This ensures the agent gets a clean response with ONLY the getting-started
|
|
542
|
-
// action — no competing "results" array to distract it.
|
|
543
|
-
const gettingStartedData = {};
|
|
544
|
-
if (options.connectionString)
|
|
545
|
-
gettingStartedData.hasConnectionString = true;
|
|
546
|
-
if (options.framework)
|
|
547
|
-
gettingStartedData.framework = options.framework;
|
|
548
|
-
if (options.orm)
|
|
549
|
-
gettingStartedData.orm = options.orm;
|
|
550
|
-
if (options.migrationTool)
|
|
551
|
-
gettingStartedData.migrationTool = options.migrationTool;
|
|
552
|
-
if (options.migrationDir)
|
|
553
|
-
gettingStartedData.migrationDir = options.migrationDir;
|
|
554
|
-
// Pass features so getting-started knows which phases to chain to
|
|
555
|
-
const resolvedFeatures = options.templateRequires ?? options.features;
|
|
556
|
-
if (resolvedFeatures && resolvedFeatures.length > 0)
|
|
557
|
-
gettingStartedData.features = resolvedFeatures;
|
|
558
|
-
// Bootstrap implies preview mode (new project in us-east required)
|
|
559
|
-
if (isBootstrap)
|
|
560
|
-
gettingStartedData.preview = true;
|
|
561
|
-
const gettingStartedArgs = [
|
|
562
|
-
"getting-started",
|
|
563
|
-
"--json",
|
|
564
|
-
"--data",
|
|
565
|
-
JSON.stringify(gettingStartedData),
|
|
566
|
-
];
|
|
567
|
-
return {
|
|
568
|
-
phase: "setup",
|
|
569
|
-
status: allSucceeded ? "installed" : "partial",
|
|
570
|
-
results,
|
|
571
|
-
nextAction: {
|
|
572
|
-
type: "run_neon_init",
|
|
573
|
-
args: gettingStartedArgs,
|
|
574
|
-
},
|
|
575
|
-
};
|
|
264
|
+
const mcpScope = options.mcpScope ?? "global";
|
|
265
|
+
const agentId = options.agent ?? "cursor";
|
|
266
|
+
const mcpAgentId = resolveAddMcpAgentId(agentId);
|
|
267
|
+
const installExt = options.installExtension === true;
|
|
268
|
+
const results = [];
|
|
269
|
+
const isBootstrap = !!options.template;
|
|
270
|
+
if (isBootstrap && options.template) try {
|
|
271
|
+
const templates = await fetchTemplates();
|
|
272
|
+
const template = findTemplate(templates, options.template) ?? findTemplate(FALLBACK_TEMPLATES, options.template);
|
|
273
|
+
if (!template) throw new Error(`Unknown template "${options.template}".`);
|
|
274
|
+
await scaffoldTemplate(template, ".");
|
|
275
|
+
results.push({
|
|
276
|
+
id: "bootstrap",
|
|
277
|
+
description: `Scaffolded project from template "${options.template}"`,
|
|
278
|
+
status: "success"
|
|
279
|
+
});
|
|
280
|
+
if (options.templateRequires) {
|
|
281
|
+
const neonContextPath = resolve(process.cwd(), ".neon");
|
|
282
|
+
const context = { _init: { features: options.templateRequires } };
|
|
283
|
+
writeFileSync(neonContextPath, `${JSON.stringify(context, null, 2)}\n`);
|
|
284
|
+
}
|
|
285
|
+
} catch (err) {
|
|
286
|
+
results.push({
|
|
287
|
+
id: "bootstrap",
|
|
288
|
+
description: `Failed to scaffold project from template "${options.template}"`,
|
|
289
|
+
status: "failed",
|
|
290
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
const neonctlResult = await ensureNeonctl();
|
|
294
|
+
switch (neonctlResult.status) {
|
|
295
|
+
case "already_current":
|
|
296
|
+
results.push({
|
|
297
|
+
id: "neonctl",
|
|
298
|
+
description: `Neon CLI is up to date (v${neonctlResult.version})`,
|
|
299
|
+
status: "success"
|
|
300
|
+
});
|
|
301
|
+
break;
|
|
302
|
+
case "installed":
|
|
303
|
+
results.push({
|
|
304
|
+
id: "neonctl",
|
|
305
|
+
description: `Installed Neon CLI (v${neonctlResult.version})`,
|
|
306
|
+
status: "success"
|
|
307
|
+
});
|
|
308
|
+
break;
|
|
309
|
+
case "updated":
|
|
310
|
+
results.push({
|
|
311
|
+
id: "neonctl",
|
|
312
|
+
description: `Updated Neon CLI to v${neonctlResult.version}`,
|
|
313
|
+
status: "success"
|
|
314
|
+
});
|
|
315
|
+
break;
|
|
316
|
+
case "failed": results.push({
|
|
317
|
+
id: "neonctl",
|
|
318
|
+
description: "Failed to install Neon CLI",
|
|
319
|
+
status: "failed",
|
|
320
|
+
error: neonctlResult.error
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
const isCursor = mcpAgentId === "cursor" || options.ide?.toLowerCase() === "cursor" || options.agent?.toLowerCase() === "cursor";
|
|
324
|
+
if (mcpScope === "none") results.push({
|
|
325
|
+
id: "skip_mcp",
|
|
326
|
+
description: "Neon MCP server installation skipped by user",
|
|
327
|
+
status: "success"
|
|
328
|
+
});
|
|
329
|
+
else if (options.mcpConfigured) results.push({
|
|
330
|
+
id: "skip_mcp",
|
|
331
|
+
description: "Neon MCP server already configured",
|
|
332
|
+
status: "success"
|
|
333
|
+
});
|
|
334
|
+
else {
|
|
335
|
+
const mcpArgs = [
|
|
336
|
+
"-y",
|
|
337
|
+
"add-mcp",
|
|
338
|
+
"https://mcp.neon.tech/mcp",
|
|
339
|
+
...mcpScope === "global" ? ["-g"] : [],
|
|
340
|
+
"-n",
|
|
341
|
+
"Neon",
|
|
342
|
+
"-y",
|
|
343
|
+
"-a",
|
|
344
|
+
mcpAgentId
|
|
345
|
+
];
|
|
346
|
+
try {
|
|
347
|
+
await execa("npx", mcpArgs, {
|
|
348
|
+
stdio: "pipe",
|
|
349
|
+
timeout: 6e4
|
|
350
|
+
});
|
|
351
|
+
results.push({
|
|
352
|
+
id: "install_mcp",
|
|
353
|
+
description: `Installed Neon MCP server (${mcpScope} scope)`,
|
|
354
|
+
status: "success"
|
|
355
|
+
});
|
|
356
|
+
const isClaudeCode = mcpAgentId === "claude-code" || options.agent?.toLowerCase() === "claude-code";
|
|
357
|
+
if (isCursor && mcpScope === "project") results.push({
|
|
358
|
+
id: "enable_mcp",
|
|
359
|
+
description: "Cursor disables project-level MCP servers by default. Open Cursor Settings > MCP and toggle the \"Neon\" server on.",
|
|
360
|
+
status: "success",
|
|
361
|
+
manualAction: true
|
|
362
|
+
});
|
|
363
|
+
else if (isClaudeCode) results.push({
|
|
364
|
+
id: "enable_mcp",
|
|
365
|
+
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.",
|
|
366
|
+
status: "success",
|
|
367
|
+
manualAction: true
|
|
368
|
+
});
|
|
369
|
+
} catch (err) {
|
|
370
|
+
results.push({
|
|
371
|
+
id: "install_mcp",
|
|
372
|
+
description: "Failed to install Neon MCP server",
|
|
373
|
+
status: "failed",
|
|
374
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (isBootstrap) results.push({
|
|
379
|
+
id: "install_skills",
|
|
380
|
+
description: "Neon agent skills included in template",
|
|
381
|
+
status: "success"
|
|
382
|
+
});
|
|
383
|
+
else {
|
|
384
|
+
const skillsScope = options.skillsScope ?? "project";
|
|
385
|
+
if (await ensureSkillsUpToDate(agentId, skillsScope, options.preview)) results.push({
|
|
386
|
+
id: "install_skills",
|
|
387
|
+
description: "Neon agent skills installed",
|
|
388
|
+
status: "success"
|
|
389
|
+
});
|
|
390
|
+
else {
|
|
391
|
+
const { getSkillList } = await import("../skills.js");
|
|
392
|
+
const cmds = getSkillList(options.preview).map((s) => `skills add neondatabase/agent-skills --skill ${s} --agent ${agentId}${skillsScope === "global" ? " -g" : ""} -y`);
|
|
393
|
+
results.push({
|
|
394
|
+
id: "install_skills",
|
|
395
|
+
description: "Failed to install Neon agent skills automatically. Run these commands to install manually:",
|
|
396
|
+
status: "failed",
|
|
397
|
+
commands: cmds
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (installExt) {
|
|
402
|
+
const extResult = await installExtensionForIde(options.ide ?? agentId);
|
|
403
|
+
results.push(extResult);
|
|
404
|
+
}
|
|
405
|
+
if (!isBootstrap && options.features && options.features.length > 0) {
|
|
406
|
+
const neonContextPath = resolve(process.cwd(), ".neon");
|
|
407
|
+
const context = { _init: { features: options.features } };
|
|
408
|
+
writeFileSync(neonContextPath, `${JSON.stringify(context, null, 2)}\n`);
|
|
409
|
+
}
|
|
410
|
+
const allSucceeded = results.every((r) => r.status === "success");
|
|
411
|
+
const gettingStartedData = {};
|
|
412
|
+
if (options.connectionString) gettingStartedData.hasConnectionString = true;
|
|
413
|
+
if (options.framework) gettingStartedData.framework = options.framework;
|
|
414
|
+
if (options.orm) gettingStartedData.orm = options.orm;
|
|
415
|
+
if (options.migrationTool) gettingStartedData.migrationTool = options.migrationTool;
|
|
416
|
+
if (options.migrationDir) gettingStartedData.migrationDir = options.migrationDir;
|
|
417
|
+
const resolvedFeatures = options.templateRequires ?? options.features;
|
|
418
|
+
if (resolvedFeatures && resolvedFeatures.length > 0) gettingStartedData.features = resolvedFeatures;
|
|
419
|
+
if (isBootstrap) gettingStartedData.preview = true;
|
|
420
|
+
const gettingStartedArgs = [
|
|
421
|
+
"getting-started",
|
|
422
|
+
"--json",
|
|
423
|
+
"--data",
|
|
424
|
+
JSON.stringify(gettingStartedData)
|
|
425
|
+
];
|
|
426
|
+
return {
|
|
427
|
+
phase: "setup",
|
|
428
|
+
status: allSucceeded ? "installed" : "partial",
|
|
429
|
+
results,
|
|
430
|
+
nextAction: {
|
|
431
|
+
type: "run_neon_init",
|
|
432
|
+
args: gettingStartedArgs
|
|
433
|
+
}
|
|
434
|
+
};
|
|
576
435
|
}
|
|
577
436
|
/**
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
437
|
+
* Fills in missing filesystem inspection fields by running inspectProject().
|
|
438
|
+
* Agent-reported data (mcpConfigured, agent, mode, scopes) is preserved.
|
|
439
|
+
* CLI-detectable fields (framework, orm, migrations, connectionString, isVscodeIde)
|
|
440
|
+
* are filled in only if not already present.
|
|
441
|
+
*/
|
|
583
442
|
async function mergeCliInspection(options) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
443
|
+
if (options.framework !== void 0 && options.orm !== void 0) return options;
|
|
444
|
+
const inspection = await inspectProject([
|
|
445
|
+
{
|
|
446
|
+
id: "connection_string",
|
|
447
|
+
description: "",
|
|
448
|
+
lookFor: []
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
id: "project_stack",
|
|
452
|
+
description: "",
|
|
453
|
+
lookFor: []
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
id: "migrations",
|
|
457
|
+
description: "",
|
|
458
|
+
lookFor: []
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
id: "ide_type",
|
|
462
|
+
description: "",
|
|
463
|
+
lookFor: []
|
|
464
|
+
}
|
|
465
|
+
]);
|
|
466
|
+
const ide = options.ide?.toLowerCase().replace(/\s+/g, "-") || detectIde()?.toLowerCase().replace(/\s+/g, "-") || void 0;
|
|
467
|
+
return {
|
|
468
|
+
...options,
|
|
469
|
+
ide,
|
|
470
|
+
connectionString: options.connectionString ?? inspection.connectionString,
|
|
471
|
+
framework: options.framework ?? inspection.framework,
|
|
472
|
+
orm: options.orm ?? inspection.orm,
|
|
473
|
+
migrationTool: options.migrationTool ?? inspection.migrationTool,
|
|
474
|
+
migrationDir: options.migrationDir ?? inspection.migrationDir,
|
|
475
|
+
isVscodeIde: options.isVscodeIde ?? inspection.isVscodeIde
|
|
476
|
+
};
|
|
612
477
|
}
|
|
613
478
|
function isVscodeBasedIde(options) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
ide === "windsurf");
|
|
620
|
-
}
|
|
621
|
-
return options.isVscodeIde === true;
|
|
479
|
+
if (options.ide) {
|
|
480
|
+
const ide = options.ide.toLowerCase();
|
|
481
|
+
return ide === "cursor" || ide === "vscode" || ide === "vs-code" || ide === "windsurf";
|
|
482
|
+
}
|
|
483
|
+
return options.isVscodeIde === true;
|
|
622
484
|
}
|
|
623
485
|
/**
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
486
|
+
* Resolves which IDE to install the extension for.
|
|
487
|
+
* Accepts the agent-reported IDE value (preferred), the agent ID, or
|
|
488
|
+
* falls back to env-var detection.
|
|
489
|
+
*/
|
|
628
490
|
function resolveEditorForExtension(ideOrAgentId) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
break;
|
|
641
|
-
}
|
|
642
|
-
// Fall back to env-var detection
|
|
643
|
-
const ide = detectIde();
|
|
644
|
-
if (ide === "Cursor" || ide === "VS Code")
|
|
645
|
-
return ide;
|
|
646
|
-
return null;
|
|
491
|
+
switch (ideOrAgentId.toLowerCase()) {
|
|
492
|
+
case "cursor": return "Cursor";
|
|
493
|
+
case "vscode":
|
|
494
|
+
case "vs-code":
|
|
495
|
+
case "copilot":
|
|
496
|
+
case "github-copilot":
|
|
497
|
+
case "github-copilot-cli": return "VS Code";
|
|
498
|
+
}
|
|
499
|
+
const ide = detectIde();
|
|
500
|
+
if (ide === "Cursor" || ide === "VS Code") return ide;
|
|
501
|
+
return null;
|
|
647
502
|
}
|
|
648
503
|
const MANUAL_INSTALL_MSG = `Search for "Neon" in the extensions panel (Cmd+Shift+X / Ctrl+Shift+X) and install "Neon Local Connect" by Databricks.`;
|
|
649
504
|
/**
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
505
|
+
* Installs the Neon extension for the detected IDE.
|
|
506
|
+
*
|
|
507
|
+
* Uses env-var detection to determine the IDE (not the agent identity),
|
|
508
|
+
* so Claude Code running in Cursor correctly installs for Cursor.
|
|
509
|
+
*
|
|
510
|
+
* Strategy:
|
|
511
|
+
* 1. Try `<editor> --install-extension <id>` directly (uses editor's configured marketplace)
|
|
512
|
+
* 2. If that fails, download .vsix (from proxy or Open VSX) and install via local file
|
|
513
|
+
* 3. If all else fails: return manual install instructions
|
|
514
|
+
*/
|
|
660
515
|
async function installExtensionForIde(agentId) {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
catch {
|
|
716
|
-
return {
|
|
717
|
-
id: "install_extension",
|
|
718
|
-
description: MANUAL_INSTALL_MSG,
|
|
719
|
-
status: "success",
|
|
720
|
-
manualAction: true,
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
finally {
|
|
724
|
-
try {
|
|
725
|
-
await unlink(vsixPath);
|
|
726
|
-
}
|
|
727
|
-
catch { }
|
|
728
|
-
}
|
|
516
|
+
const editorType = resolveEditorForExtension(agentId);
|
|
517
|
+
if (!editorType) return {
|
|
518
|
+
id: "install_extension",
|
|
519
|
+
description: MANUAL_INSTALL_MSG,
|
|
520
|
+
status: "success",
|
|
521
|
+
manualAction: true
|
|
522
|
+
};
|
|
523
|
+
const editorCmd = await findEditorCommand(editorType);
|
|
524
|
+
if (!editorCmd) return {
|
|
525
|
+
id: "install_extension",
|
|
526
|
+
description: MANUAL_INSTALL_MSG,
|
|
527
|
+
status: "success",
|
|
528
|
+
manualAction: true
|
|
529
|
+
};
|
|
530
|
+
try {
|
|
531
|
+
await execa(editorCmd, ["--install-extension", NEON_EXTENSION_ID], {
|
|
532
|
+
stdio: "pipe",
|
|
533
|
+
timeout: 6e4
|
|
534
|
+
});
|
|
535
|
+
return {
|
|
536
|
+
id: "install_extension",
|
|
537
|
+
description: `Installed Neon extension for ${editorType}`,
|
|
538
|
+
status: "success"
|
|
539
|
+
};
|
|
540
|
+
} catch {}
|
|
541
|
+
const vsixPath = await downloadVsix();
|
|
542
|
+
if (!vsixPath) return {
|
|
543
|
+
id: "install_extension",
|
|
544
|
+
description: MANUAL_INSTALL_MSG,
|
|
545
|
+
status: "success",
|
|
546
|
+
manualAction: true
|
|
547
|
+
};
|
|
548
|
+
try {
|
|
549
|
+
await execa(editorCmd, ["--install-extension", vsixPath], {
|
|
550
|
+
stdio: "pipe",
|
|
551
|
+
timeout: 6e4
|
|
552
|
+
});
|
|
553
|
+
return {
|
|
554
|
+
id: "install_extension",
|
|
555
|
+
description: `Installed Neon extension for ${editorType}`,
|
|
556
|
+
status: "success"
|
|
557
|
+
};
|
|
558
|
+
} catch {
|
|
559
|
+
return {
|
|
560
|
+
id: "install_extension",
|
|
561
|
+
description: MANUAL_INSTALL_MSG,
|
|
562
|
+
status: "success",
|
|
563
|
+
manualAction: true
|
|
564
|
+
};
|
|
565
|
+
} finally {
|
|
566
|
+
try {
|
|
567
|
+
await unlink(vsixPath);
|
|
568
|
+
} catch {}
|
|
569
|
+
}
|
|
729
570
|
}
|
|
571
|
+
//#endregion
|
|
572
|
+
export { handleSetupPhase };
|