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.
- package/dist/cli.js +368 -33
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +15 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +220 -41
- package/dist/index.js.map +1 -1
- package/dist/interactive.d.ts +12 -0
- package/dist/interactive.d.ts.map +1 -0
- package/dist/interactive.js +495 -0
- package/dist/interactive.js.map +1 -0
- package/dist/lib/agents.d.ts +23 -0
- package/dist/lib/agents.d.ts.map +1 -0
- package/dist/lib/agents.js +148 -0
- package/dist/lib/agents.js.map +1 -0
- package/dist/lib/auth.d.ts +10 -3
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +20 -13
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/bootstrap.d.ts +30 -0
- package/dist/lib/bootstrap.d.ts.map +1 -0
- package/dist/lib/bootstrap.js +61 -0
- package/dist/lib/bootstrap.js.map +1 -0
- package/dist/lib/build-config.d.ts +5 -0
- package/dist/lib/build-config.d.ts.map +1 -0
- package/dist/lib/build-config.js +6 -0
- package/dist/lib/build-config.js.map +1 -0
- package/dist/lib/detect-agent.d.ts +22 -0
- package/dist/lib/detect-agent.d.ts.map +1 -0
- package/dist/lib/detect-agent.js +65 -0
- package/dist/lib/detect-agent.js.map +1 -0
- package/dist/lib/editors.d.ts.map +1 -1
- package/dist/lib/editors.js +1 -2
- package/dist/lib/editors.js.map +1 -1
- package/dist/lib/extension.d.ts +11 -3
- package/dist/lib/extension.d.ts.map +1 -1
- package/dist/lib/extension.js +29 -9
- package/dist/lib/extension.js.map +1 -1
- package/dist/lib/inspect.d.ts +28 -0
- package/dist/lib/inspect.d.ts.map +1 -0
- package/dist/lib/inspect.js +190 -0
- package/dist/lib/inspect.js.map +1 -0
- package/dist/lib/install.d.ts +10 -4
- package/dist/lib/install.d.ts.map +1 -1
- package/dist/lib/install.js +74 -71
- package/dist/lib/install.js.map +1 -1
- package/dist/lib/neonctl.d.ts +32 -0
- package/dist/lib/neonctl.d.ts.map +1 -0
- package/dist/lib/neonctl.js +149 -0
- package/dist/lib/neonctl.js.map +1 -0
- package/dist/lib/phases/auth.d.ts +12 -0
- package/dist/lib/phases/auth.d.ts.map +1 -0
- package/dist/lib/phases/auth.js +188 -0
- package/dist/lib/phases/auth.js.map +1 -0
- package/dist/lib/phases/cleanup.d.ts +12 -0
- package/dist/lib/phases/cleanup.d.ts.map +1 -0
- package/dist/lib/phases/cleanup.js +29 -0
- package/dist/lib/phases/cleanup.js.map +1 -0
- package/dist/lib/phases/db.d.ts +17 -0
- package/dist/lib/phases/db.d.ts.map +1 -0
- package/dist/lib/phases/db.js +258 -0
- package/dist/lib/phases/db.js.map +1 -0
- package/dist/lib/phases/getting-started.d.ts +26 -0
- package/dist/lib/phases/getting-started.d.ts.map +1 -0
- package/dist/lib/phases/getting-started.js +195 -0
- package/dist/lib/phases/getting-started.js.map +1 -0
- package/dist/lib/phases/mcp.d.ts +15 -0
- package/dist/lib/phases/mcp.d.ts.map +1 -0
- package/dist/lib/phases/mcp.js +179 -0
- package/dist/lib/phases/mcp.js.map +1 -0
- package/dist/lib/phases/migrations.d.ts +14 -0
- package/dist/lib/phases/migrations.d.ts.map +1 -0
- package/dist/lib/phases/migrations.js +239 -0
- package/dist/lib/phases/migrations.js.map +1 -0
- package/dist/lib/phases/neon-auth.d.ts +13 -0
- package/dist/lib/phases/neon-auth.d.ts.map +1 -0
- package/dist/lib/phases/neon-auth.js +117 -0
- package/dist/lib/phases/neon-auth.js.map +1 -0
- package/dist/lib/phases/setup.d.ts +41 -0
- package/dist/lib/phases/setup.d.ts.map +1 -0
- package/dist/lib/phases/setup.js +689 -0
- package/dist/lib/phases/setup.js.map +1 -0
- package/dist/lib/phases/skills.d.ts +14 -0
- package/dist/lib/phases/skills.d.ts.map +1 -0
- package/dist/lib/phases/skills.js +80 -0
- package/dist/lib/phases/skills.js.map +1 -0
- package/dist/lib/phases/status.d.ts +10 -0
- package/dist/lib/phases/status.d.ts.map +1 -0
- package/dist/lib/phases/status.js +65 -0
- package/dist/lib/phases/status.js.map +1 -0
- package/dist/lib/resolve-context.d.ts +19 -0
- package/dist/lib/resolve-context.d.ts.map +1 -0
- package/dist/lib/resolve-context.js +112 -0
- package/dist/lib/resolve-context.js.map +1 -0
- package/dist/lib/route-command.d.ts +8 -0
- package/dist/lib/route-command.d.ts.map +1 -0
- package/dist/lib/route-command.js +195 -0
- package/dist/lib/route-command.js.map +1 -0
- package/dist/lib/skills.d.ts +21 -4
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +129 -22
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/types.d.ts +146 -13
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +1 -1
- package/dist/lib/vsix.d.ts +15 -0
- package/dist/lib/vsix.d.ts.map +1 -0
- package/dist/lib/vsix.js +91 -0
- package/dist/lib/vsix.js.map +1 -0
- package/dist/v2.d.ts +31 -0
- package/dist/v2.d.ts.map +1 -0
- package/dist/v2.js +147 -0
- package/dist/v2.js.map +1 -0
- package/package.json +9 -4
- package/dist/lib/mcp-config.d.ts +0 -24
- package/dist/lib/mcp-config.d.ts.map +0 -1
- package/dist/lib/mcp-config.js +0 -51
- package/dist/lib/mcp-config.js.map +0 -1
package/dist/lib/extension.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
*
|
|
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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"}
|
package/dist/lib/install.d.ts
CHANGED
|
@@ -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(
|
|
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"],"
|
|
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"}
|
package/dist/lib/install.js
CHANGED
|
@@ -1,81 +1,71 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getAddMcpAgentId } from "./agents.js";
|
|
2
2
|
import { createApiKeyFromNeonctl, ensureNeonctlAuth } from "./auth.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
-
*
|
|
9
|
-
*
|
|
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
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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(
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
71
|
-
const apiKey = await createApiKeyFromNeonctl();
|
|
55
|
+
authSpinner?.stop("Authentication successful ✓");
|
|
56
|
+
const apiKey = await createApiKeyFromNeonctl(authOptions);
|
|
72
57
|
if (!apiKey) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
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, "
|
|
78
|
+
else results.set(editor, "failed");
|
|
89
79
|
}
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
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
|
|
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
|