neon 4.7.0 → 4.9.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 +49 -59
- package/dist/_chunks/{auth_selection-ktL6uoEe.js → auth_selection-pitMFzDe.js} +1 -1
- package/dist/_chunks/{credential_io-B1JxWELN.js → credential_io-YeAxg9Xn.js} +2 -2
- package/dist/_chunks/{paths-g3DRqJiD.js → paths-DMq0Lt7a.js} +1 -1
- package/dist/_chunks/{profiles-D95AeZ5v.js → profiles-CvnFEQyd.js} +1 -1
- package/dist/analytics.js +2 -2
- package/dist/auth_context.js +1 -1
- package/dist/commands/auth.js +12 -9
- package/dist/commands/bootstrap.js +72 -43
- package/dist/commands/branches.js +29 -14
- package/dist/commands/claim.js +1 -1
- package/dist/commands/init.js +80 -79
- package/dist/commands/link.js +78 -63
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/plugins.js +4 -4
- package/dist/commands/profile.js +4 -4
- package/dist/commands/projects.js +17 -6
- package/dist/commands/skills.js +1 -1
- package/dist/config.js +1 -1
- package/dist/credential_io.js +1 -1
- package/dist/index.js +1 -1
- package/dist/init/child.js +36 -0
- package/dist/init/plan.js +47 -0
- package/dist/init/tooling.js +43 -0
- package/dist/init/wizard.js +42 -0
- package/dist/mcp/targets.js +1 -1
- package/dist/plugins/plan.js +1 -1
- package/dist/plugins/targets.js +5 -1
- package/dist/retire_credential.js +1 -1
- package/dist/utils/middlewares.js +1 -1
- package/dist/utils/package_manager.js +10 -5
- package/package.json +15 -16
- package/dist/init/auth.js +0 -53
- package/dist/init/build_config.js +0 -4
- package/dist/init/detect_agent.js +0 -63
- package/dist/init/editors.js +0 -19
- package/dist/init/enrich_output.js +0 -61
- package/dist/init/extension.js +0 -154
- package/dist/init/inspect.js +0 -249
- package/dist/init/interactive.js +0 -529
- package/dist/init/neonctl.js +0 -139
- package/dist/init/orchestrate.js +0 -189
- package/dist/init/phases/auth.js +0 -195
- package/dist/init/phases/cleanup.js +0 -27
- package/dist/init/phases/db.js +0 -257
- package/dist/init/phases/getting_started.js +0 -218
- package/dist/init/phases/mcp.js +0 -219
- package/dist/init/phases/migrations.js +0 -250
- package/dist/init/phases/neon_auth.js +0 -116
- package/dist/init/phases/setup.js +0 -582
- package/dist/init/phases/skills.js +0 -86
- package/dist/init/phases/status.js +0 -82
- package/dist/init/profile_cli.js +0 -56
- package/dist/init/resolve_context.js +0 -105
- package/dist/init/route_command.js +0 -93
- package/dist/init/skills.js +0 -206
- package/dist/init/types.js +0 -1
- package/dist/init/vsix.js +0 -89
package/dist/init/orchestrate.js
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { tryResolveAddMcpAgentId } from "../mcp/agents.js";
|
|
2
|
-
import { supportsSkills } from "./agents.js";
|
|
3
|
-
import { isAuthenticated } from "./auth.js";
|
|
4
|
-
import { inspectProject } from "./inspect.js";
|
|
5
|
-
import { getSkillList, missingSkillsForAgent, skillsInstalledForAgent } from "./skills.js";
|
|
6
|
-
import { handleAuthPhase } from "./phases/auth.js";
|
|
7
|
-
import { handleGettingStartedPhase } from "./phases/getting_started.js";
|
|
8
|
-
import { handleMigrationsPhase } from "./phases/migrations.js";
|
|
9
|
-
import { handleNeonAuthPhase } from "./phases/neon_auth.js";
|
|
10
|
-
import { handleSetupPhase } from "./phases/setup.js";
|
|
11
|
-
import { resolveNeonContext } from "./resolve_context.js";
|
|
12
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
-
import { resolve } from "node:path";
|
|
14
|
-
//#region src/init/orchestrate.ts
|
|
15
|
-
/**
|
|
16
|
-
* v2 orchestrator: checks phases in order and returns the first that needs attention.
|
|
17
|
-
*
|
|
18
|
-
* Phase order:
|
|
19
|
-
* auth -> setup (if tooling not installed)
|
|
20
|
-
* -> getting-started (if tooling installed but no Neon connection string)
|
|
21
|
-
* -> resolve .neon context (if connection string exists but no .neon file)
|
|
22
|
-
* -> neon_auth (optional) -> complete
|
|
23
|
-
*
|
|
24
|
-
* Each call is stateless — it re-checks everything from the file system and credentials.
|
|
25
|
-
*
|
|
26
|
-
* The orchestrator uses filesystem inspection to decide what to do:
|
|
27
|
-
* - No app detected → bootstrap phase (scaffold from template)
|
|
28
|
-
* - MCP not configured → full setup flow (inspect → install → getting-started)
|
|
29
|
-
* - MCP configured, no connection string → skip install, go to getting-started
|
|
30
|
-
* - MCP configured + connection string → fall through to neon-auth/migrations/complete
|
|
31
|
-
*/
|
|
32
|
-
async function orchestrate(options) {
|
|
33
|
-
if (!await isAuthenticated()) return handleAuthPhase({ agent: options.agent });
|
|
34
|
-
const cwd = process.cwd();
|
|
35
|
-
const inspection = await inspectProject([
|
|
36
|
-
{
|
|
37
|
-
id: "has_app",
|
|
38
|
-
description: "",
|
|
39
|
-
lookFor: []
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
id: "mcp_server",
|
|
43
|
-
description: "",
|
|
44
|
-
lookFor: []
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
id: "skills",
|
|
48
|
-
description: "",
|
|
49
|
-
lookFor: []
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
id: "connection_string",
|
|
53
|
-
description: "",
|
|
54
|
-
lookFor: []
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: "project_stack",
|
|
58
|
-
description: "",
|
|
59
|
-
lookFor: []
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
id: "migrations",
|
|
63
|
-
description: "",
|
|
64
|
-
lookFor: []
|
|
65
|
-
}
|
|
66
|
-
]);
|
|
67
|
-
const hasApp = options.preview ? inspection.hasApp === true : true;
|
|
68
|
-
const requestedAgent = options.agent ? tryResolveAddMcpAgentId(options.agent) : void 0;
|
|
69
|
-
let skillsInstalled = inspection.skillsInstalled;
|
|
70
|
-
if (options.preview && requestedAgent && supportsSkills(requestedAgent)) {
|
|
71
|
-
const missing = missingSkillsForAgent(requestedAgent, {
|
|
72
|
-
cwd,
|
|
73
|
-
preview: true
|
|
74
|
-
});
|
|
75
|
-
const required = getSkillList(true);
|
|
76
|
-
if (missing.length > 0) {
|
|
77
|
-
skillsInstalled = false;
|
|
78
|
-
if (missing.length < required.length) inspection.skillsScope = `${inspection.skillsScope && !String(inspection.skillsScope).includes("partial") ? inspection.skillsScope : "project"}-partial`;
|
|
79
|
-
}
|
|
80
|
-
} else if (options.preview && skillsInstalled && inspection.skillsScope) {
|
|
81
|
-
const previewSkills = getSkillList(true);
|
|
82
|
-
const { existsSync: exists } = await import("node:fs");
|
|
83
|
-
const { resolve: resolvePath } = await import("node:path");
|
|
84
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
85
|
-
const dirs = inspection.skillsScope === "project" ? [
|
|
86
|
-
resolvePath(cwd, ".cursor", "skills"),
|
|
87
|
-
resolvePath(cwd, ".claude", "skills"),
|
|
88
|
-
resolvePath(cwd, ".agents", "skills")
|
|
89
|
-
] : [
|
|
90
|
-
resolvePath(home, ".cursor", "skills"),
|
|
91
|
-
resolvePath(home, ".claude", "skills"),
|
|
92
|
-
resolvePath(home, ".agents", "skills")
|
|
93
|
-
];
|
|
94
|
-
if (!previewSkills.every((skill) => dirs.some((dir) => exists(resolvePath(dir, skill, "SKILL.md"))))) {
|
|
95
|
-
skillsInstalled = false;
|
|
96
|
-
inspection.skillsScope = `${inspection.skillsScope}-partial`;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
const mcpForThisAgent = requestedAgent ? (inspection.mcpAgents ?? []).some((hit) => hit.agent === requestedAgent) : inspection.mcpConfigured === true;
|
|
100
|
-
const skillsReady = requestedAgent && !supportsSkills(requestedAgent) ? true : requestedAgent ? skillsInstalledForAgent(requestedAgent, cwd, options.preview) : skillsInstalled === true;
|
|
101
|
-
const toolingInstalled = mcpForThisAgent && skillsReady;
|
|
102
|
-
const hasNeonConnection = inspection.connectionString === true;
|
|
103
|
-
if (!hasApp || !toolingInstalled) {
|
|
104
|
-
cleanupInitState(resolve(cwd, ".neon"));
|
|
105
|
-
return handleSetupPhase({
|
|
106
|
-
agent: options.agent,
|
|
107
|
-
preview: options.preview,
|
|
108
|
-
hasApp,
|
|
109
|
-
mcpConfigured: requestedAgent ? mcpForThisAgent : inspection.mcpConfigured ?? null,
|
|
110
|
-
mcpScope: inspection.mcpScope || void 0,
|
|
111
|
-
skillsInstalled: requestedAgent ? skillsReady : skillsInstalled ?? null,
|
|
112
|
-
skillsScope: inspection.skillsScope || void 0
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
const neonContextPath = resolve(cwd, ".neon");
|
|
116
|
-
const neonContext = readNeonContext(neonContextPath);
|
|
117
|
-
const initState = typeof neonContext._init === "object" && neonContext._init !== null ? neonContext._init : {};
|
|
118
|
-
const features = Array.isArray(initState.features) ? initState.features : [];
|
|
119
|
-
if (!hasNeonConnection) return handleGettingStartedPhase({
|
|
120
|
-
agent: options.agent,
|
|
121
|
-
cwd,
|
|
122
|
-
hasConnectionString: false,
|
|
123
|
-
framework: inspection.framework,
|
|
124
|
-
orm: inspection.orm,
|
|
125
|
-
migrationTool: inspection.migrationTool,
|
|
126
|
-
migrationDir: inspection.migrationDir,
|
|
127
|
-
features,
|
|
128
|
-
preview: options.preview
|
|
129
|
-
});
|
|
130
|
-
if (!neonContext.projectId) try {
|
|
131
|
-
const resolved = await resolveNeonContext(cwd);
|
|
132
|
-
if (resolved) {
|
|
133
|
-
const merged = { ...neonContext };
|
|
134
|
-
if (resolved.orgId) merged.orgId = resolved.orgId;
|
|
135
|
-
if (resolved.projectId) merged.projectId = resolved.projectId;
|
|
136
|
-
writeFileSync(neonContextPath, `${JSON.stringify(merged, null, 2)}\n`);
|
|
137
|
-
}
|
|
138
|
-
} catch {}
|
|
139
|
-
if (features.includes("auth")) {
|
|
140
|
-
if (!checkNeonAuth(cwd)) return handleNeonAuthPhase({
|
|
141
|
-
agent: options.agent,
|
|
142
|
-
setup: true
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
if (!options.skipMigrations) return handleMigrationsPhase({
|
|
146
|
-
agent: options.agent,
|
|
147
|
-
cwd
|
|
148
|
-
});
|
|
149
|
-
cleanupInitState(neonContextPath);
|
|
150
|
-
return {
|
|
151
|
-
phase: "setup",
|
|
152
|
-
status: "complete",
|
|
153
|
-
nextAction: {
|
|
154
|
-
type: "complete",
|
|
155
|
-
message: requestedAgent && !supportsSkills(requestedAgent) ? "Neon setup is complete. Your database is configured and your agent has the Neon MCP server available." : "Neon setup is complete. Your database is configured and your agent has the Neon MCP server and skills available."
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
/** Remove the ephemeral _init key from .neon, preserving other fields. */
|
|
160
|
-
function cleanupInitState(neonContextPath) {
|
|
161
|
-
if (!existsSync(neonContextPath)) return;
|
|
162
|
-
try {
|
|
163
|
-
const context = JSON.parse(readFileSync(neonContextPath, "utf-8"));
|
|
164
|
-
if (context._init !== void 0) {
|
|
165
|
-
delete context._init;
|
|
166
|
-
writeFileSync(neonContextPath, `${JSON.stringify(context, null, 2)}\n`);
|
|
167
|
-
}
|
|
168
|
-
} catch {}
|
|
169
|
-
}
|
|
170
|
-
function readNeonContext(neonContextPath) {
|
|
171
|
-
if (!existsSync(neonContextPath)) return {};
|
|
172
|
-
try {
|
|
173
|
-
return JSON.parse(readFileSync(neonContextPath, "utf-8"));
|
|
174
|
-
} catch {
|
|
175
|
-
return {};
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function checkNeonAuth(cwd) {
|
|
179
|
-
const envPath = resolve(cwd, ".env");
|
|
180
|
-
if (!existsSync(envPath)) return false;
|
|
181
|
-
try {
|
|
182
|
-
const content = readFileSync(envPath, "utf-8");
|
|
183
|
-
return /^NEON_AUTH_/m.test(content);
|
|
184
|
-
} catch {
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
//#endregion
|
|
189
|
-
export { orchestrate };
|
package/dist/init/phases/auth.js
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import { isAuthenticated } from "../auth.js";
|
|
2
|
-
import { neonctlCmd } from "../neonctl.js";
|
|
3
|
-
//#region src/init/phases/auth.ts
|
|
4
|
-
function getSignupUrl() {
|
|
5
|
-
return process.env.NEON_API_HOST ? `${new URL(process.env.NEON_API_HOST).origin}/signup` : "https://console.neon.tech/signup";
|
|
6
|
-
}
|
|
7
|
-
function getSignupCommands() {
|
|
8
|
-
const url = getSignupUrl();
|
|
9
|
-
return {
|
|
10
|
-
darwin: `open ${url}`,
|
|
11
|
-
linux: `xdg-open ${url}`,
|
|
12
|
-
win32: `start ${url}`
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
async function handleAuthPhase(options) {
|
|
16
|
-
const agentArgs = options.agent ? [
|
|
17
|
-
"--agent",
|
|
18
|
-
options.agent,
|
|
19
|
-
"--json"
|
|
20
|
-
] : ["--json"];
|
|
21
|
-
if (options.verify) {
|
|
22
|
-
if (await isAuthenticated()) return {
|
|
23
|
-
phase: "auth",
|
|
24
|
-
status: "verified",
|
|
25
|
-
nextAction: {
|
|
26
|
-
type: "run_neon_init",
|
|
27
|
-
args: agentArgs
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
return {
|
|
31
|
-
phase: "auth",
|
|
32
|
-
status: "not_authenticated",
|
|
33
|
-
nextAction: {
|
|
34
|
-
type: "run_neon_init",
|
|
35
|
-
args: ["auth", "--json"]
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
if (await isAuthenticated()) return {
|
|
40
|
-
phase: "auth",
|
|
41
|
-
status: "verified",
|
|
42
|
-
nextAction: {
|
|
43
|
-
type: "run_neon_init",
|
|
44
|
-
args: agentArgs
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
if (options.method === "new") return {
|
|
48
|
-
phase: "auth",
|
|
49
|
-
status: "in_progress",
|
|
50
|
-
nextAction: {
|
|
51
|
-
type: "agent_action",
|
|
52
|
-
steps: [{
|
|
53
|
-
id: "open_signup",
|
|
54
|
-
description: "Open the Neon sign-up page in the user's browser",
|
|
55
|
-
command: getSignupCommands()[process.platform] ?? getSignupCommands().linux
|
|
56
|
-
}, {
|
|
57
|
-
id: "wait_for_signup",
|
|
58
|
-
description: "Tell the user: 'I've opened the Neon sign-up page. Create your account and verify your email, then let me know when you're ready.'"
|
|
59
|
-
}],
|
|
60
|
-
onComplete: {
|
|
61
|
-
type: "run_neon_init",
|
|
62
|
-
args: [
|
|
63
|
-
"auth",
|
|
64
|
-
"--json",
|
|
65
|
-
"--method",
|
|
66
|
-
"existing"
|
|
67
|
-
]
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
if (options.method === "existing") return {
|
|
72
|
-
phase: "auth",
|
|
73
|
-
status: "in_progress",
|
|
74
|
-
nextAction: {
|
|
75
|
-
type: "run_command",
|
|
76
|
-
command: `${neonctlCmd()} auth`,
|
|
77
|
-
description: "This will open your browser for Neon OAuth sign-in.",
|
|
78
|
-
timeout: 12e4,
|
|
79
|
-
onSuccess: {
|
|
80
|
-
type: "run_neon_init",
|
|
81
|
-
args: [
|
|
82
|
-
"auth",
|
|
83
|
-
"--json",
|
|
84
|
-
"--verify"
|
|
85
|
-
]
|
|
86
|
-
},
|
|
87
|
-
onFailure: {
|
|
88
|
-
"2": {
|
|
89
|
-
type: "ask_user",
|
|
90
|
-
question: "The sign-in timed out. Did you complete the sign-in in your browser?",
|
|
91
|
-
options: ["yes_retry", "need_help"],
|
|
92
|
-
responseMapping: {
|
|
93
|
-
yes_retry: { args: [
|
|
94
|
-
"auth",
|
|
95
|
-
"--json",
|
|
96
|
-
"--method",
|
|
97
|
-
"existing"
|
|
98
|
-
] },
|
|
99
|
-
need_help: { args: [
|
|
100
|
-
"auth",
|
|
101
|
-
"--json",
|
|
102
|
-
"--method",
|
|
103
|
-
"new"
|
|
104
|
-
] }
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
other: {
|
|
108
|
-
type: "run_neon_init",
|
|
109
|
-
args: ["auth", "--json"]
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
const openCmd = getSignupCommands()[process.platform] ?? getSignupCommands().linux;
|
|
115
|
-
return {
|
|
116
|
-
phase: "auth",
|
|
117
|
-
status: "required",
|
|
118
|
-
nextAction: {
|
|
119
|
-
type: "ask_user",
|
|
120
|
-
instructions: "IMPORTANT: You MUST present this question to the user and WAIT for their response before proceeding. Do NOT assume the answer or auto-select an option. The user's answer determines the next step.",
|
|
121
|
-
question: "Do you have an existing Neon account, or do you need to create one?",
|
|
122
|
-
options: [{
|
|
123
|
-
value: "existing_account",
|
|
124
|
-
label: "I have an existing Neon account"
|
|
125
|
-
}, {
|
|
126
|
-
value: "new_account",
|
|
127
|
-
label: "I need to create a new account"
|
|
128
|
-
}],
|
|
129
|
-
context: "Neon is a complete set of cloud backend primitives built around Lakebase Postgres. A free account is required to continue.",
|
|
130
|
-
responseMapping: {
|
|
131
|
-
existing_account: { action: {
|
|
132
|
-
type: "run_command",
|
|
133
|
-
command: `${neonctlCmd()} auth`,
|
|
134
|
-
description: "This will open your browser for Neon OAuth sign-in.",
|
|
135
|
-
timeout: 12e4,
|
|
136
|
-
onSuccess: {
|
|
137
|
-
type: "run_neon_init",
|
|
138
|
-
args: [
|
|
139
|
-
"auth",
|
|
140
|
-
"--json",
|
|
141
|
-
"--verify"
|
|
142
|
-
]
|
|
143
|
-
},
|
|
144
|
-
onFailure: {
|
|
145
|
-
"2": {
|
|
146
|
-
type: "ask_user",
|
|
147
|
-
question: "The sign-in timed out. Did you complete the sign-in in your browser?",
|
|
148
|
-
options: ["yes_retry", "need_help"],
|
|
149
|
-
responseMapping: {
|
|
150
|
-
yes_retry: { args: [
|
|
151
|
-
"auth",
|
|
152
|
-
"--json",
|
|
153
|
-
"--method",
|
|
154
|
-
"existing"
|
|
155
|
-
] },
|
|
156
|
-
need_help: { args: [
|
|
157
|
-
"auth",
|
|
158
|
-
"--json",
|
|
159
|
-
"--method",
|
|
160
|
-
"new"
|
|
161
|
-
] }
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
other: {
|
|
165
|
-
type: "run_neon_init",
|
|
166
|
-
args: ["auth", "--json"]
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
} },
|
|
170
|
-
new_account: { action: {
|
|
171
|
-
type: "agent_action",
|
|
172
|
-
steps: [{
|
|
173
|
-
id: "open_signup",
|
|
174
|
-
description: "Open the Neon sign-up page in the user's browser",
|
|
175
|
-
command: openCmd
|
|
176
|
-
}, {
|
|
177
|
-
id: "wait_for_signup",
|
|
178
|
-
description: "Tell the user: 'I've opened the Neon sign-up page. Create your account and verify your email, then let me know when you're ready.'"
|
|
179
|
-
}],
|
|
180
|
-
onComplete: {
|
|
181
|
-
type: "run_neon_init",
|
|
182
|
-
args: [
|
|
183
|
-
"auth",
|
|
184
|
-
"--json",
|
|
185
|
-
"--method",
|
|
186
|
-
"existing"
|
|
187
|
-
]
|
|
188
|
-
}
|
|
189
|
-
} }
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
//#endregion
|
|
195
|
-
export { handleAuthPhase };
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
//#region src/init/phases/cleanup.ts
|
|
4
|
-
/**
|
|
5
|
-
* Terminal phase: cleans up ephemeral _init state from .neon and
|
|
6
|
-
* returns a complete message. All feature chains should end here.
|
|
7
|
-
*/
|
|
8
|
-
function handleCleanup() {
|
|
9
|
-
const neonContextPath = resolve(process.cwd(), ".neon");
|
|
10
|
-
if (existsSync(neonContextPath)) try {
|
|
11
|
-
const context = JSON.parse(readFileSync(neonContextPath, "utf-8"));
|
|
12
|
-
if (context._init !== void 0) {
|
|
13
|
-
delete context._init;
|
|
14
|
-
writeFileSync(neonContextPath, `${JSON.stringify(context, null, 2)}\n`);
|
|
15
|
-
}
|
|
16
|
-
} catch {}
|
|
17
|
-
return {
|
|
18
|
-
phase: "setup",
|
|
19
|
-
status: "complete",
|
|
20
|
-
nextAction: {
|
|
21
|
-
type: "complete",
|
|
22
|
-
message: "Neon setup is complete! Your database is connected and your agent has the Neon MCP server and skills available."
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
//#endregion
|
|
27
|
-
export { handleCleanup };
|
package/dist/init/phases/db.js
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
import { neonctlCmd } from "../neonctl.js";
|
|
2
|
-
import { SKILL_REFERENCE_URLS } from "../skills.js";
|
|
3
|
-
//#region src/init/phases/db.ts
|
|
4
|
-
/**
|
|
5
|
-
* Validates that an ID contains only safe characters for shell interpolation.
|
|
6
|
-
* Neon org/project IDs are typically UUIDs or slug-like strings.
|
|
7
|
-
*/
|
|
8
|
-
function assertSafeId(value, label) {
|
|
9
|
-
if (!/^[\w.:-]+$/.test(value)) throw new Error(`Invalid ${label}: "${value}". Expected alphanumeric, hyphens, underscores, dots, or colons.`);
|
|
10
|
-
}
|
|
11
|
-
async function handleDbPhase(options) {
|
|
12
|
-
const agentArgs = options.agent ? [
|
|
13
|
-
"--agent",
|
|
14
|
-
options.agent,
|
|
15
|
-
"--json"
|
|
16
|
-
] : ["--json"];
|
|
17
|
-
if (options.projectId) assertSafeId(options.projectId, "project ID");
|
|
18
|
-
if (options.orgId) assertSafeId(options.orgId, "org ID");
|
|
19
|
-
if (options.error) return {
|
|
20
|
-
phase: "db",
|
|
21
|
-
status: "error",
|
|
22
|
-
error: options.error,
|
|
23
|
-
nextAction: {
|
|
24
|
-
type: "ask_user",
|
|
25
|
-
question: `An error occurred during database setup: ${options.error}. Would you like to try again or skip this step?`,
|
|
26
|
-
options: [{
|
|
27
|
-
value: "retry",
|
|
28
|
-
label: "Try again"
|
|
29
|
-
}, {
|
|
30
|
-
value: "skip",
|
|
31
|
-
label: "Skip database setup"
|
|
32
|
-
}],
|
|
33
|
-
responseMapping: {
|
|
34
|
-
retry: { args: ["db", "--json"] },
|
|
35
|
-
skip: { args: agentArgs }
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
if (options.projectId) return {
|
|
40
|
-
phase: "db",
|
|
41
|
-
status: "project_ready",
|
|
42
|
-
project: { id: options.projectId },
|
|
43
|
-
nextAction: {
|
|
44
|
-
type: "agent_action",
|
|
45
|
-
prerequisite: SKILL_REFERENCE_URLS.connectionMethods,
|
|
46
|
-
steps: [
|
|
47
|
-
{
|
|
48
|
-
id: "get_connection_string",
|
|
49
|
-
description: "Get the database connection string",
|
|
50
|
-
command: `${neonctlCmd()} connection-string --project-id ${options.projectId}`
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
id: "store_env",
|
|
54
|
-
description: "Append DATABASE_URL=<connection_string> to .env. Create .env if it doesn't exist. Do NOT overwrite existing entries. Ensure .env is in .gitignore."
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: "detect_framework",
|
|
58
|
-
description: "Examine the project to determine the framework (Next.js, Remix, Express, etc.) and ORM (Prisma, Drizzle, raw SQL) in use."
|
|
59
|
-
}
|
|
60
|
-
],
|
|
61
|
-
onComplete: {
|
|
62
|
-
type: "run_neon_init",
|
|
63
|
-
args: agentArgs
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
if (options.projectsResult) {
|
|
68
|
-
let projects;
|
|
69
|
-
try {
|
|
70
|
-
const parsed = JSON.parse(options.projectsResult);
|
|
71
|
-
projects = Array.isArray(parsed.projects) ? parsed.projects : Array.isArray(parsed) ? parsed : [];
|
|
72
|
-
} catch {
|
|
73
|
-
projects = [];
|
|
74
|
-
}
|
|
75
|
-
const orgIdArgs = options.orgId ? ["--org-id", options.orgId] : [];
|
|
76
|
-
if (projects.length === 0) return {
|
|
77
|
-
phase: "db",
|
|
78
|
-
status: "no_projects",
|
|
79
|
-
nextAction: {
|
|
80
|
-
type: "ask_user",
|
|
81
|
-
question: "You don't have any Neon projects yet. What would you like to name your new project?",
|
|
82
|
-
options: [{
|
|
83
|
-
value: "create",
|
|
84
|
-
label: "Create a new project (provide a name)"
|
|
85
|
-
}],
|
|
86
|
-
context: `The agent should ask the user for a project name, then run: ${neonctlCmd()} projects create --name <name> --output json` + (options.orgId ? ` --org-id ${options.orgId}` : "") + " and pass the result back.",
|
|
87
|
-
responseMapping: { create: { args: [
|
|
88
|
-
"db",
|
|
89
|
-
"--json",
|
|
90
|
-
...orgIdArgs,
|
|
91
|
-
"--project-id",
|
|
92
|
-
"<created-project-id>"
|
|
93
|
-
] } }
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
const projectOptions = projects.map((p) => ({
|
|
97
|
-
value: p.id,
|
|
98
|
-
label: `${p.name} (${p.id})`
|
|
99
|
-
}));
|
|
100
|
-
projectOptions.push({
|
|
101
|
-
value: "create_new",
|
|
102
|
-
label: "Create a new project"
|
|
103
|
-
});
|
|
104
|
-
const responseMapping = {};
|
|
105
|
-
for (const p of projects) responseMapping[p.id] = { args: [
|
|
106
|
-
"db",
|
|
107
|
-
"--json",
|
|
108
|
-
...orgIdArgs,
|
|
109
|
-
"--project-id",
|
|
110
|
-
p.id
|
|
111
|
-
] };
|
|
112
|
-
responseMapping.create_new = { args: [
|
|
113
|
-
"db",
|
|
114
|
-
"--json",
|
|
115
|
-
...orgIdArgs,
|
|
116
|
-
"--project-id",
|
|
117
|
-
"<created-project-id>"
|
|
118
|
-
] };
|
|
119
|
-
return {
|
|
120
|
-
phase: "db",
|
|
121
|
-
status: "select_project",
|
|
122
|
-
nextAction: {
|
|
123
|
-
type: "ask_user",
|
|
124
|
-
question: "Which Neon project would you like to use?",
|
|
125
|
-
options: projectOptions,
|
|
126
|
-
context: `If the user wants to create a new project, ask for a name then run: ${neonctlCmd()} projects create --name <name> --output json and use the returned project id.`,
|
|
127
|
-
responseMapping
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
if (options.orgsResult) {
|
|
132
|
-
let orgs;
|
|
133
|
-
try {
|
|
134
|
-
const parsed = JSON.parse(options.orgsResult);
|
|
135
|
-
orgs = Array.isArray(parsed.organizations) ? parsed.organizations : Array.isArray(parsed) ? parsed : [];
|
|
136
|
-
} catch {
|
|
137
|
-
orgs = [];
|
|
138
|
-
}
|
|
139
|
-
const orgId = options.orgId ?? (orgs.length === 1 ? orgs[0].id : null);
|
|
140
|
-
if (orgId) {
|
|
141
|
-
assertSafeId(orgId, "org ID");
|
|
142
|
-
return {
|
|
143
|
-
phase: "db",
|
|
144
|
-
status: "org_selected",
|
|
145
|
-
org: { id: orgId },
|
|
146
|
-
nextAction: {
|
|
147
|
-
type: "run_command",
|
|
148
|
-
command: `${neonctlCmd()} projects list --org-id ${orgId} --output json`,
|
|
149
|
-
description: "Listing Neon projects.",
|
|
150
|
-
timeout: 3e4,
|
|
151
|
-
onSuccess: {
|
|
152
|
-
type: "run_neon_init",
|
|
153
|
-
args: [
|
|
154
|
-
"db",
|
|
155
|
-
"--json",
|
|
156
|
-
"--org-id",
|
|
157
|
-
orgId,
|
|
158
|
-
"--projects-result",
|
|
159
|
-
"<stdout>"
|
|
160
|
-
]
|
|
161
|
-
},
|
|
162
|
-
onFailure: { other: {
|
|
163
|
-
type: "run_neon_init",
|
|
164
|
-
args: [
|
|
165
|
-
"db",
|
|
166
|
-
"--json",
|
|
167
|
-
"--error",
|
|
168
|
-
"projects-list-failed"
|
|
169
|
-
]
|
|
170
|
-
} }
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
const orgOptions = orgs.map((o) => ({
|
|
175
|
-
value: o.id,
|
|
176
|
-
label: `${o.name} (${o.id})`
|
|
177
|
-
}));
|
|
178
|
-
const responseMapping = {};
|
|
179
|
-
for (const o of orgs) responseMapping[o.id] = { args: [
|
|
180
|
-
"db",
|
|
181
|
-
"--json",
|
|
182
|
-
"--org-id",
|
|
183
|
-
o.id
|
|
184
|
-
] };
|
|
185
|
-
return {
|
|
186
|
-
phase: "db",
|
|
187
|
-
status: "select_org",
|
|
188
|
-
nextAction: {
|
|
189
|
-
type: "ask_user",
|
|
190
|
-
question: "Which Neon organization would you like to use?",
|
|
191
|
-
options: orgOptions,
|
|
192
|
-
responseMapping
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
if (options.orgId) return {
|
|
197
|
-
phase: "db",
|
|
198
|
-
status: "org_selected",
|
|
199
|
-
org: { id: options.orgId },
|
|
200
|
-
nextAction: {
|
|
201
|
-
type: "run_command",
|
|
202
|
-
command: `${neonctlCmd()} projects list --org-id ${options.orgId} --output json`,
|
|
203
|
-
description: "Listing Neon projects.",
|
|
204
|
-
timeout: 3e4,
|
|
205
|
-
onSuccess: {
|
|
206
|
-
type: "run_neon_init",
|
|
207
|
-
args: [
|
|
208
|
-
"db",
|
|
209
|
-
"--json",
|
|
210
|
-
"--org-id",
|
|
211
|
-
options.orgId,
|
|
212
|
-
"--projects-result",
|
|
213
|
-
"<stdout>"
|
|
214
|
-
]
|
|
215
|
-
},
|
|
216
|
-
onFailure: { other: {
|
|
217
|
-
type: "run_neon_init",
|
|
218
|
-
args: [
|
|
219
|
-
"db",
|
|
220
|
-
"--json",
|
|
221
|
-
"--error",
|
|
222
|
-
"projects-list-failed"
|
|
223
|
-
]
|
|
224
|
-
} }
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
return {
|
|
228
|
-
phase: "db",
|
|
229
|
-
status: "ready",
|
|
230
|
-
nextAction: {
|
|
231
|
-
type: "run_command",
|
|
232
|
-
command: `${neonctlCmd()} orgs list --output json`,
|
|
233
|
-
description: "Listing your Neon organizations.",
|
|
234
|
-
timeout: 3e4,
|
|
235
|
-
onSuccess: {
|
|
236
|
-
type: "run_neon_init",
|
|
237
|
-
args: [
|
|
238
|
-
"db",
|
|
239
|
-
"--json",
|
|
240
|
-
"--orgs-result",
|
|
241
|
-
"<stdout>"
|
|
242
|
-
]
|
|
243
|
-
},
|
|
244
|
-
onFailure: { other: {
|
|
245
|
-
type: "run_neon_init",
|
|
246
|
-
args: [
|
|
247
|
-
"db",
|
|
248
|
-
"--json",
|
|
249
|
-
"--error",
|
|
250
|
-
"orgs-list-failed"
|
|
251
|
-
]
|
|
252
|
-
} }
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
//#endregion
|
|
257
|
-
export { handleDbPhase };
|