neon 2.47.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/dist/_shared/auth_selection.js +76 -79
- package/dist/_shared/credentials.js +166 -187
- package/dist/_shared/env-core/env.js +395 -0
- package/dist/_shared/env-core/reuse-secrets.js +179 -0
- package/dist/_shared/paths.js +129 -126
- package/dist/_shared/profiles.js +192 -242
- package/dist/_shared/secure_file.js +36 -38
- package/dist/_virtual/_rolldown/runtime.js +13 -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 +575 -658
- 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 +642 -681
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +243 -303
- package/dist/commands/functions.js +275 -341
- 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 +614 -845
- 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 +313 -394
- package/dist/dev/functions.js +54 -64
- 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 +36 -38
- 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 +727 -983
- 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 +1293 -2082
- 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 +547 -1099
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +261 -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 +762 -1180
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1302
- 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 +626 -815
- 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 +1414 -1927
- 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 +2 -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 +133 -147
- 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 +37 -44
- 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 +9 -7
package/dist/init/interactive.js
CHANGED
|
@@ -1,64 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Uses the same underlying install functions but with a clean clack-based UX.
|
|
4
|
-
*/
|
|
5
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
-
import { resolve } from "node:path";
|
|
7
|
-
import { SelectPrompt } from "@clack/core";
|
|
8
|
-
import { confirm, isCancel, log, multiselect, outro, select, spinner, } from "@clack/prompts";
|
|
9
|
-
import { execa } from "execa";
|
|
10
|
-
import { bold, dim, gray, italic } from "yoctocolors";
|
|
1
|
+
import { FALLBACK_TEMPLATES, fetchTemplates, scaffoldTemplate } from "./bootstrap.js";
|
|
2
|
+
import { detectAgent, detectIde } from "./detect_agent.js";
|
|
11
3
|
import { ALL_CONFIGURABLE_AGENTS, getAddMcpAgentId } from "./agents.js";
|
|
12
4
|
import { ensureNeonctlAuth, isAuthenticated } from "./auth.js";
|
|
13
|
-
import { FALLBACK_TEMPLATES, fetchTemplates, scaffoldTemplate, } from "./bootstrap.js";
|
|
14
|
-
import { detectAgent, detectIde } from "./detect_agent.js";
|
|
15
5
|
import { detectAvailableEditors } from "./editors.js";
|
|
16
|
-
import { installExtension, isExtensionInstalled, usesExtension
|
|
6
|
+
import { installExtension, isExtensionInstalled, usesExtension } from "./extension.js";
|
|
17
7
|
import { inspectProject } from "./inspect.js";
|
|
18
8
|
import { ensureNeonctl } from "./neonctl.js";
|
|
19
9
|
import { ensureSkillsUpToDate, installAgentSkills } from "./skills.js";
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import which from "which";
|
|
13
|
+
import { SelectPrompt } from "@clack/core";
|
|
14
|
+
import { confirm, isCancel, log, multiselect, outro, select, spinner } from "@clack/prompts";
|
|
15
|
+
import { execa } from "execa";
|
|
16
|
+
import { bold, dim, gray, italic } from "yoctocolors";
|
|
17
|
+
import picocolors from "picocolors";
|
|
18
|
+
//#region src/init/interactive.ts
|
|
19
|
+
/**
|
|
20
|
+
* Interactive v2 CLI — purpose-built guided flow for humans.
|
|
21
|
+
* Uses the same underlying install functions but with a clean clack-based UX.
|
|
22
|
+
*/
|
|
20
23
|
function wordWrap(text, width) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
current = word;
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
current = current.length > 0 ? `${current} ${word}` : word;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (current.length > 0)
|
|
40
|
-
lines.push(current);
|
|
41
|
-
return lines.join("\n");
|
|
42
|
-
})
|
|
43
|
-
.join("\n");
|
|
24
|
+
return text.split("\n").map((line) => {
|
|
25
|
+
if (line.length <= width) return line;
|
|
26
|
+
const words = line.split(" ");
|
|
27
|
+
const lines = [];
|
|
28
|
+
let current = "";
|
|
29
|
+
for (const word of words) if (current.length + word.length + 1 > width && current.length > 0) {
|
|
30
|
+
lines.push(current);
|
|
31
|
+
current = word;
|
|
32
|
+
} else current = current.length > 0 ? `${current} ${word}` : word;
|
|
33
|
+
if (current.length > 0) lines.push(current);
|
|
34
|
+
return lines.join("\n");
|
|
35
|
+
}).join("\n");
|
|
44
36
|
}
|
|
45
|
-
// Patch picocolors (used by @clack/prompts) to use Neon green instead of cyan/magenta.
|
|
46
|
-
// clack hardcodes picocolors.cyan() with no theme API, so this is the least invasive override.
|
|
47
|
-
import picocolors from "picocolors";
|
|
48
37
|
const neonGreenFn = (s) => `\x1b[38;2;75;181;120m${s}\x1b[39m`;
|
|
49
38
|
const originalCyan = picocolors.cyan;
|
|
50
39
|
const originalMagenta = picocolors.magenta;
|
|
51
40
|
function patchClackColors() {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
41
|
+
const pc = picocolors;
|
|
42
|
+
pc.cyan = neonGreenFn;
|
|
43
|
+
pc.magenta = neonGreenFn;
|
|
44
|
+
return () => {
|
|
45
|
+
pc.cyan = originalCyan;
|
|
46
|
+
pc.magenta = originalMagenta;
|
|
47
|
+
};
|
|
59
48
|
}
|
|
60
|
-
// clack box-drawing glyphs, mirrored from @clack/prompts so our custom
|
|
61
|
-
// template picker lines up with every other step in the flow.
|
|
62
49
|
const S_BAR = "│";
|
|
63
50
|
const S_BAR_END = "└";
|
|
64
51
|
const S_STEP_ACTIVE = "◆";
|
|
@@ -68,584 +55,485 @@ const S_RADIO_ACTIVE = "●";
|
|
|
68
55
|
const S_RADIO_INACTIVE = "○";
|
|
69
56
|
const SENTINEL_NONE = "none";
|
|
70
57
|
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
58
|
+
* Bespoke template picker built on @clack/core's `SelectPrompt`.
|
|
59
|
+
*
|
|
60
|
+
* The stock `@clack/prompts` `select` can only render an option on a single
|
|
61
|
+
* line (title + a parenthesized hint on the focused row), which can't express
|
|
62
|
+
* what we want here: a clean title-only list, and a focused row that expands to
|
|
63
|
+
* `Title (tools)` with the description on its own italic line beneath. Driving
|
|
64
|
+
* the core prompt directly lets us own the frame, so we emit a second
|
|
65
|
+
* gutter-aligned line for the active option only.
|
|
66
|
+
*/
|
|
80
67
|
async function selectTemplate(templates, message) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const prompt = new SelectPrompt({
|
|
112
|
-
options,
|
|
113
|
-
initialValue: options[0]?.value,
|
|
114
|
-
render() {
|
|
115
|
-
const active = this.options[this.cursor];
|
|
116
|
-
const heading = (symbol) => `${gray(S_BAR)}\n${symbol} ${message}\n`;
|
|
117
|
-
if (this.state === "submit") {
|
|
118
|
-
return `${heading(green(S_STEP_SUBMIT))}${gray(S_BAR)} ${dim(active?.label ?? "")}`;
|
|
119
|
-
}
|
|
120
|
-
if (this.state === "cancel") {
|
|
121
|
-
return `${heading(green(S_STEP_CANCEL))}${gray(S_BAR)} ${dim(active?.label ?? "")}\n${gray(S_BAR)}`;
|
|
122
|
-
}
|
|
123
|
-
const body = this.options
|
|
124
|
-
.map((option) => option === active
|
|
125
|
-
? renderActive(option)
|
|
126
|
-
: `${dim(S_RADIO_INACTIVE)} ${dim(option.label)}`)
|
|
127
|
-
.join(`\n${green(S_BAR)} `);
|
|
128
|
-
return `${heading(green(S_STEP_ACTIVE))}${green(S_BAR)} ${body}\n${green(S_BAR_END)}\n`;
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
return prompt.prompt();
|
|
68
|
+
const options = [...templates.map((t) => ({
|
|
69
|
+
value: t.id,
|
|
70
|
+
label: t.title,
|
|
71
|
+
tools: t.tools,
|
|
72
|
+
description: t.description
|
|
73
|
+
})), {
|
|
74
|
+
value: SENTINEL_NONE,
|
|
75
|
+
label: "No thanks — continue without scaffolding"
|
|
76
|
+
}];
|
|
77
|
+
const green = neonGreenFn;
|
|
78
|
+
const descIndent = " ";
|
|
79
|
+
const descWidth = Math.max(24, (process.stdout.columns || 80) - 8);
|
|
80
|
+
const renderActive = (option) => {
|
|
81
|
+
const tools = option.tools && option.tools.length > 0 ? ` ${dim(`(${option.tools.join(", ")})`)}` : "";
|
|
82
|
+
const head = `${green(S_RADIO_ACTIVE)} ${option.label}${tools}`;
|
|
83
|
+
if (!option.description) return head;
|
|
84
|
+
return `${head}\n${wordWrap(option.description, descWidth).split("\n").map((line) => `${green(S_BAR)}${descIndent}${italic(dim(line))}`).join("\n")}`;
|
|
85
|
+
};
|
|
86
|
+
return new SelectPrompt({
|
|
87
|
+
options,
|
|
88
|
+
initialValue: options[0]?.value,
|
|
89
|
+
render() {
|
|
90
|
+
const active = this.options[this.cursor];
|
|
91
|
+
const heading = (symbol) => `${gray(S_BAR)}\n${symbol} ${message}\n`;
|
|
92
|
+
if (this.state === "submit") return `${heading(green(S_STEP_SUBMIT))}${gray(S_BAR)} ${dim(active?.label ?? "")}`;
|
|
93
|
+
if (this.state === "cancel") return `${heading(green(S_STEP_CANCEL))}${gray(S_BAR)} ${dim(active?.label ?? "")}\n${gray(S_BAR)}`;
|
|
94
|
+
const body = this.options.map((option) => option === active ? renderActive(option) : `${dim(S_RADIO_INACTIVE)} ${dim(option.label)}`).join(`\n${green(S_BAR)} `);
|
|
95
|
+
return `${heading(green(S_STEP_ACTIVE))}${green(S_BAR)} ${body}\n${green(S_BAR_END)}\n`;
|
|
96
|
+
}
|
|
97
|
+
}).prompt();
|
|
132
98
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
99
|
+
async function interactiveInit(options = {}) {
|
|
100
|
+
const restoreColors = patchClackColors();
|
|
101
|
+
try {
|
|
102
|
+
await interactiveInitInner(options);
|
|
103
|
+
} finally {
|
|
104
|
+
restoreColors();
|
|
105
|
+
}
|
|
141
106
|
}
|
|
142
107
|
async function interactiveInitInner(options) {
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
562
|
-
mcpS.stop(`Failed to configure MCP server for ${editor}`);
|
|
563
|
-
log.error(msg);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
if (needsSkills) {
|
|
567
|
-
await installAgentSkills([editor], {
|
|
568
|
-
scope: skillsScope,
|
|
569
|
-
preview: options.preview,
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
if (doInstallExtension && usesExtension(editor)) {
|
|
573
|
-
const extS = spinner();
|
|
574
|
-
extS.start(`Installing Neon extension for ${editor}...`);
|
|
575
|
-
const extOk = await installExtension(editor);
|
|
576
|
-
if (extOk) {
|
|
577
|
-
extS.stop(dim(`Neon extension installed for ${editor} ✓`));
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
extS.stop(`Extension install failed — install manually from the extensions panel.`);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
// Ensure all required skills are present (fills in any missing ones).
|
|
586
|
-
// detectAgent() returns null in a human terminal (TTY), so fall back
|
|
587
|
-
// to IDE detection which works regardless of TTY.
|
|
588
|
-
const ide = detectIde();
|
|
589
|
-
const agentForSkills = detectAgent() ??
|
|
590
|
-
(ide === "Cursor"
|
|
591
|
-
? "cursor"
|
|
592
|
-
: ide === "VS Code"
|
|
593
|
-
? "vscode"
|
|
594
|
-
: ide === "Windsurf"
|
|
595
|
-
? "windsurf"
|
|
596
|
-
: null);
|
|
597
|
-
if (agentForSkills) {
|
|
598
|
-
const detectedSkillsScope = inspection.skillsScope === "global" ? "global" : undefined;
|
|
599
|
-
await ensureSkillsUpToDate(agentForSkills, detectedSkillsScope, options.preview);
|
|
600
|
-
}
|
|
601
|
-
// -----------------------------------------------------------------------
|
|
602
|
-
// Step 6: Done — build prompt for the agent to continue
|
|
603
|
-
// -----------------------------------------------------------------------
|
|
604
|
-
// Build the getting-started data payload (same as agent mode)
|
|
605
|
-
const gettingStartedData = {};
|
|
606
|
-
if (hasNeonConnection)
|
|
607
|
-
gettingStartedData.hasConnectionString = true;
|
|
608
|
-
if (inspection.framework && inspection.framework !== "none")
|
|
609
|
-
gettingStartedData.framework = inspection.framework;
|
|
610
|
-
if (inspection.orm && inspection.orm !== "none")
|
|
611
|
-
gettingStartedData.orm = inspection.orm;
|
|
612
|
-
if (inspection.migrationTool && inspection.migrationTool !== "none")
|
|
613
|
-
gettingStartedData.migrationTool = inspection.migrationTool;
|
|
614
|
-
if (inspection.migrationDir && inspection.migrationDir !== "none")
|
|
615
|
-
gettingStartedData.migrationDir = inspection.migrationDir;
|
|
616
|
-
if (selectedFeatures.length > 0)
|
|
617
|
-
gettingStartedData.features = selectedFeatures;
|
|
618
|
-
if (options.preview)
|
|
619
|
-
gettingStartedData.preview = true;
|
|
620
|
-
// Build a prompt for the user to paste into their agent chat
|
|
621
|
-
const cmd = `neon init --agent --data '${JSON.stringify({ step: "getting-started", ...gettingStartedData })}'`;
|
|
622
|
-
// Account for clack's "│ " prefix (3 chars) when wrapping
|
|
623
|
-
const cols = (process.stdout.columns || 80) - 3;
|
|
624
|
-
const promptText = `To finish setting up Neon using Neon's agent-guided onboarding experience, have your agent run this shell command: ${cmd}`;
|
|
625
|
-
log.step("Next steps");
|
|
626
|
-
log.message(dim("Copy the following into your agent chat:"));
|
|
627
|
-
log.message(wordWrap(promptText, cols)
|
|
628
|
-
.split("\n")
|
|
629
|
-
.map((line) => bold(neonGreenFn(line)))
|
|
630
|
-
.join("\n"));
|
|
631
|
-
outro(dim("Have feedback? Email us at feedback@neon.tech"));
|
|
108
|
+
process.stdout.write(`\n\x1b[38;2;75;181;120m${[
|
|
109
|
+
" ██╗ ██╗██████╗ ██████╗ ██╗ ██╗",
|
|
110
|
+
" ███╗ ██║██╔═══╝ ██╔═══██╗███╗ ██║",
|
|
111
|
+
" ████╗██║██████╗ ██║ ██║████╗██║",
|
|
112
|
+
" ██╔████║██╔═══╝ ██║ ██║██╔████║",
|
|
113
|
+
" ██║╚███║██████╗ ╚██████╔╝██║╚███║",
|
|
114
|
+
" ╚═╝ ╚══╝╚═════╝ ╚═════╝ ╚═╝ ╚══╝"
|
|
115
|
+
].join("\n")}\x1b[0m\n`);
|
|
116
|
+
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 detectedAgentId = detectAgent();
|
|
118
|
+
const detectedEditor = detectedAgentId ? agentIdToEditor(detectedAgentId) : null;
|
|
119
|
+
const inspectSpinner = spinner();
|
|
120
|
+
inspectSpinner.start("Checking existing configuration...");
|
|
121
|
+
const inspection = await inspectProject([
|
|
122
|
+
{
|
|
123
|
+
id: "has_app",
|
|
124
|
+
description: "",
|
|
125
|
+
lookFor: []
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "mcp_server",
|
|
129
|
+
description: "",
|
|
130
|
+
lookFor: []
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: "skills",
|
|
134
|
+
description: "",
|
|
135
|
+
lookFor: []
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "connection_string",
|
|
139
|
+
description: "",
|
|
140
|
+
lookFor: []
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "project_stack",
|
|
144
|
+
description: "",
|
|
145
|
+
lookFor: []
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "migrations",
|
|
149
|
+
description: "",
|
|
150
|
+
lookFor: []
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "ide_type",
|
|
154
|
+
description: "",
|
|
155
|
+
lookFor: []
|
|
156
|
+
}
|
|
157
|
+
]);
|
|
158
|
+
inspectSpinner.stop(dim("Configuration checked ✓"));
|
|
159
|
+
const hasApp = inspection.hasApp === true;
|
|
160
|
+
let selectedFeatures = [];
|
|
161
|
+
let selectedTemplate = null;
|
|
162
|
+
if (options.preview && !hasApp) {
|
|
163
|
+
let templates = FALLBACK_TEMPLATES;
|
|
164
|
+
try {
|
|
165
|
+
const fetched = await fetchTemplates();
|
|
166
|
+
if (fetched && fetched.length > 0) templates = fetched;
|
|
167
|
+
} catch {}
|
|
168
|
+
const templateResult = await selectTemplate(templates, "No application detected. Would you like to scaffold a new project from a template?");
|
|
169
|
+
if (isCancel(templateResult)) {
|
|
170
|
+
outro("Setup cancelled.");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (templateResult !== "none") {
|
|
174
|
+
selectedTemplate = templates.find((t) => t.id === templateResult) ?? null;
|
|
175
|
+
if (selectedTemplate) {
|
|
176
|
+
selectedFeatures = selectedTemplate.requires;
|
|
177
|
+
const bootstrapS = spinner();
|
|
178
|
+
bootstrapS.start(`Scaffolding project from template "${selectedTemplate.title}"...`);
|
|
179
|
+
try {
|
|
180
|
+
await scaffoldTemplate(selectedTemplate, ".", { onWarn: (message) => log.warn(message) });
|
|
181
|
+
bootstrapS.stop(dim(`Scaffolded project from "${selectedTemplate.title}" ✓`));
|
|
182
|
+
} catch (err) {
|
|
183
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
184
|
+
bootstrapS.stop("Failed to scaffold project");
|
|
185
|
+
log.error(msg);
|
|
186
|
+
outro("Setup failed.");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!selectedTemplate && hasApp) {
|
|
193
|
+
const featuresResult = await select({
|
|
194
|
+
message: "Which Neon features would you like to enable for this project?",
|
|
195
|
+
options: [{
|
|
196
|
+
value: "database",
|
|
197
|
+
label: "Database"
|
|
198
|
+
}, {
|
|
199
|
+
value: "database,auth",
|
|
200
|
+
label: "Database + Neon Auth (adds authentication via Neon)"
|
|
201
|
+
}],
|
|
202
|
+
initialValue: "database"
|
|
203
|
+
});
|
|
204
|
+
if (isCancel(featuresResult)) {
|
|
205
|
+
outro("Setup cancelled.");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
selectedFeatures = featuresResult.split(",");
|
|
209
|
+
}
|
|
210
|
+
if (selectedFeatures.length > 0) {
|
|
211
|
+
const neonPath = resolve(process.cwd(), ".neon");
|
|
212
|
+
let existing = {};
|
|
213
|
+
if (existsSync(neonPath)) try {
|
|
214
|
+
existing = JSON.parse(readFileSync(neonPath, "utf-8"));
|
|
215
|
+
} catch {}
|
|
216
|
+
existing._init = { features: selectedFeatures };
|
|
217
|
+
writeFileSync(neonPath, `${JSON.stringify(existing, null, 2)}\n`);
|
|
218
|
+
}
|
|
219
|
+
const mcpAlready = inspection.mcpConfigured === true;
|
|
220
|
+
const skillsAlready = inspection.skillsInstalled === true || selectedTemplate !== null;
|
|
221
|
+
const hasNeonConnection = inspection.connectionString === true;
|
|
222
|
+
let needsMcp = !mcpAlready;
|
|
223
|
+
const needsSkills = !skillsAlready;
|
|
224
|
+
const needsInstall = needsMcp || needsSkills;
|
|
225
|
+
const neonContextPath = resolve(process.cwd(), ".neon");
|
|
226
|
+
const hasNeonContext = existsSync(neonContextPath) && (() => {
|
|
227
|
+
try {
|
|
228
|
+
return !!JSON.parse(readFileSync(neonContextPath, "utf-8")).projectId;
|
|
229
|
+
} catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
})();
|
|
233
|
+
const hasNeonAuth = (() => {
|
|
234
|
+
for (const envFile of [".env", ".env.local"]) {
|
|
235
|
+
const envPath = resolve(process.cwd(), envFile);
|
|
236
|
+
if (existsSync(envPath)) try {
|
|
237
|
+
const content = readFileSync(envPath, "utf-8");
|
|
238
|
+
if (/^NEON_AUTH_/m.test(content)) return true;
|
|
239
|
+
} catch {}
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
})();
|
|
243
|
+
let extensionAlready = false;
|
|
244
|
+
if (detectedEditor && usesExtension(detectedEditor)) extensionAlready = await isExtensionInstalled(detectedEditor);
|
|
245
|
+
if (mcpAlready && skillsAlready && hasNeonConnection && hasNeonContext) {
|
|
246
|
+
log.step(dim(`Neon MCP server already configured (${inspection.mcpScope || "detected"}) ✓`));
|
|
247
|
+
log.step(dim(`Neon agent skills already installed (${inspection.skillsScope || "detected"}) ✓`));
|
|
248
|
+
if (extensionAlready) log.step(dim("Neon editor extension installed ✓"));
|
|
249
|
+
log.step(dim("Neon database connected ✓"));
|
|
250
|
+
if (hasNeonAuth) {
|
|
251
|
+
log.step(dim("Neon Auth configured ✓"));
|
|
252
|
+
outro(dim("Your project is fully configured with Neon. Nothing to do."));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const authResult = await select({
|
|
256
|
+
message: "Would you like to set up Neon Auth for user authentication?",
|
|
257
|
+
options: [{
|
|
258
|
+
value: "yes",
|
|
259
|
+
label: "Yes, set up Neon Auth"
|
|
260
|
+
}, {
|
|
261
|
+
value: "no",
|
|
262
|
+
label: "No, skip for now"
|
|
263
|
+
}],
|
|
264
|
+
initialValue: "no"
|
|
265
|
+
});
|
|
266
|
+
if (isCancel(authResult) || authResult === "no") {
|
|
267
|
+
outro(dim(`Your project is configured with Neon. You can set up Neon Auth later by having your agent run: neon init --agent --data '{"step":"neon-auth"}'`));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
let projectId = null;
|
|
271
|
+
try {
|
|
272
|
+
projectId = JSON.parse(readFileSync(neonContextPath, "utf-8")).projectId ?? null;
|
|
273
|
+
} catch {}
|
|
274
|
+
log.step("Next steps");
|
|
275
|
+
const promptLines = ["Set up Neon Auth for this project."];
|
|
276
|
+
if (projectId) promptLines.push(`Project ID: ${projectId}.`);
|
|
277
|
+
log.message(dim("Copy the following into your agent chat:"));
|
|
278
|
+
log.message(promptLines.map((line) => bold(neonGreenFn(line))).join("\n"));
|
|
279
|
+
outro(dim("Have feedback? Email us at feedback@neon.tech"));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (mcpAlready) log.step(dim(`Neon MCP server already configured (${inspection.mcpScope || "detected"}) ✓`));
|
|
283
|
+
if (skillsAlready) log.step(dim(`Neon agent skills already installed (${inspection.skillsScope || "detected"}) ✓`));
|
|
284
|
+
if (needsInstall) {
|
|
285
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
286
|
+
if (!homeDir) {
|
|
287
|
+
log.error("Could not determine home directory.");
|
|
288
|
+
outro("Setup failed.");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
let selectedEditors;
|
|
292
|
+
if (detectedEditor) selectedEditors = [detectedEditor];
|
|
293
|
+
else {
|
|
294
|
+
const availableEditors = await detectAvailableEditors(homeDir);
|
|
295
|
+
const editorResponse = await multiselect({
|
|
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.");
|
|
312
|
+
outro("Setup cancelled.");
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const vscodeEditors = selectedEditors.filter(usesExtension);
|
|
317
|
+
let extensionAlreadyInstalled = false;
|
|
318
|
+
if (vscodeEditors.length > 0) {
|
|
319
|
+
extensionAlreadyInstalled = (await Promise.all(vscodeEditors.map((e) => isExtensionInstalled(e)))).every(Boolean);
|
|
320
|
+
if (extensionAlreadyInstalled) log.step(dim("Neon editor extension already installed ✓"));
|
|
321
|
+
}
|
|
322
|
+
const canInstallExtension = vscodeEditors.length > 0 && !extensionAlreadyInstalled;
|
|
323
|
+
let doInstallExtension = false;
|
|
324
|
+
const hintParts = [];
|
|
325
|
+
if (needsMcp) hintParts.push("MCP server (global)");
|
|
326
|
+
if (needsSkills) hintParts.push("agent skills (project)");
|
|
327
|
+
let mcpScope = "global";
|
|
328
|
+
let skillsScope = "project";
|
|
329
|
+
let modeResult;
|
|
330
|
+
while (true) {
|
|
331
|
+
const editorName = selectedEditors.join(", ");
|
|
332
|
+
const result = await select({
|
|
333
|
+
message: `Configure ${editorName} for Neon:`,
|
|
334
|
+
options: [
|
|
335
|
+
{
|
|
336
|
+
value: "defaults",
|
|
337
|
+
label: "Install with defaults",
|
|
338
|
+
hint: hintParts.join(", ")
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
value: "customize",
|
|
342
|
+
label: "Customize installation",
|
|
343
|
+
hint: canInstallExtension ? "choose scopes and optional editor extension" : "choose scopes and options"
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
value: "change_editor",
|
|
347
|
+
label: "Configure a different editor"
|
|
348
|
+
}
|
|
349
|
+
],
|
|
350
|
+
initialValue: "defaults"
|
|
351
|
+
});
|
|
352
|
+
if (isCancel(result)) {
|
|
353
|
+
outro("Setup cancelled.");
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (result === "change_editor") {
|
|
357
|
+
const availableEditors = await detectAvailableEditors(homeDir);
|
|
358
|
+
const editorResponse = await multiselect({
|
|
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) {
|
|
374
|
+
outro("Setup cancelled.");
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
modeResult = result;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
if (modeResult === "customize") {
|
|
383
|
+
if (needsMcp) {
|
|
384
|
+
const scopeResult = await select({
|
|
385
|
+
message: "Where should the Neon MCP server be configured?",
|
|
386
|
+
options: [
|
|
387
|
+
{
|
|
388
|
+
value: "global",
|
|
389
|
+
label: "Global (available in all projects)"
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
value: "project",
|
|
393
|
+
label: "Project-level (this project only)"
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
value: "none",
|
|
397
|
+
label: "Skip — do not install the MCP server"
|
|
398
|
+
}
|
|
399
|
+
]
|
|
400
|
+
});
|
|
401
|
+
if (isCancel(scopeResult)) {
|
|
402
|
+
outro("Setup cancelled.");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
mcpScope = scopeResult;
|
|
406
|
+
if (mcpScope === "none") needsMcp = false;
|
|
407
|
+
}
|
|
408
|
+
if (needsSkills) {
|
|
409
|
+
const skillsScopeResult = await select({
|
|
410
|
+
message: "Where should Neon agent skills be installed?",
|
|
411
|
+
options: [{
|
|
412
|
+
value: "global",
|
|
413
|
+
label: "Global (available in all projects)"
|
|
414
|
+
}, {
|
|
415
|
+
value: "project",
|
|
416
|
+
label: "Project-level (this project only)"
|
|
417
|
+
}],
|
|
418
|
+
initialValue: "project"
|
|
419
|
+
});
|
|
420
|
+
if (isCancel(skillsScopeResult)) {
|
|
421
|
+
outro("Setup cancelled.");
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
skillsScope = skillsScopeResult;
|
|
425
|
+
}
|
|
426
|
+
if (canInstallExtension) {
|
|
427
|
+
const extResult = await confirm({ message: `Install the Neon extension for ${vscodeEditors.join(", ")}?` });
|
|
428
|
+
if (isCancel(extResult)) {
|
|
429
|
+
outro("Setup cancelled.");
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
doInstallExtension = extResult;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (!await isAuthenticated()) {
|
|
436
|
+
const authS = spinner();
|
|
437
|
+
authS.start("Authenticating with Neon...");
|
|
438
|
+
if (!await ensureNeonctlAuth()) {
|
|
439
|
+
authS.stop("Authentication failed.");
|
|
440
|
+
outro("Run `neon init` again after signing in.");
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
authS.stop("Authenticated.");
|
|
444
|
+
}
|
|
445
|
+
const nctlS = spinner();
|
|
446
|
+
nctlS.start("Checking Neon CLI...");
|
|
447
|
+
const nctlResult = await ensureNeonctl();
|
|
448
|
+
switch (nctlResult.status) {
|
|
449
|
+
case "already_current":
|
|
450
|
+
nctlS.stop(dim(`Neon CLI is up to date (v${nctlResult.version}) ✓`));
|
|
451
|
+
break;
|
|
452
|
+
case "installed":
|
|
453
|
+
nctlS.stop(dim(`Installed Neon CLI (v${nctlResult.version}) ✓`));
|
|
454
|
+
break;
|
|
455
|
+
case "updated":
|
|
456
|
+
nctlS.stop(dim(`Updated Neon CLI to v${nctlResult.version} ✓`));
|
|
457
|
+
break;
|
|
458
|
+
case "failed":
|
|
459
|
+
nctlS.stop("Failed to install Neon CLI");
|
|
460
|
+
log.warn(nctlResult.error ?? "The Neon CLI could not be installed automatically.");
|
|
461
|
+
if (which.sync("npx", { nothrow: true })) log.warn("Setup will continue using npx.");
|
|
462
|
+
}
|
|
463
|
+
for (const editor of selectedEditors) {
|
|
464
|
+
if (needsMcp) {
|
|
465
|
+
const mcpArgs = [
|
|
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");
|
|
476
|
+
const mcpS = spinner();
|
|
477
|
+
mcpS.start(`Installing Neon MCP server for ${editor}...`);
|
|
478
|
+
try {
|
|
479
|
+
await execa("npx", mcpArgs, {
|
|
480
|
+
stdio: "pipe",
|
|
481
|
+
timeout: 6e4
|
|
482
|
+
});
|
|
483
|
+
mcpS.stop(dim(`Neon MCP server configured for ${editor} (${mcpScope}) ✓`));
|
|
484
|
+
} catch (err) {
|
|
485
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
486
|
+
mcpS.stop(`Failed to configure MCP server for ${editor}`);
|
|
487
|
+
log.error(msg);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (needsSkills) await installAgentSkills([editor], {
|
|
491
|
+
scope: skillsScope,
|
|
492
|
+
preview: options.preview
|
|
493
|
+
});
|
|
494
|
+
if (doInstallExtension && usesExtension(editor)) {
|
|
495
|
+
const extS = spinner();
|
|
496
|
+
extS.start(`Installing Neon extension for ${editor}...`);
|
|
497
|
+
if (await installExtension(editor)) extS.stop(dim(`Neon extension installed for ${editor} ✓`));
|
|
498
|
+
else extS.stop(`Extension install failed — install manually from the extensions panel.`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const ide = detectIde();
|
|
503
|
+
const agentForSkills = detectAgent() ?? (ide === "Cursor" ? "cursor" : ide === "VS Code" ? "vscode" : ide === "Windsurf" ? "windsurf" : null);
|
|
504
|
+
if (agentForSkills) {
|
|
505
|
+
const detectedSkillsScope = inspection.skillsScope === "global" ? "global" : void 0;
|
|
506
|
+
await ensureSkillsUpToDate(agentForSkills, detectedSkillsScope, options.preview);
|
|
507
|
+
}
|
|
508
|
+
const gettingStartedData = {};
|
|
509
|
+
if (hasNeonConnection) gettingStartedData.hasConnectionString = true;
|
|
510
|
+
if (inspection.framework && inspection.framework !== "none") gettingStartedData.framework = inspection.framework;
|
|
511
|
+
if (inspection.orm && inspection.orm !== "none") gettingStartedData.orm = inspection.orm;
|
|
512
|
+
if (inspection.migrationTool && inspection.migrationTool !== "none") gettingStartedData.migrationTool = inspection.migrationTool;
|
|
513
|
+
if (inspection.migrationDir && inspection.migrationDir !== "none") gettingStartedData.migrationDir = inspection.migrationDir;
|
|
514
|
+
if (selectedFeatures.length > 0) gettingStartedData.features = selectedFeatures;
|
|
515
|
+
if (options.preview) gettingStartedData.preview = true;
|
|
516
|
+
const cmd = `neon init --agent --data '${JSON.stringify({
|
|
517
|
+
step: "getting-started",
|
|
518
|
+
...gettingStartedData
|
|
519
|
+
})}'`;
|
|
520
|
+
const cols = (process.stdout.columns || 80) - 3;
|
|
521
|
+
const promptText = `To finish setting up Neon using Neon's agent-guided onboarding experience, have your agent run this shell command: ${cmd}`;
|
|
522
|
+
log.step("Next steps");
|
|
523
|
+
log.message(dim("Copy the following into your agent chat:"));
|
|
524
|
+
log.message(wordWrap(promptText, cols).split("\n").map((line) => bold(neonGreenFn(line))).join("\n"));
|
|
525
|
+
outro(dim("Have feedback? Email us at feedback@neon.tech"));
|
|
632
526
|
}
|
|
633
527
|
function agentIdToEditor(agentId) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
return null;
|
|
644
|
-
case "codex":
|
|
645
|
-
return "Codex";
|
|
646
|
-
case "cline":
|
|
647
|
-
return "Cline";
|
|
648
|
-
default:
|
|
649
|
-
return null;
|
|
650
|
-
}
|
|
528
|
+
switch (agentId) {
|
|
529
|
+
case "cursor": return "Cursor";
|
|
530
|
+
case "vscode": return "VS Code";
|
|
531
|
+
case "claude-code": return "Claude CLI";
|
|
532
|
+
case "windsurf": return null;
|
|
533
|
+
case "codex": return "Codex";
|
|
534
|
+
case "cline": return "Cline";
|
|
535
|
+
default: return null;
|
|
536
|
+
}
|
|
651
537
|
}
|
|
538
|
+
//#endregion
|
|
539
|
+
export { interactiveInit };
|