neon-init 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/cli.js +366 -30
  2. package/dist/cli.js.map +1 -1
  3. package/dist/index.d.ts +15 -3
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +207 -13
  6. package/dist/index.js.map +1 -1
  7. package/dist/interactive.d.ts +12 -0
  8. package/dist/interactive.d.ts.map +1 -0
  9. package/dist/interactive.js +495 -0
  10. package/dist/interactive.js.map +1 -0
  11. package/dist/lib/agents.d.ts +6 -1
  12. package/dist/lib/agents.d.ts.map +1 -1
  13. package/dist/lib/agents.js +62 -1
  14. package/dist/lib/agents.js.map +1 -1
  15. package/dist/lib/auth.d.ts +10 -3
  16. package/dist/lib/auth.d.ts.map +1 -1
  17. package/dist/lib/auth.js +19 -11
  18. package/dist/lib/auth.js.map +1 -1
  19. package/dist/lib/bootstrap.d.ts +30 -0
  20. package/dist/lib/bootstrap.d.ts.map +1 -0
  21. package/dist/lib/bootstrap.js +61 -0
  22. package/dist/lib/bootstrap.js.map +1 -0
  23. package/dist/lib/build-config.d.ts +5 -0
  24. package/dist/lib/build-config.d.ts.map +1 -0
  25. package/dist/lib/build-config.js +6 -0
  26. package/dist/lib/build-config.js.map +1 -0
  27. package/dist/lib/detect-agent.d.ts +22 -0
  28. package/dist/lib/detect-agent.d.ts.map +1 -0
  29. package/dist/lib/detect-agent.js +65 -0
  30. package/dist/lib/detect-agent.js.map +1 -0
  31. package/dist/lib/editors.d.ts.map +1 -1
  32. package/dist/lib/editors.js.map +1 -1
  33. package/dist/lib/extension.d.ts +11 -3
  34. package/dist/lib/extension.d.ts.map +1 -1
  35. package/dist/lib/extension.js +28 -7
  36. package/dist/lib/extension.js.map +1 -1
  37. package/dist/lib/inspect.d.ts +28 -0
  38. package/dist/lib/inspect.d.ts.map +1 -0
  39. package/dist/lib/inspect.js +190 -0
  40. package/dist/lib/inspect.js.map +1 -0
  41. package/dist/lib/install.d.ts +10 -4
  42. package/dist/lib/install.d.ts.map +1 -1
  43. package/dist/lib/install.js +37 -20
  44. package/dist/lib/install.js.map +1 -1
  45. package/dist/lib/neonctl.d.ts +32 -0
  46. package/dist/lib/neonctl.d.ts.map +1 -0
  47. package/dist/lib/neonctl.js +149 -0
  48. package/dist/lib/neonctl.js.map +1 -0
  49. package/dist/lib/phases/auth.d.ts +12 -0
  50. package/dist/lib/phases/auth.d.ts.map +1 -0
  51. package/dist/lib/phases/auth.js +188 -0
  52. package/dist/lib/phases/auth.js.map +1 -0
  53. package/dist/lib/phases/cleanup.d.ts +12 -0
  54. package/dist/lib/phases/cleanup.d.ts.map +1 -0
  55. package/dist/lib/phases/cleanup.js +29 -0
  56. package/dist/lib/phases/cleanup.js.map +1 -0
  57. package/dist/lib/phases/db.d.ts +17 -0
  58. package/dist/lib/phases/db.d.ts.map +1 -0
  59. package/dist/lib/phases/db.js +258 -0
  60. package/dist/lib/phases/db.js.map +1 -0
  61. package/dist/lib/phases/getting-started.d.ts +26 -0
  62. package/dist/lib/phases/getting-started.d.ts.map +1 -0
  63. package/dist/lib/phases/getting-started.js +195 -0
  64. package/dist/lib/phases/getting-started.js.map +1 -0
  65. package/dist/lib/phases/mcp.d.ts +15 -0
  66. package/dist/lib/phases/mcp.d.ts.map +1 -0
  67. package/dist/lib/phases/mcp.js +179 -0
  68. package/dist/lib/phases/mcp.js.map +1 -0
  69. package/dist/lib/phases/migrations.d.ts +14 -0
  70. package/dist/lib/phases/migrations.d.ts.map +1 -0
  71. package/dist/lib/phases/migrations.js +239 -0
  72. package/dist/lib/phases/migrations.js.map +1 -0
  73. package/dist/lib/phases/neon-auth.d.ts +13 -0
  74. package/dist/lib/phases/neon-auth.d.ts.map +1 -0
  75. package/dist/lib/phases/neon-auth.js +117 -0
  76. package/dist/lib/phases/neon-auth.js.map +1 -0
  77. package/dist/lib/phases/setup.d.ts +41 -0
  78. package/dist/lib/phases/setup.d.ts.map +1 -0
  79. package/dist/lib/phases/setup.js +689 -0
  80. package/dist/lib/phases/setup.js.map +1 -0
  81. package/dist/lib/phases/skills.d.ts +14 -0
  82. package/dist/lib/phases/skills.d.ts.map +1 -0
  83. package/dist/lib/phases/skills.js +80 -0
  84. package/dist/lib/phases/skills.js.map +1 -0
  85. package/dist/lib/phases/status.d.ts +10 -0
  86. package/dist/lib/phases/status.d.ts.map +1 -0
  87. package/dist/lib/phases/status.js +65 -0
  88. package/dist/lib/phases/status.js.map +1 -0
  89. package/dist/lib/resolve-context.d.ts +19 -0
  90. package/dist/lib/resolve-context.d.ts.map +1 -0
  91. package/dist/lib/resolve-context.js +112 -0
  92. package/dist/lib/resolve-context.js.map +1 -0
  93. package/dist/lib/route-command.d.ts +8 -0
  94. package/dist/lib/route-command.d.ts.map +1 -0
  95. package/dist/lib/route-command.js +195 -0
  96. package/dist/lib/route-command.js.map +1 -0
  97. package/dist/lib/skills.d.ts +20 -3
  98. package/dist/lib/skills.d.ts.map +1 -1
  99. package/dist/lib/skills.js +116 -12
  100. package/dist/lib/skills.js.map +1 -1
  101. package/dist/lib/types.d.ts +150 -1
  102. package/dist/lib/types.d.ts.map +1 -1
  103. package/dist/lib/vsix.d.ts +15 -0
  104. package/dist/lib/vsix.d.ts.map +1 -0
  105. package/dist/lib/vsix.js +91 -0
  106. package/dist/lib/vsix.js.map +1 -0
  107. package/dist/v2.d.ts +31 -0
  108. package/dist/v2.d.ts.map +1 -0
  109. package/dist/v2.js +147 -0
  110. package/dist/v2.js.map +1 -0
  111. package/package.json +7 -3
@@ -0,0 +1,28 @@
1
+ import { AgentCheck } from "./types.js";
2
+
3
+ //#region src/lib/inspect.d.ts
4
+ interface InspectionResults {
5
+ [key: string]: unknown;
6
+ mcpConfigured?: boolean;
7
+ skillsInstalled?: boolean;
8
+ /** True if a Neon-specific connection string (DATABASE_URL with "neon" or PGHOST with "neon") is found */
9
+ connectionString?: boolean;
10
+ /** True if any DATABASE_URL is set in .env (regardless of provider) */
11
+ databaseUrl?: boolean;
12
+ framework?: string;
13
+ orm?: string;
14
+ migrationTool?: string;
15
+ migrationDir?: string;
16
+ isVscodeIde?: boolean;
17
+ agent?: string;
18
+ /** True if the directory contains an application (package.json with deps, or source files) */
19
+ hasApp?: boolean;
20
+ }
21
+ /**
22
+ * Runs filesystem checks based on the agent_check descriptors.
23
+ * Maps check IDs to concrete inspection functions.
24
+ */
25
+ declare function inspectProject(checks: AgentCheck[]): Promise<InspectionResults>;
26
+ //#endregion
27
+ export { InspectionResults, inspectProject };
28
+ //# sourceMappingURL=inspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.d.ts","names":[],"sources":["../../src/lib/inspect.ts"],"mappings":";;;UASiB,iBAAA;;EAAA,aAAA,CAAA,EAAA,OAAiB;EAsBZ,eAAA,CAAA,EAAc,OAAA;EAAA;kBAC3B,CAAA,EAAA,OAAA;;aACN,CAAA,EAAA,OAAA;EAAO,SAAA,CAAA,EAAA,MAAA;;;;;;;;;;;;;iBAFY,cAAA,SACb,eACN,QAAQ"}
@@ -0,0 +1,190 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ //#region src/lib/inspect.ts
4
+ /**
5
+ * Filesystem-based project inspection for interactive (agentless) mode.
6
+ * Replaces the "agent_check" pattern — instead of asking an agent to look,
7
+ * we examine the filesystem directly.
8
+ */
9
+ /**
10
+ * Runs filesystem checks based on the agent_check descriptors.
11
+ * Maps check IDs to concrete inspection functions.
12
+ */
13
+ async function inspectProject(checks) {
14
+ const results = {};
15
+ const cwd = process.cwd();
16
+ for (const check of checks) switch (check.id) {
17
+ case "mcp_server":
18
+ results.mcpConfigured = checkMcpServer(cwd);
19
+ break;
20
+ case "connection_string":
21
+ results.connectionString = checkConnectionString(cwd);
22
+ break;
23
+ case "database_url":
24
+ results.databaseUrl = checkDatabaseUrl(cwd);
25
+ break;
26
+ case "project_stack": {
27
+ const stack = detectProjectStack(cwd);
28
+ results.framework = stack.framework;
29
+ results.orm = stack.orm;
30
+ break;
31
+ }
32
+ case "migrations": {
33
+ const migrations = detectMigrations(cwd);
34
+ results.migrationTool = migrations.tool;
35
+ results.migrationDir = migrations.dir;
36
+ break;
37
+ }
38
+ case "skills":
39
+ results.skillsInstalled = checkSkillsInstalled(cwd);
40
+ break;
41
+ case "ide_type":
42
+ results.isVscodeIde = checkVscodeIde();
43
+ break;
44
+ case "has_app":
45
+ results.hasApp = checkHasApp(cwd);
46
+ break;
47
+ case "agent_type": break;
48
+ }
49
+ return results;
50
+ }
51
+ 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
+ const home = process.env.HOME || process.env.USERPROFILE || "";
58
+ const globalCursorMcp = resolve(home, ".cursor", "mcp.json");
59
+ if (existsSync(globalCursorMcp)) try {
60
+ const content = readFileSync(globalCursorMcp, "utf-8");
61
+ if (content.includes("neon") || content.includes("mcp.neon.tech")) return true;
62
+ } catch {}
63
+ for (const claudeFile of ["settings.local.json", "settings.json"]) {
64
+ const claudeConfig = resolve(home, ".claude", claudeFile);
65
+ if (existsSync(claudeConfig)) try {
66
+ const content = readFileSync(claudeConfig, "utf-8");
67
+ if (content.includes("neon") || content.includes("mcp.neon.tech")) return true;
68
+ } catch {}
69
+ }
70
+ const vscodeSettings = resolve(cwd, ".vscode", "settings.json");
71
+ if (existsSync(vscodeSettings)) try {
72
+ const content = readFileSync(vscodeSettings, "utf-8");
73
+ if (content.includes("neon") || content.includes("mcp.neon.tech")) return true;
74
+ } catch {}
75
+ return false;
76
+ }
77
+ function checkConnectionString(cwd) {
78
+ for (const envFile of [".env", ".env.local"]) {
79
+ const envPath = resolve(cwd, envFile);
80
+ if (existsSync(envPath)) try {
81
+ const content = readFileSync(envPath, "utf-8");
82
+ if (/^DATABASE_URL=.*neon/m.test(content) || /^PGHOST=.*neon/m.test(content)) return true;
83
+ } catch {}
84
+ }
85
+ return false;
86
+ }
87
+ function detectProjectStack(cwd) {
88
+ const result = {
89
+ framework: "none",
90
+ orm: "none"
91
+ };
92
+ const pkgPath = resolve(cwd, "package.json");
93
+ if (!existsSync(pkgPath)) return result;
94
+ try {
95
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
96
+ const allDeps = {
97
+ ...pkg.dependencies,
98
+ ...pkg.devDependencies
99
+ };
100
+ if (allDeps.next) result.framework = "next";
101
+ else if (allDeps.remix || allDeps["@remix-run/node"]) result.framework = "remix";
102
+ else if (allDeps.express) result.framework = "express";
103
+ else if (allDeps.hono) result.framework = "hono";
104
+ else if (allDeps.fastify) result.framework = "fastify";
105
+ else if (allDeps["@sveltejs/kit"]) result.framework = "sveltekit";
106
+ else if (allDeps.nuxt) result.framework = "nuxt";
107
+ else if (allDeps.astro) result.framework = "astro";
108
+ if (allDeps.prisma || allDeps["@prisma/client"]) result.orm = "prisma";
109
+ else if (allDeps["drizzle-orm"]) result.orm = "drizzle";
110
+ else if (allDeps.knex) result.orm = "knex";
111
+ else if (allDeps.typeorm) result.orm = "typeorm";
112
+ else if (allDeps.sequelize) result.orm = "sequelize";
113
+ else if (allDeps["@neondatabase/serverless"]) result.orm = "neon-serverless";
114
+ } catch {}
115
+ return result;
116
+ }
117
+ function detectMigrations(cwd) {
118
+ if (existsSync(resolve(cwd, "prisma", "schema.prisma"))) return {
119
+ tool: "prisma",
120
+ dir: existsSync(resolve(cwd, "prisma", "migrations")) ? "prisma/migrations" : "none"
121
+ };
122
+ if (existsSync(resolve(cwd, "drizzle.config.ts")) || existsSync(resolve(cwd, "drizzle.config.js"))) return {
123
+ tool: "drizzle",
124
+ dir: existsSync(resolve(cwd, "drizzle")) ? "drizzle" : "none"
125
+ };
126
+ if (existsSync(resolve(cwd, "knexfile.js")) || existsSync(resolve(cwd, "knexfile.ts"))) return {
127
+ tool: "knex",
128
+ dir: existsSync(resolve(cwd, "migrations")) ? "migrations" : "none"
129
+ };
130
+ return {
131
+ tool: "none",
132
+ dir: "none"
133
+ };
134
+ }
135
+ function checkDatabaseUrl(cwd) {
136
+ const envPath = resolve(cwd, ".env");
137
+ if (existsSync(envPath)) try {
138
+ const content = readFileSync(envPath, "utf-8");
139
+ if (/^DATABASE_URL=/m.test(content)) return true;
140
+ } catch {}
141
+ return false;
142
+ }
143
+ function checkSkillsInstalled(cwd) {
144
+ const home = process.env.HOME || process.env.USERPROFILE || "";
145
+ const skillsDirs = [
146
+ resolve(cwd, ".cursor", "skills"),
147
+ resolve(cwd, ".claude", "skills"),
148
+ resolve(cwd, ".agents", "skills"),
149
+ resolve(home, ".cursor", "skills"),
150
+ resolve(home, ".claude", "skills")
151
+ ];
152
+ for (const dir of skillsDirs) if (existsSync(resolve(dir, "neon-postgres", "SKILL.md"))) return true;
153
+ const claudeMd = resolve(cwd, "CLAUDE.md");
154
+ if (existsSync(claudeMd)) try {
155
+ if (readFileSync(claudeMd, "utf-8").includes("neon-postgres")) return true;
156
+ } catch {}
157
+ return false;
158
+ }
159
+ function checkVscodeIde() {
160
+ const env = process.env;
161
+ return !!(env.TERM_PROGRAM === "vscode" || env.TERM_PROGRAM === "cursor" || env.TERM_PROGRAM === "windsurf" || env.VSCODE_PID || env.VSCODE_CWD);
162
+ }
163
+ /**
164
+ * Detects whether the current directory contains an application.
165
+ * Returns true if package.json has dependencies or common source directories exist.
166
+ */
167
+ function checkHasApp(cwd) {
168
+ const pkgPath = resolve(cwd, "package.json");
169
+ if (existsSync(pkgPath)) try {
170
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
171
+ const deps = Object.keys(pkg.dependencies ?? {});
172
+ const devDeps = Object.keys(pkg.devDependencies ?? {});
173
+ if (deps.length > 0 || devDeps.length > 0) return true;
174
+ } catch {}
175
+ for (const indicator of [
176
+ "src",
177
+ "app",
178
+ "pages",
179
+ "lib",
180
+ "index.ts",
181
+ "index.js",
182
+ "main.ts",
183
+ "main.js"
184
+ ]) if (existsSync(resolve(cwd, indicator))) return true;
185
+ return false;
186
+ }
187
+ //#endregion
188
+ export { inspectProject };
189
+
190
+ //# sourceMappingURL=inspect.js.map
@@ -0,0 +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,11 +1,17 @@
1
1
  import { Editor, InstallStatus } from "./types.js";
2
2
 
3
3
  //#region src/lib/install.d.ts
4
-
4
+ interface InstallNeonOptions {
5
+ json?: boolean;
6
+ }
5
7
  /**
6
- * Installs Neon's Local Connect extension or MCP Server for specific editors
8
+ * Installs Neon's Local Connect extension or MCP Server for specific editors.
9
+ * Returns a map of editor → install status and whether auth succeeded.
7
10
  */
8
- declare function installNeon(selectedEditors: Editor[]): Promise<Map<Editor, InstallStatus>>;
11
+ declare function installNeon(selectedEditors: Editor[], options?: InstallNeonOptions): Promise<{
12
+ results: Map<Editor, InstallStatus>;
13
+ authSuccess: boolean;
14
+ }>;
9
15
  //#endregion
10
- export { installNeon };
16
+ export { InstallNeonOptions, installNeon };
11
17
  //# sourceMappingURL=install.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":[],"mappings":";;;;;;AAiDA;AAAiC,iBAAX,WAAA,CAAW,eAAA,EACf,MADe,EAAA,CAAA,EAE9B,OAF8B,CAEtB,GAFsB,CAElB,MAFkB,EAEV,aAFU,CAAA,CAAA"}
1
+ {"version":3,"file":"install.d.ts","names":[],"sources":["../../src/lib/install.ts"],"mappings":";;;UAkDiB,kBAAA;;AAAjB;AAQA;;;;AAG0B,iBAHJ,WAAA,CAGI,eAAA,EAFR,MAEQ,EAAA,EAAA,OAAA,CAAA,EADf,kBACe,CAAA,EAAvB,OAAuB,CAAA;SAAQ,EAAZ,GAAY,CAAR,MAAQ,EAAA,aAAA,CAAA;aAAZ,EAAA,OAAA"}
@@ -1,6 +1,6 @@
1
1
  import { getAddMcpAgentId } from "./agents.js";
2
- import { configureExtension, installExtension, usesExtension, waitForExtensionInstalled } from "./extension.js";
3
2
  import { createApiKeyFromNeonctl, ensureNeonctlAuth } from "./auth.js";
3
+ import { configureExtension, installExtension, usesExtension, waitForExtensionInstalled } from "./extension.js";
4
4
  import { log, spinner } from "@clack/prompts";
5
5
  import { execa } from "execa";
6
6
  //#region src/lib/install.ts
@@ -29,27 +29,41 @@ async function installMCPServerViaAddMcp(editor, apiKey) {
29
29
  });
30
30
  }
31
31
  /**
32
- * Installs Neon's Local Connect extension or MCP Server for specific editors
32
+ * Installs Neon's Local Connect extension or MCP Server for specific editors.
33
+ * Returns a map of editor → install status and whether auth succeeded.
33
34
  */
34
- async function installNeon(selectedEditors) {
35
+ async function installNeon(selectedEditors, options) {
36
+ const quiet = options?.json === true;
37
+ const authOptions = { json: quiet };
35
38
  const results = /* @__PURE__ */ new Map();
36
39
  const extensionEditors = selectedEditors.filter(usesExtension);
37
40
  const mcpEditors = selectedEditors.filter((e) => !usesExtension(e));
38
- if (extensionEditors.length === 0 && mcpEditors.length === 0) return results;
39
- const authSpinner = spinner();
40
- authSpinner.start("Authenticating...");
41
- if (!await ensureNeonctlAuth()) {
42
- authSpinner.stop("Authentication failed");
41
+ if (extensionEditors.length === 0 && mcpEditors.length === 0) return {
42
+ results,
43
+ authSuccess: false
44
+ };
45
+ const authSpinner = quiet ? null : spinner();
46
+ authSpinner?.start("Authenticating...");
47
+ if (!await ensureNeonctlAuth(authOptions)) {
48
+ authSpinner?.stop("Authentication failed");
43
49
  for (const editor of selectedEditors) results.set(editor, "failed");
44
- return results;
50
+ return {
51
+ results,
52
+ authSuccess: false
53
+ };
45
54
  }
46
- authSpinner.stop("Authentication successful ✓");
47
- const apiKey = await createApiKeyFromNeonctl();
55
+ authSpinner?.stop("Authentication successful ✓");
56
+ const apiKey = await createApiKeyFromNeonctl(authOptions);
48
57
  if (!apiKey) {
49
- log.error("Could not create API key after authentication.");
50
- log.info("You can manually create one at: https://console.neon.tech/app/settings/api-keys");
58
+ if (!quiet) {
59
+ log.error("Could not create API key after authentication.");
60
+ log.info("You can manually create one at: https://console.neon.tech/app/settings/api-keys");
61
+ }
51
62
  for (const editor of selectedEditors) results.set(editor, "failed");
52
- return results;
63
+ return {
64
+ results,
65
+ authSuccess: true
66
+ };
53
67
  }
54
68
  for (const editor of extensionEditors) {
55
69
  if (!await installExtension(editor)) {
@@ -61,11 +75,11 @@ async function installNeon(selectedEditors) {
61
75
  continue;
62
76
  }
63
77
  if (await configureExtension(editor, apiKey)) results.set(editor, "success");
64
- else results.set(editor, "success");
78
+ else results.set(editor, "failed");
65
79
  }
66
80
  if (mcpEditors.length > 0) {
67
- const mcpSpinner = spinner();
68
- mcpSpinner.start("Installing and configuring Neon MCP Server...");
81
+ const mcpSpinner = quiet ? null : spinner();
82
+ mcpSpinner?.start("Installing and configuring Neon MCP Server...");
69
83
  let mcpSuccessCount = 0;
70
84
  for (const editor of mcpEditors) try {
71
85
  await installMCPServerViaAddMcp(editor, apiKey);
@@ -73,11 +87,14 @@ async function installNeon(selectedEditors) {
73
87
  mcpSuccessCount++;
74
88
  } catch (err) {
75
89
  results.set(editor, "failed");
76
- if (err && typeof err === "object" && "stderr" in err && err.stderr) log.error(String(err.stderr).trim() || "failed to install MCP server via add-mcp");
90
+ if (!quiet && err && typeof err === "object" && "stderr" in err && err.stderr) log.error(String(err.stderr).trim() || "failed to install MCP server via add-mcp");
77
91
  }
78
- mcpSpinner.stop(mcpSuccessCount > 0 ? "Neon MCP Server configuration complete ✓" : "Failed to configure Neon MCP Server");
92
+ mcpSpinner?.stop(mcpSuccessCount > 0 ? "Neon MCP Server configuration complete ✓" : "Failed to configure Neon MCP Server");
79
93
  }
80
- return results;
94
+ return {
95
+ results,
96
+ authSuccess: true
97
+ };
81
98
  }
82
99
  //#endregion
83
100
  export { installNeon };
@@ -1 +1 @@
1
- {"version":3,"file":"install.js","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":["import { log, spinner } from \"@clack/prompts\";\nimport { execa } from \"execa\";\nimport { getAddMcpAgentId } from \"./agents.js\";\nimport { createApiKeyFromNeonctl, ensureNeonctlAuth } from \"./auth.js\";\nimport {\n\tconfigureExtension,\n\tinstallExtension,\n\tusesExtension,\n\twaitForExtensionInstalled,\n} from \"./extension.js\";\nimport type { Editor, InstallStatus } from \"./types.js\";\n\nconst NEON_MCP_SERVER_URL = \"https://mcp.neon.tech/mcp\";\n\n/**\n * Installs Neon MCP Server for a single editor via the add-mcp CLI.\n * Uses API key authentication via the Authorization header.\n */\nasync function installMCPServerViaAddMcp(\n\teditor: Editor,\n\tapiKey: string,\n): Promise<void> {\n\tconst agentId = getAddMcpAgentId(editor);\n\n\tawait execa(\n\t\t\"npx\",\n\t\t[\n\t\t\t\"-y\",\n\t\t\t\"add-mcp\",\n\t\t\tNEON_MCP_SERVER_URL,\n\t\t\t\"--header\",\n\t\t\t`Authorization: Bearer ${apiKey}`,\n\t\t\t\"-g\",\n\t\t\t\"-n\",\n\t\t\t\"Neon\",\n\t\t\t\"-y\",\n\t\t\t\"-a\",\n\t\t\tagentId,\n\t\t],\n\t\t{\n\t\t\tstdio: \"pipe\",\n\t\t\ttimeout: 60000,\n\t\t},\n\t);\n}\n\n/**\n * Installs Neon's Local Connect extension or MCP Server for specific editors\n */\nexport async function installNeon(\n\tselectedEditors: Editor[],\n): Promise<Map<Editor, InstallStatus>> {\n\tconst results = new Map<Editor, InstallStatus>();\n\n\tconst extensionEditors = selectedEditors.filter(usesExtension);\n\tconst mcpEditors = selectedEditors.filter((e) => !usesExtension(e));\n\n\tif (extensionEditors.length === 0 && mcpEditors.length === 0) {\n\t\treturn results;\n\t}\n\n\tconst authSpinner = spinner();\n\tauthSpinner.start(\"Authenticating...\");\n\n\tconst authSuccess = await ensureNeonctlAuth();\n\n\tif (!authSuccess) {\n\t\tauthSpinner.stop(\"Authentication failed\");\n\t\tfor (const editor of selectedEditors) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn results;\n\t}\n\n\tauthSpinner.stop(\"Authentication successful ✓\");\n\n\t// Create API key using the OAuth token\n\tconst apiKey = await createApiKeyFromNeonctl();\n\n\tif (!apiKey) {\n\t\tlog.error(\"Could not create API key after authentication.\");\n\t\tlog.info(\n\t\t\t\"You can manually create one at: https://console.neon.tech/app/settings/api-keys\",\n\t\t);\n\t\tfor (const editor of selectedEditors) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn results;\n\t}\n\n\tfor (const editor of extensionEditors) {\n\t\tconst installSuccess = await installExtension(editor);\n\n\t\tif (!installSuccess) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst isReady = await waitForExtensionInstalled(editor);\n\t\tif (!isReady) {\n\t\t\t// Extension install command succeeded but extension didn't appear in list\n\t\t\tresults.set(editor, \"failed\");\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Configure the extension with the API key\n\t\tconst configSuccess = await configureExtension(editor, apiKey);\n\n\t\tif (configSuccess) {\n\t\t\tresults.set(editor, \"success\");\n\t\t} else {\n\t\t\t// Extension installed but auth failed but user can manually configure later\n\t\t\tresults.set(editor, \"success\");\n\t\t}\n\t}\n\n\tif (mcpEditors.length > 0) {\n\t\tconst mcpSpinner = spinner();\n\t\tmcpSpinner.start(\"Installing and configuring Neon MCP Server...\");\n\n\t\tlet mcpSuccessCount = 0;\n\t\tfor (const editor of mcpEditors) {\n\t\t\ttry {\n\t\t\t\tawait installMCPServerViaAddMcp(editor, apiKey);\n\t\t\t\tresults.set(editor, \"success\");\n\t\t\t\tmcpSuccessCount++;\n\t\t\t} catch (err) {\n\t\t\t\tresults.set(editor, \"failed\");\n\t\t\t\tif (\n\t\t\t\t\terr &&\n\t\t\t\t\ttypeof err === \"object\" &&\n\t\t\t\t\t\"stderr\" in err &&\n\t\t\t\t\terr.stderr\n\t\t\t\t) {\n\t\t\t\t\tlog.error(\n\t\t\t\t\t\tString(err.stderr).trim() ||\n\t\t\t\t\t\t\t\"failed to install MCP server via add-mcp\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tmcpSpinner.stop(\n\t\t\tmcpSuccessCount > 0\n\t\t\t\t? \"Neon MCP Server configuration complete ✓\"\n\t\t\t\t: \"Failed to configure Neon MCP Server\",\n\t\t);\n\t}\n\n\treturn results;\n}\n"],"mappings":";;;;;;AAYA,MAAM,sBAAsB;;;;;AAM5B,eAAe,0BACd,QACA,QACgB;CAChB,MAAM,UAAU,iBAAiB,OAAO;AAExC,OAAM,MACL,OACA;EACC;EACA;EACA;EACA;EACA,yBAAyB;EACzB;EACA;EACA;EACA;EACA;EACA;EACA,EACD;EACC,OAAO;EACP,SAAS;EACT,CACD;;;;;AAMF,eAAsB,YACrB,iBACsC;CACtC,MAAM,0BAAU,IAAI,KAA4B;CAEhD,MAAM,mBAAmB,gBAAgB,OAAO,cAAc;CAC9D,MAAM,aAAa,gBAAgB,QAAQ,MAAM,CAAC,cAAc,EAAE,CAAC;AAEnE,KAAI,iBAAiB,WAAW,KAAK,WAAW,WAAW,EAC1D,QAAO;CAGR,MAAM,cAAc,SAAS;AAC7B,aAAY,MAAM,oBAAoB;AAItC,KAAI,CAFgB,MAAM,mBAAmB,EAE3B;AACjB,cAAY,KAAK,wBAAwB;AACzC,OAAK,MAAM,UAAU,gBACpB,SAAQ,IAAI,QAAQ,SAAS;AAE9B,SAAO;;AAGR,aAAY,KAAK,8BAA8B;CAG/C,MAAM,SAAS,MAAM,yBAAyB;AAE9C,KAAI,CAAC,QAAQ;AACZ,MAAI,MAAM,iDAAiD;AAC3D,MAAI,KACH,kFACA;AACD,OAAK,MAAM,UAAU,gBACpB,SAAQ,IAAI,QAAQ,SAAS;AAE9B,SAAO;;AAGR,MAAK,MAAM,UAAU,kBAAkB;AAGtC,MAAI,CAFmB,MAAM,iBAAiB,OAAO,EAEhC;AACpB,WAAQ,IAAI,QAAQ,SAAS;AAC7B;;AAID,MAAI,CADY,MAAM,0BAA0B,OAAO,EACzC;AAEb,WAAQ,IAAI,QAAQ,SAAS;AAC7B;;AAMD,MAFsB,MAAM,mBAAmB,QAAQ,OAAO,CAG7D,SAAQ,IAAI,QAAQ,UAAU;MAG9B,SAAQ,IAAI,QAAQ,UAAU;;AAIhC,KAAI,WAAW,SAAS,GAAG;EAC1B,MAAM,aAAa,SAAS;AAC5B,aAAW,MAAM,gDAAgD;EAEjE,IAAI,kBAAkB;AACtB,OAAK,MAAM,UAAU,WACpB,KAAI;AACH,SAAM,0BAA0B,QAAQ,OAAO;AAC/C,WAAQ,IAAI,QAAQ,UAAU;AAC9B;WACQ,KAAK;AACb,WAAQ,IAAI,QAAQ,SAAS;AAC7B,OACC,OACA,OAAO,QAAQ,YACf,YAAY,OACZ,IAAI,OAEJ,KAAI,MACH,OAAO,IAAI,OAAO,CAAC,MAAM,IACxB,2CACD;;AAKJ,aAAW,KACV,kBAAkB,IACf,6CACA,sCACH;;AAGF,QAAO"}
1
+ {"version":3,"file":"install.js","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":["import { log, spinner } from \"@clack/prompts\";\nimport { execa } from \"execa\";\nimport { getAddMcpAgentId } from \"./agents.js\";\nimport {\n\ttype AuthOptions,\n\tcreateApiKeyFromNeonctl,\n\tensureNeonctlAuth,\n} from \"./auth.js\";\nimport {\n\tconfigureExtension,\n\tinstallExtension,\n\tusesExtension,\n\twaitForExtensionInstalled,\n} from \"./extension.js\";\nimport type { Editor, InstallStatus } from \"./types.js\";\n\nconst NEON_MCP_SERVER_URL = \"https://mcp.neon.tech/mcp\";\n\n/**\n * Installs Neon MCP Server for a single editor via the add-mcp CLI.\n * Uses API key authentication via the Authorization header.\n */\nasync function installMCPServerViaAddMcp(\n\teditor: Editor,\n\tapiKey: string,\n): Promise<void> {\n\tconst agentId = getAddMcpAgentId(editor);\n\n\tawait execa(\n\t\t\"npx\",\n\t\t[\n\t\t\t\"-y\",\n\t\t\t\"add-mcp\",\n\t\t\tNEON_MCP_SERVER_URL,\n\t\t\t\"--header\",\n\t\t\t`Authorization: Bearer ${apiKey}`,\n\t\t\t\"-g\",\n\t\t\t\"-n\",\n\t\t\t\"Neon\",\n\t\t\t\"-y\",\n\t\t\t\"-a\",\n\t\t\tagentId,\n\t\t],\n\t\t{\n\t\t\tstdio: \"pipe\",\n\t\t\ttimeout: 60000,\n\t\t},\n\t);\n}\n\nexport interface InstallNeonOptions {\n\tjson?: boolean;\n}\n\n/**\n * Installs Neon's Local Connect extension or MCP Server for specific editors.\n * Returns a map of editor → install status and whether auth succeeded.\n */\nexport async function installNeon(\n\tselectedEditors: Editor[],\n\toptions?: InstallNeonOptions,\n): Promise<{ results: Map<Editor, InstallStatus>; authSuccess: boolean }> {\n\tconst quiet = options?.json === true;\n\tconst authOptions: AuthOptions = { json: quiet };\n\tconst results = new Map<Editor, InstallStatus>();\n\n\tconst extensionEditors = selectedEditors.filter(usesExtension);\n\tconst mcpEditors = selectedEditors.filter((e) => !usesExtension(e));\n\n\tif (extensionEditors.length === 0 && mcpEditors.length === 0) {\n\t\treturn { results, authSuccess: false };\n\t}\n\n\tconst authSpinner = quiet ? null : spinner();\n\tauthSpinner?.start(\"Authenticating...\");\n\n\tconst authSuccess = await ensureNeonctlAuth(authOptions);\n\n\tif (!authSuccess) {\n\t\tauthSpinner?.stop(\"Authentication failed\");\n\t\tfor (const editor of selectedEditors) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn { results, authSuccess: false };\n\t}\n\n\tauthSpinner?.stop(\"Authentication successful ✓\");\n\n\tconst apiKey = await createApiKeyFromNeonctl(authOptions);\n\n\tif (!apiKey) {\n\t\tif (!quiet) {\n\t\t\tlog.error(\"Could not create API key after authentication.\");\n\t\t\tlog.info(\n\t\t\t\t\"You can manually create one at: https://console.neon.tech/app/settings/api-keys\",\n\t\t\t);\n\t\t}\n\t\tfor (const editor of selectedEditors) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn { results, authSuccess: true };\n\t}\n\n\tfor (const editor of extensionEditors) {\n\t\tconst installSuccess = await installExtension(editor);\n\n\t\tif (!installSuccess) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst isReady = await waitForExtensionInstalled(editor);\n\t\tif (!isReady) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst configSuccess = await configureExtension(editor, apiKey);\n\n\t\tif (configSuccess) {\n\t\t\tresults.set(editor, \"success\");\n\t\t} else {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t}\n\n\tif (mcpEditors.length > 0) {\n\t\tconst mcpSpinner = quiet ? null : spinner();\n\t\tmcpSpinner?.start(\"Installing and configuring Neon MCP Server...\");\n\n\t\tlet mcpSuccessCount = 0;\n\t\tfor (const editor of mcpEditors) {\n\t\t\ttry {\n\t\t\t\tawait installMCPServerViaAddMcp(editor, apiKey);\n\t\t\t\tresults.set(editor, \"success\");\n\t\t\t\tmcpSuccessCount++;\n\t\t\t} catch (err) {\n\t\t\t\tresults.set(editor, \"failed\");\n\t\t\t\tif (\n\t\t\t\t\t!quiet &&\n\t\t\t\t\terr &&\n\t\t\t\t\ttypeof err === \"object\" &&\n\t\t\t\t\t\"stderr\" in err &&\n\t\t\t\t\terr.stderr\n\t\t\t\t) {\n\t\t\t\t\tlog.error(\n\t\t\t\t\t\tString(err.stderr).trim() ||\n\t\t\t\t\t\t\t\"failed to install MCP server via add-mcp\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tmcpSpinner?.stop(\n\t\t\tmcpSuccessCount > 0\n\t\t\t\t? \"Neon MCP Server configuration complete ✓\"\n\t\t\t\t: \"Failed to configure Neon MCP Server\",\n\t\t);\n\t}\n\n\treturn { results, authSuccess: true };\n}\n"],"mappings":";;;;;;AAgBA,MAAM,sBAAsB;;;;;AAM5B,eAAe,0BACd,QACA,QACgB;CAChB,MAAM,UAAU,iBAAiB,MAAM;CAEvC,MAAM,MACL,OACA;EACC;EACA;EACA;EACA;EACA,yBAAyB;EACzB;EACA;EACA;EACA;EACA;EACA;CACD,GACA;EACC,OAAO;EACP,SAAS;CACV,CACD;AACD;;;;;AAUA,eAAsB,YACrB,iBACA,SACyE;CACzE,MAAM,QAAQ,SAAS,SAAS;CAChC,MAAM,cAA2B,EAAE,MAAM,MAAM;CAC/C,MAAM,0BAAU,IAAI,IAA2B;CAE/C,MAAM,mBAAmB,gBAAgB,OAAO,aAAa;CAC7D,MAAM,aAAa,gBAAgB,QAAQ,MAAM,CAAC,cAAc,CAAC,CAAC;CAElE,IAAI,iBAAiB,WAAW,KAAK,WAAW,WAAW,GAC1D,OAAO;EAAE;EAAS,aAAa;CAAM;CAGtC,MAAM,cAAc,QAAQ,OAAO,QAAQ;CAC3C,aAAa,MAAM,mBAAmB;CAItC,IAAI,CAAC,MAFqB,kBAAkB,WAAW,GAErC;EACjB,aAAa,KAAK,uBAAuB;EACzC,KAAK,MAAM,UAAU,iBACpB,QAAQ,IAAI,QAAQ,QAAQ;EAE7B,OAAO;GAAE;GAAS,aAAa;EAAM;CACtC;CAEA,aAAa,KAAK,6BAA6B;CAE/C,MAAM,SAAS,MAAM,wBAAwB,WAAW;CAExD,IAAI,CAAC,QAAQ;EACZ,IAAI,CAAC,OAAO;GACX,IAAI,MAAM,gDAAgD;GAC1D,IAAI,KACH,iFACD;EACD;EACA,KAAK,MAAM,UAAU,iBACpB,QAAQ,IAAI,QAAQ,QAAQ;EAE7B,OAAO;GAAE;GAAS,aAAa;EAAK;CACrC;CAEA,KAAK,MAAM,UAAU,kBAAkB;EAGtC,IAAI,CAAC,MAFwB,iBAAiB,MAAM,GAE/B;GACpB,QAAQ,IAAI,QAAQ,QAAQ;GAC5B;EACD;EAGA,IAAI,CAAC,MADiB,0BAA0B,MAAM,GACxC;GACb,QAAQ,IAAI,QAAQ,QAAQ;GAC5B;EACD;EAIA,IAAI,MAFwB,mBAAmB,QAAQ,MAAM,GAG5D,QAAQ,IAAI,QAAQ,SAAS;OAE7B,QAAQ,IAAI,QAAQ,QAAQ;CAE9B;CAEA,IAAI,WAAW,SAAS,GAAG;EAC1B,MAAM,aAAa,QAAQ,OAAO,QAAQ;EAC1C,YAAY,MAAM,+CAA+C;EAEjE,IAAI,kBAAkB;EACtB,KAAK,MAAM,UAAU,YACpB,IAAI;GACH,MAAM,0BAA0B,QAAQ,MAAM;GAC9C,QAAQ,IAAI,QAAQ,SAAS;GAC7B;EACD,SAAS,KAAK;GACb,QAAQ,IAAI,QAAQ,QAAQ;GAC5B,IACC,CAAC,SACD,OACA,OAAO,QAAQ,YACf,YAAY,OACZ,IAAI,QAEJ,IAAI,MACH,OAAO,IAAI,MAAM,CAAC,CAAC,KAAK,KACvB,0CACF;EAEF;EAGD,YAAY,KACX,kBAAkB,IACf,6CACA,qCACJ;CACD;CAEA,OAAO;EAAE;EAAS,aAAa;CAAK;AACrC"}
@@ -0,0 +1,32 @@
1
+ //#region src/lib/neonctl.d.ts
2
+ /**
3
+ * Detects which package manager was used to invoke the current process.
4
+ * Reads the `npm_config_user_agent` env var set by npm/pnpm/yarn/bun when
5
+ * they spawn child processes (including via `npx`, `pnpx`, `bunx`, etc.).
6
+ *
7
+ * Falls back to "npm" if detection fails.
8
+ */
9
+ declare function detectPackageManager(): "npm" | "pnpm" | "yarn" | "bun";
10
+ interface NeonctlStatus {
11
+ installed: boolean;
12
+ currentVersion: string | null;
13
+ latestVersion: string | null;
14
+ needsUpdate: boolean;
15
+ }
16
+ /**
17
+ * Checks whether the neonctl CLI is globally installed and whether it's up to date.
18
+ */
19
+ declare function checkNeonctl(): Promise<NeonctlStatus>;
20
+ interface EnsureNeonctlResult {
21
+ status: "already_current" | "installed" | "updated" | "failed";
22
+ version?: string;
23
+ error?: string;
24
+ }
25
+ /**
26
+ * Ensures neonctl is globally installed and up to date.
27
+ * Uses the same package manager that invoked the init command.
28
+ */
29
+ declare function ensureNeonctl(): Promise<EnsureNeonctlResult>;
30
+ //#endregion
31
+ export { EnsureNeonctlResult, checkNeonctl, detectPackageManager, ensureNeonctl };
32
+ //# sourceMappingURL=neonctl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neonctl.d.ts","names":[],"sources":["../../src/lib/neonctl.ts"],"mappings":";;AASA;AAQC;AAkDD;;;;AAA6C,iBA1D7B,oBAAA,CAAA,CA0D6B,EAAA,KAAA,GAAA,MAAA,GAAA,MAAA,GAAA,KAAA;AAiD7C,UA9EU,aAAA,CA8EO;EAUK,SAAA,EAAA,OAAa;EAAA,cAAA,EAAA,MAAA,GAAA,IAAA;eAAY,EAAA,MAAA,GAAA,IAAA;aAAR,EAAA,OAAA;AAAO;;;;iBA3DxB,YAAA,CAAA,GAAgB,QAAQ;UAiD7B,mBAAA;;;;;;;;;iBAUK,aAAA,CAAA,GAAiB,QAAQ"}
@@ -0,0 +1,149 @@
1
+ import { execa } from "execa";
2
+ //#region src/lib/neonctl.ts
3
+ /**
4
+ * Detects which package manager was used to invoke the current process.
5
+ * Reads the `npm_config_user_agent` env var set by npm/pnpm/yarn/bun when
6
+ * they spawn child processes (including via `npx`, `pnpx`, `bunx`, etc.).
7
+ *
8
+ * Falls back to "npm" if detection fails.
9
+ */
10
+ function detectPackageManager() {
11
+ const ua = process.env.npm_config_user_agent;
12
+ if (ua) {
13
+ if (ua.startsWith("pnpm/")) return "pnpm";
14
+ if (ua.startsWith("yarn/")) return "yarn";
15
+ if (ua.startsWith("bun/")) return "bun";
16
+ }
17
+ return "npm";
18
+ }
19
+ /**
20
+ * Returns the global install command for a given package manager.
21
+ */
22
+ function globalInstallArgs(pm, pkg) {
23
+ switch (pm) {
24
+ case "pnpm": return {
25
+ command: "pnpm",
26
+ args: [
27
+ "add",
28
+ "-g",
29
+ pkg
30
+ ]
31
+ };
32
+ case "yarn": return {
33
+ command: "yarn",
34
+ args: [
35
+ "global",
36
+ "add",
37
+ pkg
38
+ ]
39
+ };
40
+ case "bun": return {
41
+ command: "bun",
42
+ args: [
43
+ "add",
44
+ "-g",
45
+ pkg
46
+ ]
47
+ };
48
+ default: return {
49
+ command: "npm",
50
+ args: [
51
+ "install",
52
+ "-g",
53
+ pkg
54
+ ]
55
+ };
56
+ }
57
+ }
58
+ /**
59
+ * Gets the currently available neonctl version.
60
+ * Tries the global binary first, then falls back to npx.
61
+ */
62
+ async function getNeonctlVersion() {
63
+ try {
64
+ const match = (await execa("neonctl", ["--version"], {
65
+ stdio: "pipe",
66
+ timeout: 5e3
67
+ })).stdout.trim().match(/(\d+\.\d+\.\d+)/);
68
+ if (match) return match[1];
69
+ } catch {}
70
+ return null;
71
+ }
72
+ /**
73
+ * Checks whether the neonctl CLI is globally installed and whether it's up to date.
74
+ */
75
+ async function checkNeonctl() {
76
+ const currentVersion = await getNeonctlVersion();
77
+ if (!currentVersion) return {
78
+ installed: false,
79
+ currentVersion: null,
80
+ latestVersion: null,
81
+ needsUpdate: true
82
+ };
83
+ let latestVersion = null;
84
+ try {
85
+ latestVersion = (await execa("npm", [
86
+ "view",
87
+ "neonctl",
88
+ "version"
89
+ ], {
90
+ stdio: "pipe",
91
+ timeout: 1e4
92
+ })).stdout.trim();
93
+ } catch {
94
+ return {
95
+ installed: true,
96
+ currentVersion,
97
+ latestVersion: null,
98
+ needsUpdate: false
99
+ };
100
+ }
101
+ const needsUpdate = currentVersion !== null && latestVersion !== null && currentVersion !== latestVersion && isOlderVersion(currentVersion, latestVersion);
102
+ return {
103
+ installed: true,
104
+ currentVersion,
105
+ latestVersion,
106
+ needsUpdate
107
+ };
108
+ }
109
+ function isOlderVersion(current, latest) {
110
+ const c = current.split(".").map(Number);
111
+ const l = latest.split(".").map(Number);
112
+ for (let i = 0; i < 3; i++) {
113
+ if ((c[i] ?? 0) < (l[i] ?? 0)) return true;
114
+ if ((c[i] ?? 0) > (l[i] ?? 0)) return false;
115
+ }
116
+ return false;
117
+ }
118
+ /**
119
+ * Ensures neonctl is globally installed and up to date.
120
+ * Uses the same package manager that invoked the init command.
121
+ */
122
+ async function ensureNeonctl() {
123
+ const check = await checkNeonctl();
124
+ if (check.installed && !check.needsUpdate) return {
125
+ status: "already_current",
126
+ version: check.currentVersion ?? void 0
127
+ };
128
+ const { command, args } = globalInstallArgs(detectPackageManager(), "neonctl");
129
+ try {
130
+ await execa(command, args, {
131
+ stdio: "pipe",
132
+ timeout: 6e4
133
+ });
134
+ const version = await getNeonctlVersion();
135
+ return {
136
+ status: check.installed ? "updated" : "installed",
137
+ version: version ?? void 0
138
+ };
139
+ } catch (err) {
140
+ return {
141
+ status: "failed",
142
+ error: err instanceof Error ? err.message : "Unknown error"
143
+ };
144
+ }
145
+ }
146
+ //#endregion
147
+ export { checkNeonctl, detectPackageManager, ensureNeonctl };
148
+
149
+ //# sourceMappingURL=neonctl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neonctl.js","names":[],"sources":["../../src/lib/neonctl.ts"],"sourcesContent":["import { execa } from \"execa\";\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":";;;;;;;;;AASA,SAAgB,uBAAwD;CACvE,MAAM,KAAK,QAAQ,IAAI;CACvB,IAAI,IAAI;EACP,IAAI,GAAG,WAAW,OAAO,GAAG,OAAO;EACnC,IAAI,GAAG,WAAW,OAAO,GAAG,OAAO;EACnC,IAAI,GAAG,WAAW,MAAM,GAAG,OAAO;CACnC;CACA,OAAO;AACR;;;;AAKA,SAAS,kBACR,IACA,KACsC;CACtC,QAAQ,IAAR;EACC,KAAK,QACJ,OAAO;GAAE,SAAS;GAAQ,MAAM;IAAC;IAAO;IAAM;GAAG;EAAE;EACpD,KAAK,QACJ,OAAO;GAAE,SAAS;GAAQ,MAAM;IAAC;IAAU;IAAO;GAAG;EAAE;EACxD,KAAK,OACJ,OAAO;GAAE,SAAS;GAAO,MAAM;IAAC;IAAO;IAAM;GAAG;EAAE;EACnD,SACC,OAAO;GAAE,SAAS;GAAO,MAAM;IAAC;IAAW;IAAM;GAAG;EAAE;CACxD;AACD;;;;;AAaA,eAAe,oBAA4C;CAE1D,IAAI;EAKH,MAAM,SAAQ,MAJO,MAAM,WAAW,CAAC,WAAW,GAAG;GACpD,OAAO;GACP,SAAS;EACV,CAAC,EAAA,CACoB,OAAO,KAAK,CAAC,CAAC,MAAM,iBAAiB;EAC1D,IAAI,OAAO,OAAO,MAAM;CACzB,QAAQ,CAER;CACA,OAAO;AACR;;;;AAKA,eAAsB,eAAuC;CAC5D,MAAM,iBAAiB,MAAM,kBAAkB;CAE/C,IAAI,CAAC,gBACJ,OAAO;EACN,WAAW;EACX,gBAAgB;EAChB,eAAe;EACf,aAAa;CACd;CAID,IAAI,gBAA+B;CACnC,IAAI;EAKH,iBAAgB,MAJK,MAAM,OAAO;GAAC;GAAQ;GAAW;EAAS,GAAG;GACjE,OAAO;GACP,SAAS;EACV,CAAC,EAAA,CACsB,OAAO,KAAK;CACpC,QAAQ;EAEP,OAAO;GACN,WAAW;GACX;GACA,eAAe;GACf,aAAa;EACd;CACD;CAEA,MAAM,cACL,mBAAmB,QACnB,kBAAkB,QAClB,mBAAmB,iBACnB,eAAe,gBAAgB,aAAa;CAE7C,OAAO;EAAE,WAAW;EAAM;EAAgB;EAAe;CAAY;AACtE;AAEA,SAAS,eAAe,SAAiB,QAAyB;CACjE,MAAM,IAAI,QAAQ,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CACvC,MAAM,IAAI,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CACtC,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC3B,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM,IAAI,OAAO;EACtC,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM,IAAI,OAAO;CACvC;CACA,OAAO;AACR;;;;;AAYA,eAAsB,gBAA8C;CACnE,MAAM,QAAQ,MAAM,aAAa;CAEjC,IAAI,MAAM,aAAa,CAAC,MAAM,aAC7B,OAAO;EACN,QAAQ;EACR,SAAS,MAAM,kBAAkB,KAAA;CAClC;CAID,MAAM,EAAE,SAAS,SAAS,kBADf,qBACkC,GAAG,SAAS;CAEzD,IAAI;EACH,MAAM,MAAM,SAAS,MAAM;GAAE,OAAO;GAAQ,SAAS;EAAM,CAAC;EAG5D,MAAM,UAAU,MAAM,kBAAkB;EACxC,OAAO;GACN,QAAQ,MAAM,YAAY,YAAY;GACtC,SAAS,WAAW,KAAA;EACrB;CACD,SAAS,KAAK;EACb,OAAO;GACN,QAAQ;GACR,OAAO,eAAe,QAAQ,IAAI,UAAU;EAC7C;CACD;AACD"}
@@ -0,0 +1,12 @@
1
+ import { PhaseResponse } from "../types.js";
2
+
3
+ //#region src/lib/phases/auth.d.ts
4
+ interface AuthPhaseOptions {
5
+ agent?: string;
6
+ method?: "existing" | "new";
7
+ verify?: boolean;
8
+ }
9
+ declare function handleAuthPhase(options: AuthPhaseOptions): Promise<PhaseResponse>;
10
+ //#endregion
11
+ export { AuthPhaseOptions, handleAuthPhase };
12
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","names":[],"sources":["../../../src/lib/phases/auth.ts"],"mappings":";;;UASiB,gBAAA;;EAAA,MAAA,CAAA,EAAA,UAAgB,GAAA,KAAA;EAMX,MAAA,CAAA,EAAA,OAAA;;AACZ,iBADY,eAAA,CACZ,OAAA,EAAA,gBAAA,CAAA,EACP,OADO,CACC,aADD,CAAA"}