neon-init 0.13.1 → 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 (117) hide show
  1. package/dist/cli.js +368 -33
  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 +220 -41
  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 +23 -0
  12. package/dist/lib/agents.d.ts.map +1 -0
  13. package/dist/lib/agents.js +148 -0
  14. package/dist/lib/agents.js.map +1 -0
  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 +20 -13
  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 +1 -2
  33. package/dist/lib/editors.js.map +1 -1
  34. package/dist/lib/extension.d.ts +11 -3
  35. package/dist/lib/extension.d.ts.map +1 -1
  36. package/dist/lib/extension.js +29 -9
  37. package/dist/lib/extension.js.map +1 -1
  38. package/dist/lib/inspect.d.ts +28 -0
  39. package/dist/lib/inspect.d.ts.map +1 -0
  40. package/dist/lib/inspect.js +190 -0
  41. package/dist/lib/inspect.js.map +1 -0
  42. package/dist/lib/install.d.ts +10 -4
  43. package/dist/lib/install.d.ts.map +1 -1
  44. package/dist/lib/install.js +74 -71
  45. package/dist/lib/install.js.map +1 -1
  46. package/dist/lib/neonctl.d.ts +32 -0
  47. package/dist/lib/neonctl.d.ts.map +1 -0
  48. package/dist/lib/neonctl.js +149 -0
  49. package/dist/lib/neonctl.js.map +1 -0
  50. package/dist/lib/phases/auth.d.ts +12 -0
  51. package/dist/lib/phases/auth.d.ts.map +1 -0
  52. package/dist/lib/phases/auth.js +188 -0
  53. package/dist/lib/phases/auth.js.map +1 -0
  54. package/dist/lib/phases/cleanup.d.ts +12 -0
  55. package/dist/lib/phases/cleanup.d.ts.map +1 -0
  56. package/dist/lib/phases/cleanup.js +29 -0
  57. package/dist/lib/phases/cleanup.js.map +1 -0
  58. package/dist/lib/phases/db.d.ts +17 -0
  59. package/dist/lib/phases/db.d.ts.map +1 -0
  60. package/dist/lib/phases/db.js +258 -0
  61. package/dist/lib/phases/db.js.map +1 -0
  62. package/dist/lib/phases/getting-started.d.ts +26 -0
  63. package/dist/lib/phases/getting-started.d.ts.map +1 -0
  64. package/dist/lib/phases/getting-started.js +195 -0
  65. package/dist/lib/phases/getting-started.js.map +1 -0
  66. package/dist/lib/phases/mcp.d.ts +15 -0
  67. package/dist/lib/phases/mcp.d.ts.map +1 -0
  68. package/dist/lib/phases/mcp.js +179 -0
  69. package/dist/lib/phases/mcp.js.map +1 -0
  70. package/dist/lib/phases/migrations.d.ts +14 -0
  71. package/dist/lib/phases/migrations.d.ts.map +1 -0
  72. package/dist/lib/phases/migrations.js +239 -0
  73. package/dist/lib/phases/migrations.js.map +1 -0
  74. package/dist/lib/phases/neon-auth.d.ts +13 -0
  75. package/dist/lib/phases/neon-auth.d.ts.map +1 -0
  76. package/dist/lib/phases/neon-auth.js +117 -0
  77. package/dist/lib/phases/neon-auth.js.map +1 -0
  78. package/dist/lib/phases/setup.d.ts +41 -0
  79. package/dist/lib/phases/setup.d.ts.map +1 -0
  80. package/dist/lib/phases/setup.js +689 -0
  81. package/dist/lib/phases/setup.js.map +1 -0
  82. package/dist/lib/phases/skills.d.ts +14 -0
  83. package/dist/lib/phases/skills.d.ts.map +1 -0
  84. package/dist/lib/phases/skills.js +80 -0
  85. package/dist/lib/phases/skills.js.map +1 -0
  86. package/dist/lib/phases/status.d.ts +10 -0
  87. package/dist/lib/phases/status.d.ts.map +1 -0
  88. package/dist/lib/phases/status.js +65 -0
  89. package/dist/lib/phases/status.js.map +1 -0
  90. package/dist/lib/resolve-context.d.ts +19 -0
  91. package/dist/lib/resolve-context.d.ts.map +1 -0
  92. package/dist/lib/resolve-context.js +112 -0
  93. package/dist/lib/resolve-context.js.map +1 -0
  94. package/dist/lib/route-command.d.ts +8 -0
  95. package/dist/lib/route-command.d.ts.map +1 -0
  96. package/dist/lib/route-command.js +195 -0
  97. package/dist/lib/route-command.js.map +1 -0
  98. package/dist/lib/skills.d.ts +21 -4
  99. package/dist/lib/skills.d.ts.map +1 -1
  100. package/dist/lib/skills.js +129 -22
  101. package/dist/lib/skills.js.map +1 -1
  102. package/dist/lib/types.d.ts +146 -13
  103. package/dist/lib/types.d.ts.map +1 -1
  104. package/dist/lib/types.js +1 -1
  105. package/dist/lib/vsix.d.ts +15 -0
  106. package/dist/lib/vsix.d.ts.map +1 -0
  107. package/dist/lib/vsix.js +91 -0
  108. package/dist/lib/vsix.js.map +1 -0
  109. package/dist/v2.d.ts +31 -0
  110. package/dist/v2.d.ts.map +1 -0
  111. package/dist/v2.js +147 -0
  112. package/dist/v2.js.map +1 -0
  113. package/package.json +9 -4
  114. package/dist/lib/mcp-config.d.ts +0 -24
  115. package/dist/lib/mcp-config.d.ts.map +0 -1
  116. package/dist/lib/mcp-config.js +0 -51
  117. package/dist/lib/mcp-config.js.map +0 -1
@@ -1,8 +1,8 @@
1
+ import { NEON_EXTENSION_ID, downloadVsix } from "./vsix.js";
1
2
  import { existsSync } from "node:fs";
2
3
  import { execa } from "execa";
3
-
4
+ import { unlink } from "node:fs/promises";
4
5
  //#region src/lib/extension.ts
5
- const NEON_EXTENSION_ID = "databricks.neon-local-connect";
6
6
  /**
7
7
  * Uses macOS mdfind to locate an app by bundle identifier
8
8
  */
@@ -109,7 +109,7 @@ function getEditorUriScheme(editor) {
109
109
  /**
110
110
  * Checks if the extension is installed by querying the editor's extension list
111
111
  */
112
- async function isExtensionInList(editor) {
112
+ async function isExtensionInstalled(editor) {
113
113
  const command = await findEditorCommand(editor);
114
114
  if (!command) return false;
115
115
  try {
@@ -124,7 +124,7 @@ async function isExtensionInList(editor) {
124
124
  */
125
125
  async function waitForExtensionInstalled(editor, maxAttempts = 10, delayMs = 1e3) {
126
126
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
127
- if (await isExtensionInList(editor)) {
127
+ if (await isExtensionInstalled(editor)) {
128
128
  await new Promise((resolve) => setTimeout(resolve, 1e3));
129
129
  return true;
130
130
  }
@@ -133,17 +133,37 @@ async function waitForExtensionInstalled(editor, maxAttempts = 10, delayMs = 1e3
133
133
  return false;
134
134
  }
135
135
  /**
136
- * Installs the Neon Local Connect extension for VS Code or Cursor
137
- * Returns success only if installation succeeds, fails silently otherwise
136
+ * Installs the Neon Local Connect extension for VS Code or Cursor.
137
+ *
138
+ * Strategy:
139
+ * 1. Try `--install-extension <id>` directly (uses the editor's configured marketplace)
140
+ * 2. If that fails, download .vsix (from proxy or Open VSX) and install locally
141
+ * 3. Set NEON_VSX_GALLERY_URL to use a corporate proxy for the download
138
142
  */
139
143
  async function installExtension(editor) {
140
144
  const command = await findEditorCommand(editor);
141
145
  if (!command) return false;
142
146
  try {
143
- await execa(command, ["--install-extension", NEON_EXTENSION_ID]);
147
+ await execa(command, ["--install-extension", NEON_EXTENSION_ID], {
148
+ stdio: "pipe",
149
+ timeout: 6e4
150
+ });
151
+ return true;
152
+ } catch {}
153
+ const vsixPath = await downloadVsix();
154
+ if (!vsixPath) return false;
155
+ try {
156
+ await execa(command, ["--install-extension", vsixPath], {
157
+ stdio: "pipe",
158
+ timeout: 6e4
159
+ });
144
160
  return true;
145
161
  } catch {
146
162
  return false;
163
+ } finally {
164
+ try {
165
+ await unlink(vsixPath);
166
+ } catch {}
147
167
  }
148
168
  }
149
169
  /**
@@ -176,7 +196,7 @@ async function configureExtension(editor, apiKey) {
176
196
  function usesExtension(editor) {
177
197
  return editor === "VS Code" || editor === "Cursor";
178
198
  }
179
-
180
199
  //#endregion
181
- export { configureExtension, findEditorCommand, installExtension, usesExtension, waitForExtensionInstalled };
200
+ export { configureExtension, findEditorCommand, installExtension, isExtensionInstalled, usesExtension, waitForExtensionInstalled };
201
+
182
202
  //# sourceMappingURL=extension.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.js","names":[],"sources":["../../src/lib/extension.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { execa } from \"execa\";\nimport type { Editor } from \"./types.js\";\n\nconst NEON_EXTENSION_ID = \"databricks.neon-local-connect\";\n\n/**\n * Uses macOS mdfind to locate an app by bundle identifier\n */\nasync function findAppWithMdfind(bundleId: string): Promise<string | null> {\n\ttry {\n\t\tconst result = await execa(\n\t\t\t\"mdfind\",\n\t\t\t[`kMDItemCFBundleIdentifier == '${bundleId}'`],\n\t\t\t{ timeout: 5000 },\n\t\t);\n\t\tconst paths = result.stdout.trim().split(\"\\n\").filter(Boolean);\n\t\treturn paths[0] || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Known installation paths for VS Code CLI\n */\nfunction getVSCodePaths(): string[] {\n\tconst platform = process.platform;\n\tconst home = process.env.HOME || \"\";\n\n\tif (platform === \"darwin\") {\n\t\treturn [\n\t\t\t\"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code\",\n\t\t\t\"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders\",\n\t\t\t`${home}/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code`,\n\t\t\t`${home}/Downloads/Visual Studio Code.app/Contents/Resources/app/bin/code`,\n\t\t];\n\t}\n\n\tif (platform === \"linux\") {\n\t\treturn [\n\t\t\t\"/usr/share/code/bin/code\",\n\t\t\t\"/usr/bin/code\",\n\t\t\t\"/snap/bin/code\",\n\t\t\t\"/usr/share/code-insiders/bin/code-insiders\",\n\t\t];\n\t}\n\n\tif (platform === \"win32\") {\n\t\tconst localAppData = process.env.LOCALAPPDATA || \"\";\n\t\tconst programFiles = process.env.PROGRAMFILES || \"C:\\\\Program Files\";\n\t\treturn [\n\t\t\t`${localAppData}\\\\Programs\\\\Microsoft VS Code\\\\bin\\\\code.cmd`,\n\t\t\t`${programFiles}\\\\Microsoft VS Code\\\\bin\\\\code.cmd`,\n\t\t\t`${localAppData}\\\\Programs\\\\Microsoft VS Code Insiders\\\\bin\\\\code-insiders.cmd`,\n\t\t];\n\t}\n\n\treturn [];\n}\n\n/**\n * Known installation paths for Cursor CLI\n */\nfunction getCursorPaths(): string[] {\n\tconst platform = process.platform;\n\tconst home = process.env.HOME || \"\";\n\n\tif (platform === \"darwin\") {\n\t\treturn [\n\t\t\t\"/Applications/Cursor.app/Contents/Resources/app/bin/cursor\",\n\t\t\t`${home}/Applications/Cursor.app/Contents/Resources/app/bin/cursor`,\n\t\t\t`${home}/Downloads/Cursor.app/Contents/Resources/app/bin/cursor`,\n\t\t];\n\t}\n\n\tif (platform === \"linux\") {\n\t\treturn [\n\t\t\t\"/usr/share/cursor/bin/cursor\",\n\t\t\t\"/usr/bin/cursor\",\n\t\t\t`${home}/.local/bin/cursor`,\n\t\t\t\"/opt/cursor/bin/cursor\",\n\t\t];\n\t}\n\n\tif (platform === \"win32\") {\n\t\tconst localAppData = process.env.LOCALAPPDATA || \"\";\n\t\tconst programFiles = process.env.PROGRAMFILES || \"C:\\\\Program Files\";\n\t\treturn [\n\t\t\t`${localAppData}\\\\Programs\\\\Cursor\\\\resources\\\\app\\\\bin\\\\cursor.cmd`,\n\t\t\t`${localAppData}\\\\cursor\\\\Cursor.exe`,\n\t\t\t`${programFiles}\\\\Cursor\\\\resources\\\\app\\\\bin\\\\cursor.cmd`,\n\t\t];\n\t}\n\n\treturn [];\n}\n\n/**\n * Finds the CLI command for an editor by checking known installation paths\n * On macOS, also uses mdfind to locate the app if standard paths fail\n * Falls back to the simple command name if no full path is found (in case it's in PATH)\n */\nexport async function findEditorCommand(\n\teditor: Editor,\n): Promise<string | null> {\n\tlet paths: string[];\n\tlet fallbackCommand: string;\n\tlet bundleId: string | null = null;\n\n\tif (editor === \"VS Code\") {\n\t\tpaths = getVSCodePaths();\n\t\tfallbackCommand = \"code\";\n\t\tbundleId = \"com.microsoft.VSCode\";\n\t} else if (editor === \"Cursor\") {\n\t\tpaths = getCursorPaths();\n\t\tfallbackCommand = \"cursor\";\n\t\tbundleId = \"com.todesktop.230313mzl4w4u92\";\n\t} else {\n\t\treturn null;\n\t}\n\n\tfor (const path of paths) {\n\t\tif (existsSync(path)) {\n\t\t\treturn path;\n\t\t}\n\t}\n\n\t// On macOS, try mdfind to locate the app dynamically\n\tif (process.platform === \"darwin\" && bundleId) {\n\t\tconst appPath = await findAppWithMdfind(bundleId);\n\t\tif (appPath) {\n\t\t\tconst cliPath = `${appPath}/Contents/Resources/app/bin/${fallbackCommand}`;\n\t\t\tif (existsSync(cliPath)) {\n\t\t\t\treturn cliPath;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn fallbackCommand;\n}\n\n/**\n * Gets the URI scheme for an editor\n */\nfunction getEditorUriScheme(editor: Editor): string | null {\n\tif (editor === \"VS Code\") {\n\t\treturn \"vscode\";\n\t}\n\tif (editor === \"Cursor\") {\n\t\treturn \"cursor\";\n\t}\n\treturn null;\n}\n\n/**\n * Checks if the extension is installed by querying the editor's extension list\n */\nasync function isExtensionInList(editor: Editor): Promise<boolean> {\n\tconst command = await findEditorCommand(editor);\n\tif (!command) {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\tconst result = await execa(command, [\"--list-extensions\"], {\n\t\t\ttimeout: 5000,\n\t\t});\n\t\treturn result.stdout.includes(NEON_EXTENSION_ID);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Waits for the extension to appear in the installed extensions list\n * This ensures the extension is fully installed and activated before we try to configure it\n */\nexport async function waitForExtensionInstalled(\n\teditor: Editor,\n\tmaxAttempts = 10,\n\tdelayMs = 1000,\n): Promise<boolean> {\n\tfor (let attempt = 0; attempt < maxAttempts; attempt++) {\n\t\tconst isInstalled = await isExtensionInList(editor);\n\n\t\tif (isInstalled) {\n\t\t\t// Give the extension a moment to fully activate and register URI handlers\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 1000));\n\t\t\treturn true;\n\t\t}\n\n\t\t// Wait before checking again (unless this is the last attempt)\n\t\tif (attempt < maxAttempts - 1) {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, delayMs));\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Installs the Neon Local Connect extension for VS Code or Cursor\n * Returns success only if installation succeeds, fails silently otherwise\n */\nexport async function installExtension(editor: Editor): Promise<boolean> {\n\tconst command = await findEditorCommand(editor);\n\tif (!command) {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\tawait execa(command, [\"--install-extension\", NEON_EXTENSION_ID]);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Configures the Neon Local Connect extension with the API key\n * Uses the extension's URI handler to trigger the import-api-key command\n */\nexport async function configureExtension(\n\teditor: Editor,\n\tapiKey: string,\n): Promise<boolean> {\n\tconst scheme = getEditorUriScheme(editor);\n\tif (!scheme) {\n\t\treturn false;\n\t}\n\n\t// Build the URI to trigger the extension's import-api-key handler\n\t// Format: vscode://databricks.neon-local-connect/import-api-key?token=xxx\n\tconst encodedApiKey = encodeURIComponent(apiKey);\n\tconst uri = `${scheme}://${NEON_EXTENSION_ID}/import-api-key?token=${encodedApiKey}`;\n\n\ttry {\n\t\tconst platform = process.platform;\n\n\t\tif (platform === \"darwin\") {\n\t\t\t// macOS: use 'open' command\n\t\t\tawait execa(\"open\", [uri], { timeout: 10000 });\n\t\t} else if (platform === \"linux\") {\n\t\t\t// Linux: use 'xdg-open' command\n\t\t\tawait execa(\"xdg-open\", [uri], { timeout: 10000 });\n\t\t} else if (platform === \"win32\") {\n\t\t\t// Windows: use 'start' command\n\t\t\tawait execa(\"cmd\", [\"/c\", \"start\", \"\", uri], { timeout: 10000 });\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Returns the editor types that should use extension installation (vs MCP)\n */\nexport function usesExtension(editor: Editor): boolean {\n\treturn editor === \"VS Code\" || editor === \"Cursor\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.js","names":[],"sources":["../../src/lib/extension.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { unlink } from \"node:fs/promises\";\nimport { execa } from \"execa\";\nimport type { Editor } from \"./types.js\";\nimport { downloadVsix, NEON_EXTENSION_ID } from \"./vsix.js\";\n\n/**\n * Uses macOS mdfind to locate an app by bundle identifier\n */\nasync function findAppWithMdfind(bundleId: string): Promise<string | null> {\n\ttry {\n\t\tconst result = await execa(\n\t\t\t\"mdfind\",\n\t\t\t[`kMDItemCFBundleIdentifier == '${bundleId}'`],\n\t\t\t{ timeout: 5000 },\n\t\t);\n\t\tconst paths = result.stdout.trim().split(\"\\n\").filter(Boolean);\n\t\treturn paths[0] || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Known installation paths for VS Code CLI\n */\nfunction getVSCodePaths(): string[] {\n\tconst platform = process.platform;\n\tconst home = process.env.HOME || \"\";\n\n\tif (platform === \"darwin\") {\n\t\treturn [\n\t\t\t\"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code\",\n\t\t\t\"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders\",\n\t\t\t`${home}/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code`,\n\t\t\t`${home}/Downloads/Visual Studio Code.app/Contents/Resources/app/bin/code`,\n\t\t];\n\t}\n\n\tif (platform === \"linux\") {\n\t\treturn [\n\t\t\t\"/usr/share/code/bin/code\",\n\t\t\t\"/usr/bin/code\",\n\t\t\t\"/snap/bin/code\",\n\t\t\t\"/usr/share/code-insiders/bin/code-insiders\",\n\t\t];\n\t}\n\n\tif (platform === \"win32\") {\n\t\tconst localAppData = process.env.LOCALAPPDATA || \"\";\n\t\tconst programFiles = process.env.PROGRAMFILES || \"C:\\\\Program Files\";\n\t\treturn [\n\t\t\t`${localAppData}\\\\Programs\\\\Microsoft VS Code\\\\bin\\\\code.cmd`,\n\t\t\t`${programFiles}\\\\Microsoft VS Code\\\\bin\\\\code.cmd`,\n\t\t\t`${localAppData}\\\\Programs\\\\Microsoft VS Code Insiders\\\\bin\\\\code-insiders.cmd`,\n\t\t];\n\t}\n\n\treturn [];\n}\n\n/**\n * Known installation paths for Cursor CLI\n */\nfunction getCursorPaths(): string[] {\n\tconst platform = process.platform;\n\tconst home = process.env.HOME || \"\";\n\n\tif (platform === \"darwin\") {\n\t\treturn [\n\t\t\t\"/Applications/Cursor.app/Contents/Resources/app/bin/cursor\",\n\t\t\t`${home}/Applications/Cursor.app/Contents/Resources/app/bin/cursor`,\n\t\t\t`${home}/Downloads/Cursor.app/Contents/Resources/app/bin/cursor`,\n\t\t];\n\t}\n\n\tif (platform === \"linux\") {\n\t\treturn [\n\t\t\t\"/usr/share/cursor/bin/cursor\",\n\t\t\t\"/usr/bin/cursor\",\n\t\t\t`${home}/.local/bin/cursor`,\n\t\t\t\"/opt/cursor/bin/cursor\",\n\t\t];\n\t}\n\n\tif (platform === \"win32\") {\n\t\tconst localAppData = process.env.LOCALAPPDATA || \"\";\n\t\tconst programFiles = process.env.PROGRAMFILES || \"C:\\\\Program Files\";\n\t\treturn [\n\t\t\t`${localAppData}\\\\Programs\\\\Cursor\\\\resources\\\\app\\\\bin\\\\cursor.cmd`,\n\t\t\t`${localAppData}\\\\cursor\\\\Cursor.exe`,\n\t\t\t`${programFiles}\\\\Cursor\\\\resources\\\\app\\\\bin\\\\cursor.cmd`,\n\t\t];\n\t}\n\n\treturn [];\n}\n\n/**\n * Finds the CLI command for an editor by checking known installation paths\n * On macOS, also uses mdfind to locate the app if standard paths fail\n * Falls back to the simple command name if no full path is found (in case it's in PATH)\n */\nexport async function findEditorCommand(\n\teditor: Editor,\n): Promise<string | null> {\n\tlet paths: string[];\n\tlet fallbackCommand: string;\n\tlet bundleId: string | null = null;\n\n\tif (editor === \"VS Code\") {\n\t\tpaths = getVSCodePaths();\n\t\tfallbackCommand = \"code\";\n\t\tbundleId = \"com.microsoft.VSCode\";\n\t} else if (editor === \"Cursor\") {\n\t\tpaths = getCursorPaths();\n\t\tfallbackCommand = \"cursor\";\n\t\tbundleId = \"com.todesktop.230313mzl4w4u92\";\n\t} else {\n\t\treturn null;\n\t}\n\n\tfor (const path of paths) {\n\t\tif (existsSync(path)) {\n\t\t\treturn path;\n\t\t}\n\t}\n\n\t// On macOS, try mdfind to locate the app dynamically\n\tif (process.platform === \"darwin\" && bundleId) {\n\t\tconst appPath = await findAppWithMdfind(bundleId);\n\t\tif (appPath) {\n\t\t\tconst cliPath = `${appPath}/Contents/Resources/app/bin/${fallbackCommand}`;\n\t\t\tif (existsSync(cliPath)) {\n\t\t\t\treturn cliPath;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn fallbackCommand;\n}\n\n/**\n * Gets the URI scheme for an editor\n */\nfunction getEditorUriScheme(editor: Editor): string | null {\n\tif (editor === \"VS Code\") {\n\t\treturn \"vscode\";\n\t}\n\tif (editor === \"Cursor\") {\n\t\treturn \"cursor\";\n\t}\n\treturn null;\n}\n\n/**\n * Checks if the extension is installed by querying the editor's extension list\n */\nexport async function isExtensionInstalled(editor: Editor): Promise<boolean> {\n\tconst command = await findEditorCommand(editor);\n\tif (!command) {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\tconst result = await execa(command, [\"--list-extensions\"], {\n\t\t\ttimeout: 5000,\n\t\t});\n\t\treturn result.stdout.includes(NEON_EXTENSION_ID);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Waits for the extension to appear in the installed extensions list\n * This ensures the extension is fully installed and activated before we try to configure it\n */\nexport async function waitForExtensionInstalled(\n\teditor: Editor,\n\tmaxAttempts = 10,\n\tdelayMs = 1000,\n): Promise<boolean> {\n\tfor (let attempt = 0; attempt < maxAttempts; attempt++) {\n\t\tconst isInstalled = await isExtensionInstalled(editor);\n\n\t\tif (isInstalled) {\n\t\t\t// Give the extension a moment to fully activate and register URI handlers\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 1000));\n\t\t\treturn true;\n\t\t}\n\n\t\t// Wait before checking again (unless this is the last attempt)\n\t\tif (attempt < maxAttempts - 1) {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, delayMs));\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Installs the Neon Local Connect extension for VS Code or Cursor.\n *\n * Strategy:\n * 1. Try `--install-extension <id>` directly (uses the editor's configured marketplace)\n * 2. If that fails, download .vsix (from proxy or Open VSX) and install locally\n * 3. Set NEON_VSX_GALLERY_URL to use a corporate proxy for the download\n */\nexport async function installExtension(editor: Editor): Promise<boolean> {\n\tconst command = await findEditorCommand(editor);\n\tif (!command) {\n\t\treturn false;\n\t}\n\n\t// Try direct marketplace install first\n\ttry {\n\t\tawait execa(command, [\"--install-extension\", NEON_EXTENSION_ID], {\n\t\t\tstdio: \"pipe\",\n\t\t\ttimeout: 60000,\n\t\t});\n\t\treturn true;\n\t} catch {\n\t\t// Fall through to VSIX download\n\t}\n\n\t// Download .vsix and install locally\n\tconst vsixPath = await downloadVsix();\n\tif (!vsixPath) {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\tawait execa(command, [\"--install-extension\", vsixPath], {\n\t\t\tstdio: \"pipe\",\n\t\t\ttimeout: 60000,\n\t\t});\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t} finally {\n\t\ttry {\n\t\t\tawait unlink(vsixPath);\n\t\t} catch {}\n\t}\n}\n\n/**\n * Configures the Neon Local Connect extension with the API key\n * Uses the extension's URI handler to trigger the import-api-key command\n */\nexport async function configureExtension(\n\teditor: Editor,\n\tapiKey: string,\n): Promise<boolean> {\n\tconst scheme = getEditorUriScheme(editor);\n\tif (!scheme) {\n\t\treturn false;\n\t}\n\n\t// Build the URI to trigger the extension's import-api-key handler\n\t// Format: vscode://databricks.neon-local-connect/import-api-key?token=xxx\n\tconst encodedApiKey = encodeURIComponent(apiKey);\n\tconst uri = `${scheme}://${NEON_EXTENSION_ID}/import-api-key?token=${encodedApiKey}`;\n\n\ttry {\n\t\tconst platform = process.platform;\n\n\t\tif (platform === \"darwin\") {\n\t\t\t// macOS: use 'open' command\n\t\t\tawait execa(\"open\", [uri], { timeout: 10000 });\n\t\t} else if (platform === \"linux\") {\n\t\t\t// Linux: use 'xdg-open' command\n\t\t\tawait execa(\"xdg-open\", [uri], { timeout: 10000 });\n\t\t} else if (platform === \"win32\") {\n\t\t\t// Windows: use 'start' command\n\t\t\tawait execa(\"cmd\", [\"/c\", \"start\", \"\", uri], { timeout: 10000 });\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Returns the editor types that should use extension installation (vs MCP)\n */\nexport function usesExtension(editor: Editor): boolean {\n\treturn editor === \"VS Code\" || editor === \"Cursor\";\n}\n"],"mappings":"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"}
@@ -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(homeDir: string, workspaceDir: string, 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":";;;;;;AA4FA;AAAiC,iBAAX,WAAA,CAAW,OAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,eAAA,EAGf,MAHe,EAAA,CAAA,EAI9B,OAJ8B,CAItB,GAJsB,CAIlB,MAJkB,EAIV,aAJU,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,81 +1,71 @@
1
- import { configureExtension, installExtension, usesExtension, waitForExtensionInstalled } from "./extension.js";
1
+ import { getAddMcpAgentId } from "./agents.js";
2
2
  import { createApiKeyFromNeonctl, ensureNeonctlAuth } from "./auth.js";
3
- import { getMCPConfig, writeMCPConfig } from "./mcp-config.js";
4
- import { confirm, isCancel, log, spinner } from "@clack/prompts";
5
-
3
+ import { configureExtension, installExtension, usesExtension, waitForExtensionInstalled } from "./extension.js";
4
+ import { log, spinner } from "@clack/prompts";
5
+ import { execa } from "execa";
6
6
  //#region src/lib/install.ts
7
+ const NEON_MCP_SERVER_URL = "https://mcp.neon.tech/mcp";
7
8
  /**
8
- * Checks if an editor needs MCP configuration
9
- * Returns true if configuration is needed, false otherwise
9
+ * Installs Neon MCP Server for a single editor via the add-mcp CLI.
10
+ * Uses API key authentication via the Authorization header.
10
11
  */
11
- async function shouldConfigureMCP(homeDir, workspaceDir, editor) {
12
- const { config } = getMCPConfig(homeDir, workspaceDir, editor);
13
- const serverKey = editor === "VS Code" ? config.servers : config.mcpServers;
14
- if (Boolean(serverKey?.Neon)) {
15
- const response = await confirm({
16
- message: `Neon MCP Server is already configured for ${editor}. Would you like to reconfigure it? (Y/n)`,
17
- initialValue: true
18
- });
19
- if (isCancel(response)) return false;
20
- if (!response) {
21
- log.info(`Keeping existing MCP server configuration for ${editor}.`);
22
- return false;
23
- }
24
- }
25
- return true;
26
- }
27
- /**
28
- * Installs Neon's MCP Server for specific editors
29
- */
30
- async function installMCPServerForEditor(homeDir, workspaceDir, editor, apiKey) {
31
- const { config, configPath } = getMCPConfig(homeDir, workspaceDir, editor);
32
- const neonServerConfig = {
33
- type: "http",
34
- url: "https://mcp.neon.tech/mcp",
35
- headers: { Authorization: `Bearer ${apiKey}` }
36
- };
37
- if (!config.mcpServers) config.mcpServers = {};
38
- config.mcpServers.Neon = neonServerConfig;
39
- try {
40
- writeMCPConfig(configPath, config);
41
- return "success";
42
- } catch (error) {
43
- log.error(`Failed to write configuration for ${editor}: ${error instanceof Error ? error.message : "Unknown error"}`);
44
- return "failed";
45
- }
12
+ async function installMCPServerViaAddMcp(editor, apiKey) {
13
+ const agentId = getAddMcpAgentId(editor);
14
+ await execa("npx", [
15
+ "-y",
16
+ "add-mcp",
17
+ NEON_MCP_SERVER_URL,
18
+ "--header",
19
+ `Authorization: Bearer ${apiKey}`,
20
+ "-g",
21
+ "-n",
22
+ "Neon",
23
+ "-y",
24
+ "-a",
25
+ agentId
26
+ ], {
27
+ stdio: "pipe",
28
+ timeout: 6e4
29
+ });
46
30
  }
47
31
  /**
48
- * 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.
49
34
  */
50
- async function installNeon(homeDir, workspaceDir, selectedEditors) {
35
+ async function installNeon(selectedEditors, options) {
36
+ const quiet = options?.json === true;
37
+ const authOptions = { json: quiet };
51
38
  const results = /* @__PURE__ */ new Map();
52
39
  const extensionEditors = selectedEditors.filter(usesExtension);
53
40
  const mcpEditors = selectedEditors.filter((e) => !usesExtension(e));
54
- const mcpEditorsToConfigureMap = /* @__PURE__ */ new Map();
55
- for (const editor of mcpEditors) {
56
- const needsConfig = await shouldConfigureMCP(homeDir, workspaceDir, editor);
57
- mcpEditorsToConfigureMap.set(editor, needsConfig);
58
- if (!needsConfig) results.set(editor, "success");
59
- }
60
- const mcpToConfigure = mcpEditors.filter((editor) => mcpEditorsToConfigureMap.get(editor) === true);
61
- const extensionsToConfigure = extensionEditors;
62
- if (extensionsToConfigure.length === 0 && mcpToConfigure.length === 0) return results;
63
- const authSpinner = spinner();
64
- authSpinner.start("Authenticating...");
65
- if (!await ensureNeonctlAuth()) {
66
- authSpinner.stop("Authentication failed");
67
- for (const editor of [...extensionsToConfigure, ...mcpToConfigure]) results.set(editor, "failed");
68
- return results;
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");
49
+ for (const editor of selectedEditors) results.set(editor, "failed");
50
+ return {
51
+ results,
52
+ authSuccess: false
53
+ };
69
54
  }
70
- authSpinner.stop("Authentication successful ✓");
71
- const apiKey = await createApiKeyFromNeonctl();
55
+ authSpinner?.stop("Authentication successful ✓");
56
+ const apiKey = await createApiKeyFromNeonctl(authOptions);
72
57
  if (!apiKey) {
73
- log.error("Could not create API key after authentication.");
74
- log.info("You can manually create one at: https://console.neon.tech/app/settings/api-keys");
75
- for (const editor of [...extensionsToConfigure, ...mcpToConfigure]) results.set(editor, "failed");
76
- return results;
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
+ }
62
+ for (const editor of selectedEditors) results.set(editor, "failed");
63
+ return {
64
+ results,
65
+ authSuccess: true
66
+ };
77
67
  }
78
- for (const editor of extensionsToConfigure) {
68
+ for (const editor of extensionEditors) {
79
69
  if (!await installExtension(editor)) {
80
70
  results.set(editor, "failed");
81
71
  continue;
@@ -85,15 +75,28 @@ async function installNeon(homeDir, workspaceDir, selectedEditors) {
85
75
  continue;
86
76
  }
87
77
  if (await configureExtension(editor, apiKey)) results.set(editor, "success");
88
- else results.set(editor, "success");
78
+ else results.set(editor, "failed");
89
79
  }
90
- for (const editor of mcpToConfigure) {
91
- const status = await installMCPServerForEditor(homeDir, workspaceDir, editor, apiKey);
92
- results.set(editor, status);
80
+ if (mcpEditors.length > 0) {
81
+ const mcpSpinner = quiet ? null : spinner();
82
+ mcpSpinner?.start("Installing and configuring Neon MCP Server...");
83
+ let mcpSuccessCount = 0;
84
+ for (const editor of mcpEditors) try {
85
+ await installMCPServerViaAddMcp(editor, apiKey);
86
+ results.set(editor, "success");
87
+ mcpSuccessCount++;
88
+ } catch (err) {
89
+ results.set(editor, "failed");
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");
91
+ }
92
+ mcpSpinner?.stop(mcpSuccessCount > 0 ? "Neon MCP Server configuration complete ✓" : "Failed to configure Neon MCP Server");
93
93
  }
94
- return results;
94
+ return {
95
+ results,
96
+ authSuccess: true
97
+ };
95
98
  }
96
-
97
99
  //#endregion
98
100
  export { installNeon };
101
+
99
102
  //# sourceMappingURL=install.js.map