neon-init 0.17.0 → 0.17.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/dist/cli.js +36 -25
- package/dist/cli.js.map +1 -1
- package/dist/interactive.js +15 -9
- package/dist/interactive.js.map +1 -1
- package/dist/lib/agents.js +1 -1
- package/dist/lib/agents.js.map +1 -1
- package/dist/lib/enrich-output.js +1 -1
- package/dist/lib/enrich-output.js.map +1 -1
- package/dist/lib/inspect.d.ts +4 -1
- package/dist/lib/inspect.d.ts.map +1 -1
- package/dist/lib/inspect.js +36 -30
- package/dist/lib/inspect.js.map +1 -1
- package/dist/lib/neonctl.d.ts.map +1 -1
- package/dist/lib/neonctl.js +21 -0
- package/dist/lib/neonctl.js.map +1 -1
- package/dist/lib/phases/setup.d.ts +6 -3
- package/dist/lib/phases/setup.d.ts.map +1 -1
- package/dist/lib/phases/setup.js +52 -169
- package/dist/lib/phases/setup.js.map +1 -1
- package/dist/lib/phases/status.js +18 -6
- package/dist/lib/phases/status.js.map +1 -1
- package/dist/lib/route-command.d.ts +2 -2
- package/dist/lib/route-command.d.ts.map +1 -1
- package/dist/lib/route-command.js +13 -13
- package/dist/lib/route-command.js.map +1 -1
- package/dist/lib/skills.d.ts +5 -2
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +88 -36
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/types.d.ts +2 -0
- package/dist/v2.d.ts +0 -1
- package/dist/v2.d.ts.map +1 -1
- package/dist/v2.js +30 -5
- package/dist/v2.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enrich-output.js","names":[],"sources":["../../src/lib/enrich-output.ts"],"sourcesContent":["/**\n * Converts neon-init args (e.g. [\"neon-auth\", \"--json\", \"--setup\"]) to a\n * neonctl init --data command using the step routing pattern.\n */\nfunction argsToCommand(args: string[]): string {\n\tconst data: Record<string, unknown> = {};\n\tlet i = 0;\n\n\t// First non-flag arg is the subcommand → step\n\tif (args.length > 0 && !args[0].startsWith(\"-\")) {\n\t\tdata.step = args[0];\n\t\ti = 1;\n\t}\n\n\twhile (i < args.length) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--\")) {\n\t\t\tconst key = arg\n\t\t\t\t.slice(2)\n\t\t\t\t.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\n\t\t\tif (key === \"json\") {\n\t\t\t\ti += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst next = args[i + 1];\n\t\t\tif (next !== undefined && !next.startsWith(\"-\")) {\n\t\t\t\tdata[key] = next;\n\t\t\t\ti += 2;\n\t\t\t} else {\n\t\t\t\tdata[key] = true;\n\t\t\t\ti += 1;\n\t\t\t}\n\t\t} else {\n\t\t\ti += 1;\n\t\t}\n\t}\n\n\treturn `neonctl init --agent --
|
|
1
|
+
{"version":3,"file":"enrich-output.js","names":[],"sources":["../../src/lib/enrich-output.ts"],"sourcesContent":["/**\n * Converts neon-init args (e.g. [\"neon-auth\", \"--json\", \"--setup\"]) to a\n * neonctl init --data command using the step routing pattern.\n */\nfunction argsToCommand(args: string[]): string {\n\tconst data: Record<string, unknown> = {};\n\tlet i = 0;\n\n\t// First non-flag arg is the subcommand → step\n\tif (args.length > 0 && !args[0].startsWith(\"-\")) {\n\t\tdata.step = args[0];\n\t\ti = 1;\n\t}\n\n\twhile (i < args.length) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--\")) {\n\t\t\tconst key = arg\n\t\t\t\t.slice(2)\n\t\t\t\t.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\n\t\t\tif (key === \"json\") {\n\t\t\t\ti += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst next = args[i + 1];\n\t\t\tif (next !== undefined && !next.startsWith(\"-\")) {\n\t\t\t\tdata[key] = next;\n\t\t\t\ti += 2;\n\t\t\t} else {\n\t\t\t\tdata[key] = true;\n\t\t\t\ti += 1;\n\t\t\t}\n\t\t} else {\n\t\t\ti += 1;\n\t\t}\n\t}\n\n\treturn `neonctl init --agent --data '${JSON.stringify(data)}'`;\n}\n\n/**\n * Walks a phase response object and:\n * 1. Replaces `args` arrays with `command` strings (neonctl init --data format)\n * 2. Renames `run_neon_init` → `run_shell_command`\n * 3. Adds a description to finalize steps\n */\nexport function enrichResponse(obj: unknown): unknown {\n\tif (obj === null || typeof obj !== \"object\") return obj;\n\tif (Array.isArray(obj)) return obj.map(enrichResponse);\n\n\tconst record = obj as Record<string, unknown>;\n\tconst result: Record<string, unknown> = {};\n\n\tfor (const [key, value] of Object.entries(record)) {\n\t\tresult[key] = enrichResponse(value);\n\t}\n\n\t// Replace args with command in run_neon_init actions and responseMapping entries\n\tif (Array.isArray(result.args)) {\n\t\tresult.command = argsToCommand(result.args as string[]);\n\t\tdelete result.args;\n\t}\n\n\t// Rename run_neon_init → run_shell_command so agents don't infer subcommand patterns\n\tif (result.type === \"run_neon_init\") {\n\t\tresult.type = \"run_shell_command\";\n\t\t// Help agents understand finalize is the terminal step\n\t\tif (\n\t\t\ttypeof result.command === \"string\" &&\n\t\t\tresult.command.includes('\"step\":\"finalize\"')\n\t\t) {\n\t\t\tresult.description =\n\t\t\t\t\"Run this command to complete the setup. This is the final step — do not run any other neonctl init commands after this.\";\n\t\t}\n\t}\n\n\treturn result;\n}\n"],"mappings":";;;;;AAIA,SAAS,cAAc,MAAwB;CAC9C,MAAM,OAAgC,CAAC;CACvC,IAAI,IAAI;CAGR,IAAI,KAAK,SAAS,KAAK,CAAC,KAAK,EAAE,CAAC,WAAW,GAAG,GAAG;EAChD,KAAK,OAAO,KAAK;EACjB,IAAI;CACL;CAEA,OAAO,IAAI,KAAK,QAAQ;EACvB,MAAM,MAAM,KAAK;EACjB,IAAI,IAAI,WAAW,IAAI,GAAG;GACzB,MAAM,MAAM,IACV,MAAM,CAAC,CAAC,CACR,QAAQ,cAAc,GAAG,MAAc,EAAE,YAAY,CAAC;GACxD,IAAI,QAAQ,QAAQ;IACnB,KAAK;IACL;GACD;GACA,MAAM,OAAO,KAAK,IAAI;GACtB,IAAI,SAAS,KAAA,KAAa,CAAC,KAAK,WAAW,GAAG,GAAG;IAChD,KAAK,OAAO;IACZ,KAAK;GACN,OAAO;IACN,KAAK,OAAO;IACZ,KAAK;GACN;EACD,OACC,KAAK;CAEP;CAEA,OAAO,gCAAgC,KAAK,UAAU,IAAI,EAAE;AAC7D;;;;;;;AAQA,SAAgB,eAAe,KAAuB;CACrD,IAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU,OAAO;CACpD,IAAI,MAAM,QAAQ,GAAG,GAAG,OAAO,IAAI,IAAI,cAAc;CAErD,MAAM,SAAS;CACf,MAAM,SAAkC,CAAC;CAEzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC/C,OAAO,OAAO,eAAe,KAAK;CAInC,IAAI,MAAM,QAAQ,OAAO,IAAI,GAAG;EAC/B,OAAO,UAAU,cAAc,OAAO,IAAgB;EACtD,OAAO,OAAO;CACf;CAGA,IAAI,OAAO,SAAS,iBAAiB;EACpC,OAAO,OAAO;EAEd,IACC,OAAO,OAAO,YAAY,YAC1B,OAAO,QAAQ,SAAS,uBAAmB,GAE3C,OAAO,cACN;CAEH;CAEA,OAAO;AACR"}
|
package/dist/lib/inspect.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { AgentCheck } from "./types.js";
|
|
2
2
|
|
|
3
3
|
//#region src/lib/inspect.d.ts
|
|
4
|
+
type DetectedScope = "global" | "project" | "global-partial" | "project-partial" | false;
|
|
4
5
|
interface InspectionResults {
|
|
5
6
|
[key: string]: unknown;
|
|
6
7
|
mcpConfigured?: boolean;
|
|
8
|
+
mcpScope?: DetectedScope;
|
|
7
9
|
skillsInstalled?: boolean;
|
|
10
|
+
skillsScope?: DetectedScope;
|
|
8
11
|
/** True if a Neon-specific connection string (DATABASE_URL with "neon" or PGHOST with "neon") is found */
|
|
9
12
|
connectionString?: boolean;
|
|
10
13
|
/** True if any DATABASE_URL is set in .env (regardless of provider) */
|
|
@@ -24,5 +27,5 @@ interface InspectionResults {
|
|
|
24
27
|
*/
|
|
25
28
|
declare function inspectProject(checks: AgentCheck[]): Promise<InspectionResults>;
|
|
26
29
|
//#endregion
|
|
27
|
-
export { InspectionResults, inspectProject };
|
|
30
|
+
export { DetectedScope, InspectionResults, inspectProject };
|
|
28
31
|
//# sourceMappingURL=inspect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspect.d.ts","names":[],"sources":["../../src/lib/inspect.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","names":[],"sources":["../../src/lib/inspect.ts"],"mappings":";;;KASY,aAAA;UAOK,iBAAA;EAPL,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,OAAa;EAOR,aAAA,CAAA,EAAA,OAAiB;EAAA,QAAA,CAAA,EAGtB,aAHsB;iBAGtB,CAAA,EAAA,OAAA;aAEG,CAAA,EAAA,aAAA;EAAa;EAmBN,gBAAA,CAAc,EAAA,OAAA;EAAA;aAC3B,CAAA,EAAA,OAAA;WACE,CAAA,EAAA,MAAA;KAAR,CAAA,EAAA,MAAA;EAAO,aAAA,CAAA,EAAA,MAAA;;;;;;;;;;;iBAFY,cAAA,SACb,eACN,QAAQ"}
|
package/dist/lib/inspect.js
CHANGED
|
@@ -14,9 +14,12 @@ async function inspectProject(checks) {
|
|
|
14
14
|
const results = {};
|
|
15
15
|
const cwd = process.cwd();
|
|
16
16
|
for (const check of checks) switch (check.id) {
|
|
17
|
-
case "mcp_server":
|
|
18
|
-
|
|
17
|
+
case "mcp_server": {
|
|
18
|
+
const mcpScope = checkMcpServer(cwd);
|
|
19
|
+
results.mcpConfigured = mcpScope !== false;
|
|
20
|
+
results.mcpScope = mcpScope;
|
|
19
21
|
break;
|
|
22
|
+
}
|
|
20
23
|
case "connection_string":
|
|
21
24
|
results.connectionString = checkConnectionString(cwd);
|
|
22
25
|
break;
|
|
@@ -35,9 +38,12 @@ async function inspectProject(checks) {
|
|
|
35
38
|
results.migrationDir = migrations.dir;
|
|
36
39
|
break;
|
|
37
40
|
}
|
|
38
|
-
case "skills":
|
|
39
|
-
|
|
41
|
+
case "skills": {
|
|
42
|
+
const skillsScope = checkSkillsInstalled(cwd);
|
|
43
|
+
results.skillsInstalled = skillsScope !== false;
|
|
44
|
+
results.skillsScope = skillsScope;
|
|
40
45
|
break;
|
|
46
|
+
}
|
|
41
47
|
case "ide_type":
|
|
42
48
|
results.isVscodeIde = checkVscodeIde();
|
|
43
49
|
break;
|
|
@@ -49,28 +55,20 @@ async function inspectProject(checks) {
|
|
|
49
55
|
return results;
|
|
50
56
|
}
|
|
51
57
|
function checkMcpServer(cwd) {
|
|
52
|
-
const cursorMcp = resolve(cwd, ".cursor", "mcp.json");
|
|
53
|
-
if (existsSync(cursorMcp)) try {
|
|
54
|
-
const content = readFileSync(cursorMcp, "utf-8");
|
|
55
|
-
if (content.includes("neon") || content.includes("mcp.neon.tech")) return true;
|
|
56
|
-
} catch {}
|
|
57
58
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
58
|
-
const
|
|
59
|
-
if (existsSync(
|
|
60
|
-
const content = readFileSync(
|
|
61
|
-
if (content.includes("neon") || content.includes("mcp.neon.tech")) return
|
|
59
|
+
const projectConfigs = [resolve(cwd, ".cursor", "mcp.json"), resolve(cwd, ".vscode", "settings.json")];
|
|
60
|
+
for (const configPath of projectConfigs) if (existsSync(configPath)) try {
|
|
61
|
+
const content = readFileSync(configPath, "utf-8");
|
|
62
|
+
if (content.includes("neon") || content.includes("mcp.neon.tech")) return "project";
|
|
62
63
|
} catch {}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (existsSync(vscodeSettings)) try {
|
|
72
|
-
const content = readFileSync(vscodeSettings, "utf-8");
|
|
73
|
-
if (content.includes("neon") || content.includes("mcp.neon.tech")) return true;
|
|
64
|
+
const globalConfigs = [
|
|
65
|
+
resolve(home, ".cursor", "mcp.json"),
|
|
66
|
+
resolve(home, ".claude", "settings.local.json"),
|
|
67
|
+
resolve(home, ".claude", "settings.json")
|
|
68
|
+
];
|
|
69
|
+
for (const configPath of globalConfigs) if (existsSync(configPath)) try {
|
|
70
|
+
const content = readFileSync(configPath, "utf-8");
|
|
71
|
+
if (content.includes("neon") || content.includes("mcp.neon.tech")) return "global";
|
|
74
72
|
} catch {}
|
|
75
73
|
return false;
|
|
76
74
|
}
|
|
@@ -142,18 +140,26 @@ function checkDatabaseUrl(cwd) {
|
|
|
142
140
|
}
|
|
143
141
|
function checkSkillsInstalled(cwd) {
|
|
144
142
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
145
|
-
const
|
|
143
|
+
const skillNames = ["neon", "neon-postgres"];
|
|
144
|
+
const projectDirs = [
|
|
146
145
|
resolve(cwd, ".cursor", "skills"),
|
|
147
146
|
resolve(cwd, ".claude", "skills"),
|
|
148
|
-
resolve(cwd, ".agents", "skills")
|
|
149
|
-
resolve(home, ".cursor", "skills"),
|
|
150
|
-
resolve(home, ".claude", "skills")
|
|
147
|
+
resolve(cwd, ".agents", "skills")
|
|
151
148
|
];
|
|
152
|
-
|
|
149
|
+
if (skillNames.every((skill) => projectDirs.some((dir) => existsSync(resolve(dir, skill, "SKILL.md"))))) return "project";
|
|
153
150
|
const claudeMd = resolve(cwd, "CLAUDE.md");
|
|
154
151
|
if (existsSync(claudeMd)) try {
|
|
155
|
-
|
|
152
|
+
const content = readFileSync(claudeMd, "utf-8");
|
|
153
|
+
if (content.includes("neon-postgres") && content.includes("neon/SKILL.md")) return "project";
|
|
156
154
|
} catch {}
|
|
155
|
+
const globalDirs = [
|
|
156
|
+
resolve(home, ".cursor", "skills"),
|
|
157
|
+
resolve(home, ".claude", "skills"),
|
|
158
|
+
resolve(home, ".agents", "skills")
|
|
159
|
+
];
|
|
160
|
+
if (skillNames.every((skill) => globalDirs.some((dir) => existsSync(resolve(dir, skill, "SKILL.md"))))) return "global";
|
|
161
|
+
if (skillNames.some((skill) => projectDirs.some((dir) => existsSync(resolve(dir, skill, "SKILL.md"))))) return "project-partial";
|
|
162
|
+
if (skillNames.some((skill) => globalDirs.some((dir) => existsSync(resolve(dir, skill, "SKILL.md"))))) return "global-partial";
|
|
157
163
|
return false;
|
|
158
164
|
}
|
|
159
165
|
function checkVscodeIde() {
|
package/dist/lib/inspect.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspect.js","names":[],"sources":["../../src/lib/inspect.ts"],"sourcesContent":["/**\n * Filesystem-based project inspection for interactive (agentless) mode.\n * Replaces the \"agent_check\" pattern — instead of asking an agent to look,\n * we examine the filesystem directly.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { AgentCheck } from \"./types.js\";\n\nexport interface InspectionResults {\n\t[key: string]: unknown;\n\tmcpConfigured?: boolean;\n\tskillsInstalled?: boolean;\n\t/** True if a Neon-specific connection string (DATABASE_URL with \"neon\" or PGHOST with \"neon\") is found */\n\tconnectionString?: boolean;\n\t/** True if any DATABASE_URL is set in .env (regardless of provider) */\n\tdatabaseUrl?: boolean;\n\tframework?: string;\n\torm?: string;\n\tmigrationTool?: string;\n\tmigrationDir?: string;\n\tisVscodeIde?: boolean;\n\tagent?: string;\n\t/** True if the directory contains an application (package.json with deps, or source files) */\n\thasApp?: boolean;\n}\n\n/**\n * Runs filesystem checks based on the agent_check descriptors.\n * Maps check IDs to concrete inspection functions.\n */\nexport async function inspectProject(\n\tchecks: AgentCheck[],\n): Promise<InspectionResults> {\n\tconst results: InspectionResults = {};\n\tconst cwd = process.cwd();\n\n\tfor (const check of checks) {\n\t\tswitch (check.id) {\n\t\t\tcase \"mcp_server\":\n\t\t\t\tresults.mcpConfigured = checkMcpServer(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"connection_string\":\n\t\t\t\tresults.connectionString = checkConnectionString(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"database_url\":\n\t\t\t\tresults.databaseUrl = checkDatabaseUrl(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"project_stack\": {\n\t\t\t\tconst stack = detectProjectStack(cwd);\n\t\t\t\tresults.framework = stack.framework;\n\t\t\t\tresults.orm = stack.orm;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"migrations\": {\n\t\t\t\tconst migrations = detectMigrations(cwd);\n\t\t\t\tresults.migrationTool = migrations.tool;\n\t\t\t\tresults.migrationDir = migrations.dir;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"skills\":\n\t\t\t\tresults.skillsInstalled = checkSkillsInstalled(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"ide_type\":\n\t\t\t\tresults.isVscodeIde = checkVscodeIde();\n\t\t\t\tbreak;\n\t\t\tcase \"has_app\":\n\t\t\t\tresults.hasApp = checkHasApp(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"agent_type\":\n\t\t\t\t// Handled by the interactive runner (prompt or env detection)\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn results;\n}\n\n// ---------------------------------------------------------------------------\n// Individual check implementations\n// ---------------------------------------------------------------------------\n\nfunction checkMcpServer(cwd: string): boolean {\n\t// Check project-level Cursor config\n\tconst cursorMcp = resolve(cwd, \".cursor\", \"mcp.json\");\n\tif (existsSync(cursorMcp)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(cursorMcp, \"utf-8\");\n\t\t\tif (content.includes(\"neon\") || content.includes(\"mcp.neon.tech\")) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch {}\n\t}\n\n\t// Check global Cursor config\n\tconst home = process.env.HOME || process.env.USERPROFILE || \"\";\n\tconst globalCursorMcp = resolve(home, \".cursor\", \"mcp.json\");\n\tif (existsSync(globalCursorMcp)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(globalCursorMcp, \"utf-8\");\n\t\t\tif (content.includes(\"neon\") || content.includes(\"mcp.neon.tech\")) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch {}\n\t}\n\n\t// Check Claude Code config (add-mcp writes to settings.local.json)\n\tfor (const claudeFile of [\"settings.local.json\", \"settings.json\"]) {\n\t\tconst claudeConfig = resolve(home, \".claude\", claudeFile);\n\t\tif (existsSync(claudeConfig)) {\n\t\t\ttry {\n\t\t\t\tconst content = readFileSync(claudeConfig, \"utf-8\");\n\t\t\t\tif (\n\t\t\t\t\tcontent.includes(\"neon\") ||\n\t\t\t\t\tcontent.includes(\"mcp.neon.tech\")\n\t\t\t\t) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\t// Check VS Code settings\n\tconst vscodeSettings = resolve(cwd, \".vscode\", \"settings.json\");\n\tif (existsSync(vscodeSettings)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(vscodeSettings, \"utf-8\");\n\t\t\tif (content.includes(\"neon\") || content.includes(\"mcp.neon.tech\")) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch {}\n\t}\n\n\treturn false;\n}\n\nfunction checkConnectionString(cwd: string): boolean {\n\tfor (const envFile of [\".env\", \".env.local\"]) {\n\t\tconst envPath = resolve(cwd, envFile);\n\t\tif (existsSync(envPath)) {\n\t\t\ttry {\n\t\t\t\tconst content = readFileSync(envPath, \"utf-8\");\n\t\t\t\tif (\n\t\t\t\t\t/^DATABASE_URL=.*neon/m.test(content) ||\n\t\t\t\t\t/^PGHOST=.*neon/m.test(content)\n\t\t\t\t) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\treturn false;\n}\n\ninterface StackResult {\n\tframework: string;\n\torm: string;\n}\n\nfunction detectProjectStack(cwd: string): StackResult {\n\tconst result: StackResult = { framework: \"none\", orm: \"none\" };\n\n\tconst pkgPath = resolve(cwd, \"package.json\");\n\tif (!existsSync(pkgPath)) return result;\n\n\ttry {\n\t\tconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\t\tconst allDeps = {\n\t\t\t...pkg.dependencies,\n\t\t\t...pkg.devDependencies,\n\t\t};\n\n\t\t// Framework detection\n\t\tif (allDeps.next) result.framework = \"next\";\n\t\telse if (allDeps.remix || allDeps[\"@remix-run/node\"])\n\t\t\tresult.framework = \"remix\";\n\t\telse if (allDeps.express) result.framework = \"express\";\n\t\telse if (allDeps.hono) result.framework = \"hono\";\n\t\telse if (allDeps.fastify) result.framework = \"fastify\";\n\t\telse if (allDeps[\"@sveltejs/kit\"]) result.framework = \"sveltekit\";\n\t\telse if (allDeps.nuxt) result.framework = \"nuxt\";\n\t\telse if (allDeps.astro) result.framework = \"astro\";\n\n\t\t// ORM detection\n\t\tif (allDeps.prisma || allDeps[\"@prisma/client\"]) result.orm = \"prisma\";\n\t\telse if (allDeps[\"drizzle-orm\"]) result.orm = \"drizzle\";\n\t\telse if (allDeps.knex) result.orm = \"knex\";\n\t\telse if (allDeps.typeorm) result.orm = \"typeorm\";\n\t\telse if (allDeps.sequelize) result.orm = \"sequelize\";\n\t\telse if (allDeps[\"@neondatabase/serverless\"])\n\t\t\tresult.orm = \"neon-serverless\";\n\t} catch {}\n\n\treturn result;\n}\n\ninterface MigrationResult {\n\ttool: string;\n\tdir: string;\n}\n\nfunction detectMigrations(cwd: string): MigrationResult {\n\t// Prisma\n\tif (existsSync(resolve(cwd, \"prisma\", \"schema.prisma\"))) {\n\t\tconst migrationsDir = resolve(cwd, \"prisma\", \"migrations\");\n\t\treturn {\n\t\t\ttool: \"prisma\",\n\t\t\tdir: existsSync(migrationsDir) ? \"prisma/migrations\" : \"none\",\n\t\t};\n\t}\n\n\t// Drizzle\n\tif (\n\t\texistsSync(resolve(cwd, \"drizzle.config.ts\")) ||\n\t\texistsSync(resolve(cwd, \"drizzle.config.js\"))\n\t) {\n\t\tconst drizzleDir = resolve(cwd, \"drizzle\");\n\t\treturn {\n\t\t\ttool: \"drizzle\",\n\t\t\tdir: existsSync(drizzleDir) ? \"drizzle\" : \"none\",\n\t\t};\n\t}\n\n\t// Knex\n\tif (\n\t\texistsSync(resolve(cwd, \"knexfile.js\")) ||\n\t\texistsSync(resolve(cwd, \"knexfile.ts\"))\n\t) {\n\t\tconst migrationsDir = resolve(cwd, \"migrations\");\n\t\treturn {\n\t\t\ttool: \"knex\",\n\t\t\tdir: existsSync(migrationsDir) ? \"migrations\" : \"none\",\n\t\t};\n\t}\n\n\treturn { tool: \"none\", dir: \"none\" };\n}\n\nfunction checkDatabaseUrl(cwd: string): boolean {\n\tconst envPath = resolve(cwd, \".env\");\n\tif (existsSync(envPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(envPath, \"utf-8\");\n\t\t\tif (/^DATABASE_URL=/m.test(content)) return true;\n\t\t} catch {}\n\t}\n\treturn false;\n}\n\nfunction checkSkillsInstalled(cwd: string): boolean {\n\tconst home = process.env.HOME || process.env.USERPROFILE || \"\";\n\n\t// Check for neon-postgres SKILL.md in known skill directories (project and global)\n\tconst skillsDirs = [\n\t\tresolve(cwd, \".cursor\", \"skills\"),\n\t\tresolve(cwd, \".claude\", \"skills\"),\n\t\tresolve(cwd, \".agents\", \"skills\"),\n\t\tresolve(home, \".cursor\", \"skills\"),\n\t\tresolve(home, \".claude\", \"skills\"),\n\t];\n\tfor (const dir of skillsDirs) {\n\t\t// Verify the actual skill content file exists, not just the directory\n\t\tconst skillMd = resolve(dir, \"neon-postgres\", \"SKILL.md\");\n\t\tif (existsSync(skillMd)) return true;\n\t}\n\n\t// Check CLAUDE.md for neon skill references (injected by skills CLI)\n\tconst claudeMd = resolve(cwd, \"CLAUDE.md\");\n\tif (existsSync(claudeMd)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(claudeMd, \"utf-8\");\n\t\t\tif (content.includes(\"neon-postgres\")) return true;\n\t\t} catch {}\n\t}\n\n\treturn false;\n}\n\nfunction checkVscodeIde(): boolean {\n\tconst env = process.env;\n\treturn !!(\n\t\tenv.TERM_PROGRAM === \"vscode\" ||\n\t\tenv.TERM_PROGRAM === \"cursor\" ||\n\t\tenv.TERM_PROGRAM === \"windsurf\" ||\n\t\tenv.VSCODE_PID ||\n\t\tenv.VSCODE_CWD\n\t);\n}\n\n/**\n * Detects whether the current directory contains an application.\n * Returns true if package.json has dependencies or common source directories exist.\n */\nfunction checkHasApp(cwd: string): boolean {\n\tconst pkgPath = resolve(cwd, \"package.json\");\n\tif (existsSync(pkgPath)) {\n\t\ttry {\n\t\t\tconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\t\t\tconst deps = Object.keys(pkg.dependencies ?? {});\n\t\t\tconst devDeps = Object.keys(pkg.devDependencies ?? {});\n\t\t\tif (deps.length > 0 || devDeps.length > 0) return true;\n\t\t} catch {}\n\t}\n\n\t// Check for common source directories / entry files\n\tconst indicators = [\n\t\t\"src\",\n\t\t\"app\",\n\t\t\"pages\",\n\t\t\"lib\",\n\t\t\"index.ts\",\n\t\t\"index.js\",\n\t\t\"main.ts\",\n\t\t\"main.js\",\n\t];\n\tfor (const indicator of indicators) {\n\t\tif (existsSync(resolve(cwd, indicator))) return true;\n\t}\n\n\treturn false;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"inspect.js","names":[],"sources":["../../src/lib/inspect.ts"],"sourcesContent":["/**\n * Filesystem-based project inspection for interactive (agentless) mode.\n * Replaces the \"agent_check\" pattern — instead of asking an agent to look,\n * we examine the filesystem directly.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { AgentCheck } from \"./types.js\";\n\nexport type DetectedScope =\n\t| \"global\"\n\t| \"project\"\n\t| \"global-partial\"\n\t| \"project-partial\"\n\t| false;\n\nexport interface InspectionResults {\n\t[key: string]: unknown;\n\tmcpConfigured?: boolean;\n\tmcpScope?: DetectedScope;\n\tskillsInstalled?: boolean;\n\tskillsScope?: DetectedScope;\n\t/** True if a Neon-specific connection string (DATABASE_URL with \"neon\" or PGHOST with \"neon\") is found */\n\tconnectionString?: boolean;\n\t/** True if any DATABASE_URL is set in .env (regardless of provider) */\n\tdatabaseUrl?: boolean;\n\tframework?: string;\n\torm?: string;\n\tmigrationTool?: string;\n\tmigrationDir?: string;\n\tisVscodeIde?: boolean;\n\tagent?: string;\n\t/** True if the directory contains an application (package.json with deps, or source files) */\n\thasApp?: boolean;\n}\n\n/**\n * Runs filesystem checks based on the agent_check descriptors.\n * Maps check IDs to concrete inspection functions.\n */\nexport async function inspectProject(\n\tchecks: AgentCheck[],\n): Promise<InspectionResults> {\n\tconst results: InspectionResults = {};\n\tconst cwd = process.cwd();\n\n\tfor (const check of checks) {\n\t\tswitch (check.id) {\n\t\t\tcase \"mcp_server\": {\n\t\t\t\tconst mcpScope = checkMcpServer(cwd);\n\t\t\t\tresults.mcpConfigured = mcpScope !== false;\n\t\t\t\tresults.mcpScope = mcpScope;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"connection_string\":\n\t\t\t\tresults.connectionString = checkConnectionString(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"database_url\":\n\t\t\t\tresults.databaseUrl = checkDatabaseUrl(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"project_stack\": {\n\t\t\t\tconst stack = detectProjectStack(cwd);\n\t\t\t\tresults.framework = stack.framework;\n\t\t\t\tresults.orm = stack.orm;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"migrations\": {\n\t\t\t\tconst migrations = detectMigrations(cwd);\n\t\t\t\tresults.migrationTool = migrations.tool;\n\t\t\t\tresults.migrationDir = migrations.dir;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"skills\": {\n\t\t\t\tconst skillsScope = checkSkillsInstalled(cwd);\n\t\t\t\tresults.skillsInstalled = skillsScope !== false;\n\t\t\t\tresults.skillsScope = skillsScope;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"ide_type\":\n\t\t\t\tresults.isVscodeIde = checkVscodeIde();\n\t\t\t\tbreak;\n\t\t\tcase \"has_app\":\n\t\t\t\tresults.hasApp = checkHasApp(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"agent_type\":\n\t\t\t\t// Handled by the interactive runner (prompt or env detection)\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn results;\n}\n\n// ---------------------------------------------------------------------------\n// Individual check implementations\n// ---------------------------------------------------------------------------\n\nfunction checkMcpServer(cwd: string): DetectedScope {\n\tconst home = process.env.HOME || process.env.USERPROFILE || \"\";\n\n\t// Check project-level configs first\n\tconst projectConfigs = [\n\t\tresolve(cwd, \".cursor\", \"mcp.json\"),\n\t\tresolve(cwd, \".vscode\", \"settings.json\"),\n\t];\n\tfor (const configPath of projectConfigs) {\n\t\tif (existsSync(configPath)) {\n\t\t\ttry {\n\t\t\t\tconst content = readFileSync(configPath, \"utf-8\");\n\t\t\t\tif (\n\t\t\t\t\tcontent.includes(\"neon\") ||\n\t\t\t\t\tcontent.includes(\"mcp.neon.tech\")\n\t\t\t\t) {\n\t\t\t\t\treturn \"project\";\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\t// Check global configs\n\tconst globalConfigs = [\n\t\tresolve(home, \".cursor\", \"mcp.json\"),\n\t\t// Claude Code config (add-mcp writes to settings.local.json)\n\t\tresolve(home, \".claude\", \"settings.local.json\"),\n\t\tresolve(home, \".claude\", \"settings.json\"),\n\t];\n\tfor (const configPath of globalConfigs) {\n\t\tif (existsSync(configPath)) {\n\t\t\ttry {\n\t\t\t\tconst content = readFileSync(configPath, \"utf-8\");\n\t\t\t\tif (\n\t\t\t\t\tcontent.includes(\"neon\") ||\n\t\t\t\t\tcontent.includes(\"mcp.neon.tech\")\n\t\t\t\t) {\n\t\t\t\t\treturn \"global\";\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction checkConnectionString(cwd: string): boolean {\n\tfor (const envFile of [\".env\", \".env.local\"]) {\n\t\tconst envPath = resolve(cwd, envFile);\n\t\tif (existsSync(envPath)) {\n\t\t\ttry {\n\t\t\t\tconst content = readFileSync(envPath, \"utf-8\");\n\t\t\t\tif (\n\t\t\t\t\t/^DATABASE_URL=.*neon/m.test(content) ||\n\t\t\t\t\t/^PGHOST=.*neon/m.test(content)\n\t\t\t\t) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\treturn false;\n}\n\ninterface StackResult {\n\tframework: string;\n\torm: string;\n}\n\nfunction detectProjectStack(cwd: string): StackResult {\n\tconst result: StackResult = { framework: \"none\", orm: \"none\" };\n\n\tconst pkgPath = resolve(cwd, \"package.json\");\n\tif (!existsSync(pkgPath)) return result;\n\n\ttry {\n\t\tconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\t\tconst allDeps = {\n\t\t\t...pkg.dependencies,\n\t\t\t...pkg.devDependencies,\n\t\t};\n\n\t\t// Framework detection\n\t\tif (allDeps.next) result.framework = \"next\";\n\t\telse if (allDeps.remix || allDeps[\"@remix-run/node\"])\n\t\t\tresult.framework = \"remix\";\n\t\telse if (allDeps.express) result.framework = \"express\";\n\t\telse if (allDeps.hono) result.framework = \"hono\";\n\t\telse if (allDeps.fastify) result.framework = \"fastify\";\n\t\telse if (allDeps[\"@sveltejs/kit\"]) result.framework = \"sveltekit\";\n\t\telse if (allDeps.nuxt) result.framework = \"nuxt\";\n\t\telse if (allDeps.astro) result.framework = \"astro\";\n\n\t\t// ORM detection\n\t\tif (allDeps.prisma || allDeps[\"@prisma/client\"]) result.orm = \"prisma\";\n\t\telse if (allDeps[\"drizzle-orm\"]) result.orm = \"drizzle\";\n\t\telse if (allDeps.knex) result.orm = \"knex\";\n\t\telse if (allDeps.typeorm) result.orm = \"typeorm\";\n\t\telse if (allDeps.sequelize) result.orm = \"sequelize\";\n\t\telse if (allDeps[\"@neondatabase/serverless\"])\n\t\t\tresult.orm = \"neon-serverless\";\n\t} catch {}\n\n\treturn result;\n}\n\ninterface MigrationResult {\n\ttool: string;\n\tdir: string;\n}\n\nfunction detectMigrations(cwd: string): MigrationResult {\n\t// Prisma\n\tif (existsSync(resolve(cwd, \"prisma\", \"schema.prisma\"))) {\n\t\tconst migrationsDir = resolve(cwd, \"prisma\", \"migrations\");\n\t\treturn {\n\t\t\ttool: \"prisma\",\n\t\t\tdir: existsSync(migrationsDir) ? \"prisma/migrations\" : \"none\",\n\t\t};\n\t}\n\n\t// Drizzle\n\tif (\n\t\texistsSync(resolve(cwd, \"drizzle.config.ts\")) ||\n\t\texistsSync(resolve(cwd, \"drizzle.config.js\"))\n\t) {\n\t\tconst drizzleDir = resolve(cwd, \"drizzle\");\n\t\treturn {\n\t\t\ttool: \"drizzle\",\n\t\t\tdir: existsSync(drizzleDir) ? \"drizzle\" : \"none\",\n\t\t};\n\t}\n\n\t// Knex\n\tif (\n\t\texistsSync(resolve(cwd, \"knexfile.js\")) ||\n\t\texistsSync(resolve(cwd, \"knexfile.ts\"))\n\t) {\n\t\tconst migrationsDir = resolve(cwd, \"migrations\");\n\t\treturn {\n\t\t\ttool: \"knex\",\n\t\t\tdir: existsSync(migrationsDir) ? \"migrations\" : \"none\",\n\t\t};\n\t}\n\n\treturn { tool: \"none\", dir: \"none\" };\n}\n\nfunction checkDatabaseUrl(cwd: string): boolean {\n\tconst envPath = resolve(cwd, \".env\");\n\tif (existsSync(envPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(envPath, \"utf-8\");\n\t\t\tif (/^DATABASE_URL=/m.test(content)) return true;\n\t\t} catch {}\n\t}\n\treturn false;\n}\n\nfunction checkSkillsInstalled(cwd: string): DetectedScope {\n\tconst home = process.env.HOME || process.env.USERPROFILE || \"\";\n\n\tconst skillNames = [\"neon\", \"neon-postgres\"];\n\n\t// Check project-level skill directories — ALL base skills must exist\n\tconst projectDirs = [\n\t\tresolve(cwd, \".cursor\", \"skills\"),\n\t\tresolve(cwd, \".claude\", \"skills\"),\n\t\tresolve(cwd, \".agents\", \"skills\"),\n\t];\n\tconst allProjectSkills = skillNames.every((skill) =>\n\t\tprojectDirs.some((dir) => existsSync(resolve(dir, skill, \"SKILL.md\"))),\n\t);\n\tif (allProjectSkills) return \"project\";\n\n\t// Check CLAUDE.md for neon skill references (injected by skills CLI)\n\t// Only counts as installed if both skill names are referenced\n\tconst claudeMd = resolve(cwd, \"CLAUDE.md\");\n\tif (existsSync(claudeMd)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(claudeMd, \"utf-8\");\n\t\t\tif (\n\t\t\t\tcontent.includes(\"neon-postgres\") &&\n\t\t\t\tcontent.includes(\"neon/SKILL.md\")\n\t\t\t)\n\t\t\t\treturn \"project\";\n\t\t} catch {}\n\t}\n\n\t// Check global skill directories — ALL base skills must exist\n\tconst globalDirs = [\n\t\tresolve(home, \".cursor\", \"skills\"),\n\t\tresolve(home, \".claude\", \"skills\"),\n\t\tresolve(home, \".agents\", \"skills\"),\n\t];\n\tconst allGlobalSkills = skillNames.every((skill) =>\n\t\tglobalDirs.some((dir) => existsSync(resolve(dir, skill, \"SKILL.md\"))),\n\t);\n\tif (allGlobalSkills) return \"global\";\n\n\t// Check for partial installations — ANY skill exists\n\tconst anyProjectSkill = skillNames.some((skill) =>\n\t\tprojectDirs.some((dir) => existsSync(resolve(dir, skill, \"SKILL.md\"))),\n\t);\n\tif (anyProjectSkill) return \"project-partial\";\n\n\tconst anyGlobalSkill = skillNames.some((skill) =>\n\t\tglobalDirs.some((dir) => existsSync(resolve(dir, skill, \"SKILL.md\"))),\n\t);\n\tif (anyGlobalSkill) return \"global-partial\";\n\n\treturn false;\n}\n\nfunction checkVscodeIde(): boolean {\n\tconst env = process.env;\n\treturn !!(\n\t\tenv.TERM_PROGRAM === \"vscode\" ||\n\t\tenv.TERM_PROGRAM === \"cursor\" ||\n\t\tenv.TERM_PROGRAM === \"windsurf\" ||\n\t\tenv.VSCODE_PID ||\n\t\tenv.VSCODE_CWD\n\t);\n}\n\n/**\n * Detects whether the current directory contains an application.\n * Returns true if package.json has dependencies or common source directories exist.\n */\nfunction checkHasApp(cwd: string): boolean {\n\tconst pkgPath = resolve(cwd, \"package.json\");\n\tif (existsSync(pkgPath)) {\n\t\ttry {\n\t\t\tconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\t\t\tconst deps = Object.keys(pkg.dependencies ?? {});\n\t\t\tconst devDeps = Object.keys(pkg.devDependencies ?? {});\n\t\t\tif (deps.length > 0 || devDeps.length > 0) return true;\n\t\t} catch {}\n\t}\n\n\t// Check for common source directories / entry files\n\tconst indicators = [\n\t\t\"src\",\n\t\t\"app\",\n\t\t\"pages\",\n\t\t\"lib\",\n\t\t\"index.ts\",\n\t\t\"index.js\",\n\t\t\"main.ts\",\n\t\t\"main.js\",\n\t];\n\tfor (const indicator of indicators) {\n\t\tif (existsSync(resolve(cwd, indicator))) return true;\n\t}\n\n\treturn false;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"neonctl.d.ts","names":[],"sources":["../../src/lib/neonctl.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"neonctl.d.ts","names":[],"sources":["../../src/lib/neonctl.ts"],"mappings":";;AAWA;AAWA;AAQC;AAkDD;;;;AAA6C,iBArE7B,UAAA,CAAA,CAqE6B,EAAA,MAAA;AAiD7C;AAqCA;;;;AAA8C;;iBAhJ9B,oBAAA,CAAA;UA6BN,aAAA;;;;;;;;;iBA6BY,YAAA,CAAA,GAAgB,QAAQ;UAiD7B,mBAAA;;;;;;;;;iBAqCK,aAAA,CAAA,GAAiB,QAAQ"}
|
package/dist/lib/neonctl.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { lstatSync } from "node:fs";
|
|
1
2
|
import { execa } from "execa";
|
|
2
3
|
//#region src/lib/neonctl.ts
|
|
3
4
|
/**
|
|
@@ -127,10 +128,30 @@ function isOlderVersion(current, latest) {
|
|
|
127
128
|
return false;
|
|
128
129
|
}
|
|
129
130
|
/**
|
|
131
|
+
* Checks if neonctl is installed via a local dev symlink.
|
|
132
|
+
* If so, skip install/update — the developer manages it manually.
|
|
133
|
+
*/
|
|
134
|
+
function isLocalDevSymlink() {
|
|
135
|
+
try {
|
|
136
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
137
|
+
const candidates = [`${process.env.NVM_DIR || `${home}/.nvm`}/versions/node/${process.version}/lib/node_modules/neonctl`, `${home}/.nvm/versions/node/${process.version}/lib/node_modules/neonctl`];
|
|
138
|
+
for (const candidate of candidates) try {
|
|
139
|
+
if (lstatSync(candidate).isSymbolicLink()) return true;
|
|
140
|
+
} catch {}
|
|
141
|
+
return false;
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
130
147
|
* Ensures neonctl is globally installed and up to date.
|
|
131
148
|
* Uses the same package manager that invoked the init command.
|
|
132
149
|
*/
|
|
133
150
|
async function ensureNeonctl() {
|
|
151
|
+
if (isLocalDevSymlink()) return {
|
|
152
|
+
status: "already_current",
|
|
153
|
+
version: await getNeonctlVersion() ?? "dev"
|
|
154
|
+
};
|
|
134
155
|
const check = await checkNeonctl();
|
|
135
156
|
if (check.installed && !check.needsUpdate) return {
|
|
136
157
|
status: "already_current",
|
package/dist/lib/neonctl.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"neonctl.js","names":[],"sources":["../../src/lib/neonctl.ts"],"sourcesContent":["import { execa } from \"execa\";\n\n/**\n * Returns the neonctl command prefix: \"CI= npx -y neonctl\".\n *\n * neonctl reads NEON_API_HOST and NEON_OAUTH_HOST from the environment\n * directly, so no extra flags are needed.\n *\n * Usage: `${neonctlCmd()} orgs list --output json`\n */\nexport function neonctlCmd(): string {\n\treturn \"CI= npx -y neonctl\";\n}\n\n/**\n * Detects which package manager was used to invoke the current process.\n * Reads the `npm_config_user_agent` env var set by npm/pnpm/yarn/bun when\n * they spawn child processes (including via `npx`, `pnpx`, `bunx`, etc.).\n *\n * Falls back to \"npm\" if detection fails.\n */\nexport function detectPackageManager(): \"npm\" | \"pnpm\" | \"yarn\" | \"bun\" {\n\tconst ua = process.env.npm_config_user_agent;\n\tif (ua) {\n\t\tif (ua.startsWith(\"pnpm/\")) return \"pnpm\";\n\t\tif (ua.startsWith(\"yarn/\")) return \"yarn\";\n\t\tif (ua.startsWith(\"bun/\")) return \"bun\";\n\t}\n\treturn \"npm\";\n}\n\n/**\n * Returns the global install command for a given package manager.\n */\nfunction globalInstallArgs(\n\tpm: \"npm\" | \"pnpm\" | \"yarn\" | \"bun\",\n\tpkg: string,\n): { command: string; args: string[] } {\n\tswitch (pm) {\n\t\tcase \"pnpm\":\n\t\t\treturn { command: \"pnpm\", args: [\"add\", \"-g\", pkg] };\n\t\tcase \"yarn\":\n\t\t\treturn { command: \"yarn\", args: [\"global\", \"add\", pkg] };\n\t\tcase \"bun\":\n\t\t\treturn { command: \"bun\", args: [\"add\", \"-g\", pkg] };\n\t\tdefault:\n\t\t\treturn { command: \"npm\", args: [\"install\", \"-g\", pkg] };\n\t}\n}\n\ninterface NeonctlStatus {\n\tinstalled: boolean;\n\tcurrentVersion: string | null;\n\tlatestVersion: string | null;\n\tneedsUpdate: boolean;\n}\n\n/**\n * Gets the currently available neonctl version.\n * Tries the global binary first, then falls back to npx.\n */\nasync function getNeonctlVersion(): Promise<string | null> {\n\t// Try global binary first (fast path)\n\ttry {\n\t\tconst result = await execa(\"neonctl\", [\"--version\"], {\n\t\t\tstdio: \"pipe\",\n\t\t\ttimeout: 5000,\n\t\t});\n\t\tconst match = result.stdout.trim().match(/(\\d+\\.\\d+\\.\\d+)/);\n\t\tif (match) return match[1];\n\t} catch {\n\t\t// Not globally installed — that's fine\n\t}\n\treturn null;\n}\n\n/**\n * Checks whether the neonctl CLI is globally installed and whether it's up to date.\n */\nexport async function checkNeonctl(): Promise<NeonctlStatus> {\n\tconst currentVersion = await getNeonctlVersion();\n\n\tif (!currentVersion) {\n\t\treturn {\n\t\t\tinstalled: false,\n\t\t\tcurrentVersion: null,\n\t\t\tlatestVersion: null,\n\t\t\tneedsUpdate: true,\n\t\t};\n\t}\n\n\t// Check latest version from npm registry\n\tlet latestVersion: string | null = null;\n\ttry {\n\t\tconst result = await execa(\"npm\", [\"view\", \"neonctl\", \"version\"], {\n\t\t\tstdio: \"pipe\",\n\t\t\ttimeout: 10000,\n\t\t});\n\t\tlatestVersion = result.stdout.trim();\n\t} catch {\n\t\t// Can't determine latest — assume current is fine\n\t\treturn {\n\t\t\tinstalled: true,\n\t\t\tcurrentVersion,\n\t\t\tlatestVersion: null,\n\t\t\tneedsUpdate: false,\n\t\t};\n\t}\n\n\tconst needsUpdate =\n\t\tcurrentVersion !== null &&\n\t\tlatestVersion !== null &&\n\t\tcurrentVersion !== latestVersion &&\n\t\tisOlderVersion(currentVersion, latestVersion);\n\n\treturn { installed: true, currentVersion, latestVersion, needsUpdate };\n}\n\nfunction isOlderVersion(current: string, latest: string): boolean {\n\tconst c = current.split(\".\").map(Number);\n\tconst l = latest.split(\".\").map(Number);\n\tfor (let i = 0; i < 3; i++) {\n\t\tif ((c[i] ?? 0) < (l[i] ?? 0)) return true;\n\t\tif ((c[i] ?? 0) > (l[i] ?? 0)) return false;\n\t}\n\treturn false;\n}\n\nexport interface EnsureNeonctlResult {\n\tstatus: \"already_current\" | \"installed\" | \"updated\" | \"failed\";\n\tversion?: string;\n\terror?: string;\n}\n\n/**\n * Ensures neonctl is globally installed and up to date.\n * Uses the same package manager that invoked the init command.\n */\nexport async function ensureNeonctl(): Promise<EnsureNeonctlResult> {\n\tconst check = await checkNeonctl();\n\n\tif (check.installed && !check.needsUpdate) {\n\t\treturn {\n\t\t\tstatus: \"already_current\",\n\t\t\tversion: check.currentVersion ?? undefined,\n\t\t};\n\t}\n\n\tconst pm = detectPackageManager();\n\tconst { command, args } = globalInstallArgs(pm, \"neonctl\");\n\n\ttry {\n\t\tawait execa(command, args, { stdio: \"pipe\", timeout: 60000 });\n\n\t\t// Verify installation\n\t\tconst version = await getNeonctlVersion();\n\t\treturn {\n\t\t\tstatus: check.installed ? \"updated\" : \"installed\",\n\t\t\tversion: version ?? undefined,\n\t\t};\n\t} catch (err) {\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\terror: err instanceof Error ? err.message : \"Unknown error\",\n\t\t};\n\t}\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"neonctl.js","names":[],"sources":["../../src/lib/neonctl.ts"],"sourcesContent":["import { lstatSync } from \"node:fs\";\nimport { execa } from \"execa\";\n\n/**\n * Returns the neonctl command prefix: \"CI= npx -y neonctl\".\n *\n * neonctl reads NEON_API_HOST and NEON_OAUTH_HOST from the environment\n * directly, so no extra flags are needed.\n *\n * Usage: `${neonctlCmd()} orgs list --output json`\n */\nexport function neonctlCmd(): string {\n\treturn \"CI= npx -y neonctl\";\n}\n\n/**\n * Detects which package manager was used to invoke the current process.\n * Reads the `npm_config_user_agent` env var set by npm/pnpm/yarn/bun when\n * they spawn child processes (including via `npx`, `pnpx`, `bunx`, etc.).\n *\n * Falls back to \"npm\" if detection fails.\n */\nexport function detectPackageManager(): \"npm\" | \"pnpm\" | \"yarn\" | \"bun\" {\n\tconst ua = process.env.npm_config_user_agent;\n\tif (ua) {\n\t\tif (ua.startsWith(\"pnpm/\")) return \"pnpm\";\n\t\tif (ua.startsWith(\"yarn/\")) return \"yarn\";\n\t\tif (ua.startsWith(\"bun/\")) return \"bun\";\n\t}\n\treturn \"npm\";\n}\n\n/**\n * Returns the global install command for a given package manager.\n */\nfunction globalInstallArgs(\n\tpm: \"npm\" | \"pnpm\" | \"yarn\" | \"bun\",\n\tpkg: string,\n): { command: string; args: string[] } {\n\tswitch (pm) {\n\t\tcase \"pnpm\":\n\t\t\treturn { command: \"pnpm\", args: [\"add\", \"-g\", pkg] };\n\t\tcase \"yarn\":\n\t\t\treturn { command: \"yarn\", args: [\"global\", \"add\", pkg] };\n\t\tcase \"bun\":\n\t\t\treturn { command: \"bun\", args: [\"add\", \"-g\", pkg] };\n\t\tdefault:\n\t\t\treturn { command: \"npm\", args: [\"install\", \"-g\", pkg] };\n\t}\n}\n\ninterface NeonctlStatus {\n\tinstalled: boolean;\n\tcurrentVersion: string | null;\n\tlatestVersion: string | null;\n\tneedsUpdate: boolean;\n}\n\n/**\n * Gets the currently available neonctl version.\n * Tries the global binary first, then falls back to npx.\n */\nasync function getNeonctlVersion(): Promise<string | null> {\n\t// Try global binary first (fast path)\n\ttry {\n\t\tconst result = await execa(\"neonctl\", [\"--version\"], {\n\t\t\tstdio: \"pipe\",\n\t\t\ttimeout: 5000,\n\t\t});\n\t\tconst match = result.stdout.trim().match(/(\\d+\\.\\d+\\.\\d+)/);\n\t\tif (match) return match[1];\n\t} catch {\n\t\t// Not globally installed — that's fine\n\t}\n\treturn null;\n}\n\n/**\n * Checks whether the neonctl CLI is globally installed and whether it's up to date.\n */\nexport async function checkNeonctl(): Promise<NeonctlStatus> {\n\tconst currentVersion = await getNeonctlVersion();\n\n\tif (!currentVersion) {\n\t\treturn {\n\t\t\tinstalled: false,\n\t\t\tcurrentVersion: null,\n\t\t\tlatestVersion: null,\n\t\t\tneedsUpdate: true,\n\t\t};\n\t}\n\n\t// Check latest version from npm registry\n\tlet latestVersion: string | null = null;\n\ttry {\n\t\tconst result = await execa(\"npm\", [\"view\", \"neonctl\", \"version\"], {\n\t\t\tstdio: \"pipe\",\n\t\t\ttimeout: 10000,\n\t\t});\n\t\tlatestVersion = result.stdout.trim();\n\t} catch {\n\t\t// Can't determine latest — assume current is fine\n\t\treturn {\n\t\t\tinstalled: true,\n\t\t\tcurrentVersion,\n\t\t\tlatestVersion: null,\n\t\t\tneedsUpdate: false,\n\t\t};\n\t}\n\n\tconst needsUpdate =\n\t\tcurrentVersion !== null &&\n\t\tlatestVersion !== null &&\n\t\tcurrentVersion !== latestVersion &&\n\t\tisOlderVersion(currentVersion, latestVersion);\n\n\treturn { installed: true, currentVersion, latestVersion, needsUpdate };\n}\n\nfunction isOlderVersion(current: string, latest: string): boolean {\n\tconst c = current.split(\".\").map(Number);\n\tconst l = latest.split(\".\").map(Number);\n\tfor (let i = 0; i < 3; i++) {\n\t\tif ((c[i] ?? 0) < (l[i] ?? 0)) return true;\n\t\tif ((c[i] ?? 0) > (l[i] ?? 0)) return false;\n\t}\n\treturn false;\n}\n\nexport interface EnsureNeonctlResult {\n\tstatus: \"already_current\" | \"installed\" | \"updated\" | \"failed\";\n\tversion?: string;\n\terror?: string;\n}\n\n/**\n * Checks if neonctl is installed via a local dev symlink.\n * If so, skip install/update — the developer manages it manually.\n */\nfunction isLocalDevSymlink(): boolean {\n\ttry {\n\t\tconst home = process.env.HOME || process.env.USERPROFILE || \"\";\n\t\tconst nvmDir = process.env.NVM_DIR || `${home}/.nvm`;\n\t\t// Check common global module locations for a symlink\n\t\tconst candidates = [\n\t\t\t`${nvmDir}/versions/node/${process.version}/lib/node_modules/neonctl`,\n\t\t\t`${home}/.nvm/versions/node/${process.version}/lib/node_modules/neonctl`,\n\t\t];\n\t\tfor (const candidate of candidates) {\n\t\t\ttry {\n\t\t\t\tconst stat = lstatSync(candidate);\n\t\t\t\tif (stat.isSymbolicLink()) return true;\n\t\t\t} catch {\n\t\t\t\t// path doesn't exist\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Ensures neonctl is globally installed and up to date.\n * Uses the same package manager that invoked the init command.\n */\nexport async function ensureNeonctl(): Promise<EnsureNeonctlResult> {\n\t// Skip install for local dev symlinks to avoid permission errors\n\tif (isLocalDevSymlink()) {\n\t\tconst version = await getNeonctlVersion();\n\t\treturn {\n\t\t\tstatus: \"already_current\",\n\t\t\tversion: version ?? \"dev\",\n\t\t};\n\t}\n\n\tconst check = await checkNeonctl();\n\n\tif (check.installed && !check.needsUpdate) {\n\t\treturn {\n\t\t\tstatus: \"already_current\",\n\t\t\tversion: check.currentVersion ?? undefined,\n\t\t};\n\t}\n\n\tconst pm = detectPackageManager();\n\tconst { command, args } = globalInstallArgs(pm, \"neonctl\");\n\n\ttry {\n\t\tawait execa(command, args, { stdio: \"pipe\", timeout: 60000 });\n\n\t\t// Verify installation\n\t\tconst version = await getNeonctlVersion();\n\t\treturn {\n\t\t\tstatus: check.installed ? \"updated\" : \"installed\",\n\t\t\tversion: version ?? undefined,\n\t\t};\n\t} catch (err) {\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\terror: err instanceof Error ? err.message : \"Unknown error\",\n\t\t};\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -6,6 +6,8 @@ interface SetupPhaseOptions {
|
|
|
6
6
|
agent?: string;
|
|
7
7
|
/** The IDE/editor the user is running in (e.g. "cursor", "vscode") — reported by agent */
|
|
8
8
|
ide?: string;
|
|
9
|
+
/** Enable preview skills (neon-object-storage, neon-functions, neon-ai-gateway) */
|
|
10
|
+
preview?: boolean;
|
|
9
11
|
/** Whether the directory already contains an application */
|
|
10
12
|
hasApp?: boolean;
|
|
11
13
|
/** Template ID to scaffold (when bootstrapping a new project) */
|
|
@@ -15,6 +17,7 @@ interface SetupPhaseOptions {
|
|
|
15
17
|
/** Neon features selected by the user (brownfield flows) */
|
|
16
18
|
features?: NeonFeature[];
|
|
17
19
|
mcpConfigured?: boolean | null;
|
|
20
|
+
skillsInstalled?: boolean | null;
|
|
18
21
|
connectionString?: boolean | null;
|
|
19
22
|
connectionParams?: string;
|
|
20
23
|
framework?: string;
|
|
@@ -22,9 +25,9 @@ interface SetupPhaseOptions {
|
|
|
22
25
|
migrationTool?: string;
|
|
23
26
|
migrationDir?: string;
|
|
24
27
|
isVscodeIde?: boolean | null;
|
|
25
|
-
mode?:
|
|
26
|
-
mcpScope?:
|
|
27
|
-
skillsScope?:
|
|
28
|
+
mode?: string;
|
|
29
|
+
mcpScope?: string;
|
|
30
|
+
skillsScope?: string;
|
|
28
31
|
installExtension?: boolean;
|
|
29
32
|
execute?: boolean;
|
|
30
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","names":[],"sources":["../../../src/lib/phases/setup.ts"],"mappings":";;;;UAsBiB,iBAAA;;EAAA;EAAiB,GAAA,CAAA,EAAA,MAAA;;
|
|
1
|
+
{"version":3,"file":"setup.d.ts","names":[],"sources":["../../../src/lib/phases/setup.ts"],"mappings":";;;;UAsBiB,iBAAA;;EAAA;EAAiB,GAAA,CAAA,EAAA,MAAA;;SAatB,CAAA,EAAA,OAAA;EAAW;EA2BD,MAAA,CAAA,EAAA,OAAA;EAAgB;UAC5B,CAAA,EAAA,MAAA;;kBACP,CAAA,EA/BiB,WA+BjB,EAAA;EAAO;aA7BE;;;;;;;;;;;;;;;;;;;;;;;iBA2BU,gBAAA,UACZ,oBACP,QAAQ"}
|