neon-init 0.10.1 → 0.11.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;AAiBA;;iBAAsB,IAAA,CAAA,GAAQ"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;AAkBA;;iBAAsB,IAAA,CAAA,GAAQ"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { detectAvailableEditors } from "./lib/editors.js";
2
- import { installMCPServer } from "./lib/install.js";
2
+ import { usesExtension } from "./lib/extension.js";
3
+ import { installNeon } from "./lib/install.js";
3
4
  import { confirm, intro, isCancel, log, multiselect, note, outro } from "@clack/prompts";
4
5
  import { bold, cyan } from "yoctocolors";
5
6
 
@@ -20,11 +21,11 @@ async function init() {
20
21
  if (availableEditors.length === 0) {
21
22
  log.warn("No supported editors detected on your system.");
22
23
  log.info("Supported editors:");
23
- log.info(" • VS Code (with GitHub Copilot)");
24
- log.info(" • Cursor");
25
- log.info(" • Claude CLI");
24
+ log.info(" • VS Code (with Neon Local Connect extension)");
25
+ log.info(" • Cursor (with Neon Local Connect extension)");
26
+ log.info(" • Claude CLI (with MCP Server)");
26
27
  const continueAnyway = await confirm({
27
- message: "Would you like to configure MCP anyway? (You can manually select your editor)",
28
+ message: "Would you like to configure Neon anyway? (You can manually select your editor)",
28
29
  initialValue: true
29
30
  });
30
31
  if (isCancel(continueAnyway) || !continueAnyway) {
@@ -40,7 +41,8 @@ async function init() {
40
41
  "Claude CLI"
41
42
  ].map((editor) => ({
42
43
  value: editor,
43
- label: editor
44
+ label: editor,
45
+ hint: editor === "Claude CLI" ? "MCP Server" : "Neon Local Connect extension"
44
46
  })),
45
47
  initialValues: availableEditors,
46
48
  required: true
@@ -55,22 +57,36 @@ async function init() {
55
57
  outro("Installation cancelled");
56
58
  process.exit(0);
57
59
  }
58
- const results = await installMCPServer(homeDir, workspaceDir, selectedEditors);
60
+ const results = await installNeon(homeDir, workspaceDir, selectedEditors);
59
61
  const successful = [];
60
62
  const failed = [];
61
63
  for (const [editor, status] of results.entries()) if (status === "success") successful.push(editor);
62
64
  else failed.push(editor);
63
- const successList = successful.join(" / ");
64
- if (successful.length > 0) {
65
- log.step("Installed Neon MCP server");
66
- log.step(`Success! Neon is now ready to use with ${successList}.\n`);
65
+ const extensionEditors = successful.filter(usesExtension);
66
+ const mcpEditors = successful.filter((e) => !usesExtension(e));
67
+ const failedExtensionEditors = failed.filter(usesExtension);
68
+ const failedMcpEditors = failed.filter((e) => !usesExtension(e));
69
+ if (extensionEditors.length > 0) {
70
+ const extSuccessList = extensionEditors.join(" / ");
71
+ log.step(`Neon Local Connect extension installed for ${extSuccessList}.\n`);
67
72
  }
68
- if (failed.length > 0) log.error(`Failed to configure: ${failed.join(" / ")}`);
73
+ if (mcpEditors.length > 0) {
74
+ const mcpSuccessList = mcpEditors.join(" / ");
75
+ log.step(`Neon MCP Server is now ready to use with ${mcpSuccessList}.\n`);
76
+ }
77
+ if (failedExtensionEditors.length > 0) {
78
+ log.info("Failed to install extension. For the best local development experience, install Neon Local Connect manually:");
79
+ for (const editor of failedExtensionEditors) if (editor === "VS Code") log.info(" • VS Code: https://marketplace.visualstudio.com/items?itemName=databricks.neon-local-connect");
80
+ else if (editor === "Cursor") log.info(" • Cursor: https://open-vsx.org/extension/databricks/neon-local-connect");
81
+ }
82
+ if (failedMcpEditors.length > 0) log.error(`Failed to configure MCP Server for ${failedMcpEditors.join(" / ")}`);
69
83
  if (successful.length === 0) {
70
84
  outro("Installation cancelled or failed. Please check the output above and try again.");
71
85
  process.exit(1);
72
86
  }
73
- note(`\x1b[0mRestart ${successList} and type in "${bold(cyan("Get started with Neon"))}\x1b[0m" in the chat`, "What's next?");
87
+ if (extensionEditors.length > 0 && mcpEditors.length === 0) note(`\x1b[0mRestart ${extensionEditors.join(" / ")}, open the Neon extension and type in "${bold(cyan("Get started with Neon"))}\x1b[0m" in your agent chat`, "What's next?");
88
+ else if (mcpEditors.length > 0 && extensionEditors.length === 0) note(`\x1b[0mRestart ${mcpEditors.join(" / ")} and type in "${bold(cyan("Get started with Neon"))}\x1b[0m" in the chat`, "What's next?");
89
+ else note(`\x1b[0mFor ${extensionEditors.join(" / ")}: Restart, open the Neon extension and type in "${bold(cyan("Get started with Neon"))}\x1b[0m" in your agent chat\n\x1b[0mFor ${mcpEditors.join(" / ")}: Restart and type in "${bold(cyan("Get started with Neon"))}\x1b[0m" in the chat`, "What's next?");
74
90
  outro("Have feedback? Email us at feedback@neon.tech");
75
91
  }
76
92
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["successful: Editor[]","failed: Editor[]"],"sources":["../src/index.ts"],"sourcesContent":["import {\n\tconfirm,\n\tintro,\n\tisCancel,\n\tlog,\n\tmultiselect,\n\tnote,\n\toutro,\n} from \"@clack/prompts\";\nimport { bold, cyan } from \"yoctocolors\";\nimport { detectAvailableEditors } from \"./lib/editors.js\";\nimport { installMCPServer } from \"./lib/install.js\";\nimport type { Editor } from \"./lib/types.js\";\n\n/**\n * Initialize Neon projects with MCP Server\n */\nexport async function init(): Promise<void> {\n\tintro(\"Adding Neon to your project\");\n\n\t// Get the home directory\n\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\tif (!homeDir) {\n\t\tlog.error(\"Could not determine home directory\");\n\t\toutro(\"📣 Is this unexpected? Email us at feedback@neon.tech\");\n\t\tprocess.exit(1);\n\t}\n\n\t// Get the current workspace directory\n\tconst workspaceDir = process.cwd();\n\n\t// Detect available editors\n\tconst availableEditors = await detectAvailableEditors(homeDir);\n\n\t// If no editors detected, offer to continue anyway\n\tif (availableEditors.length === 0) {\n\t\tlog.warn(\"No supported editors detected on your system.\");\n\t\tlog.info(\"Supported editors:\");\n\t\tlog.info(\" • VS Code (with GitHub Copilot)\");\n\t\tlog.info(\" • Cursor\");\n\t\tlog.info(\" • Claude CLI\");\n\n\t\tconst continueAnyway = await confirm({\n\t\t\tmessage:\n\t\t\t\t\"Would you like to configure MCP anyway? (You can manually select your editor)\",\n\t\t\tinitialValue: true,\n\t\t});\n\n\t\tif (isCancel(continueAnyway) || !continueAnyway) {\n\t\t\toutro(\"Installation cancelled\");\n\t\t\tprocess.exit(0);\n\t\t}\n\t}\n\n\t// Determine which editors to configure\n\tconst response = await multiselect({\n\t\tmessage:\n\t\t\t\"Which editor(s) would you like to configure? (Space to toggle each option, Enter to confirm your selection)\",\n\t\toptions: [\"Cursor\", \"VS Code\", \"Claude CLI\"].map((editor) => ({\n\t\t\tvalue: editor,\n\t\t\tlabel: editor,\n\t\t})),\n\t\tinitialValues: availableEditors, // Select detected editors by default\n\t\trequired: true,\n\t});\n\n\tif (isCancel(response)) {\n\t\toutro(\"Installation cancelled\");\n\t\tprocess.exit(0);\n\t}\n\n\tconst selectedEditors = response as Editor[];\n\n\tif (selectedEditors.length === 0) {\n\t\tlog.warn(\"No editors selected.\");\n\t\toutro(\"Installation cancelled\");\n\t\tprocess.exit(0);\n\t}\n\n\t// Install MCP server for selected editors\n\tconst results = await installMCPServer(\n\t\thomeDir,\n\t\tworkspaceDir,\n\t\tselectedEditors,\n\t);\n\n\tconst successful: Editor[] = [];\n\tconst failed: Editor[] = [];\n\n\tfor (const [editor, status] of results.entries()) {\n\t\tif (status === \"success\") {\n\t\t\tsuccessful.push(editor);\n\t\t} else {\n\t\t\tfailed.push(editor);\n\t\t}\n\t}\n\tconst successList = successful.join(\" / \");\n\tif (successful.length > 0) {\n\t\tlog.step(\"Installed Neon MCP server\");\n\t\tlog.step(`Success! Neon is now ready to use with ${successList}.\\n`);\n\t}\n\n\tif (failed.length > 0) {\n\t\tlog.error(`Failed to configure: ${failed.join(\" / \")}`);\n\t}\n\n\t// Exit with error if all failed\n\tif (successful.length === 0) {\n\t\toutro(\n\t\t\t\"Installation cancelled or failed. Please check the output above and try again.\",\n\t\t);\n\t\tprocess.exit(1);\n\t}\n\n\tnote(\n\t\t`\\x1b[0mRestart ${successList} and type in \"${bold(cyan(\"Get started with Neon\"))}\\x1b[0m\" in the chat`,\n\t\t\"What's next?\",\n\t);\n\n\toutro(\"Have feedback? Email us at feedback@neon.tech\");\n}\n"],"mappings":";;;;;;;;;AAiBA,eAAsB,OAAsB;AAC3C,OAAM,8BAA8B;CAGpC,MAAM,UAAU,QAAQ,IAAI,QAAQ,QAAQ,IAAI;AAChD,KAAI,CAAC,SAAS;AACb,MAAI,MAAM,qCAAqC;AAC/C,QAAM,wDAAwD;AAC9D,UAAQ,KAAK,EAAE;;CAIhB,MAAM,eAAe,QAAQ,KAAK;CAGlC,MAAM,mBAAmB,MAAM,uBAAuB,QAAQ;AAG9D,KAAI,iBAAiB,WAAW,GAAG;AAClC,MAAI,KAAK,gDAAgD;AACzD,MAAI,KAAK,qBAAqB;AAC9B,MAAI,KAAK,oCAAoC;AAC7C,MAAI,KAAK,aAAa;AACtB,MAAI,KAAK,iBAAiB;EAE1B,MAAM,iBAAiB,MAAM,QAAQ;GACpC,SACC;GACD,cAAc;GACd,CAAC;AAEF,MAAI,SAAS,eAAe,IAAI,CAAC,gBAAgB;AAChD,SAAM,yBAAyB;AAC/B,WAAQ,KAAK,EAAE;;;CAKjB,MAAM,WAAW,MAAM,YAAY;EAClC,SACC;EACD,SAAS;GAAC;GAAU;GAAW;GAAa,CAAC,KAAK,YAAY;GAC7D,OAAO;GACP,OAAO;GACP,EAAE;EACH,eAAe;EACf,UAAU;EACV,CAAC;AAEF,KAAI,SAAS,SAAS,EAAE;AACvB,QAAM,yBAAyB;AAC/B,UAAQ,KAAK,EAAE;;CAGhB,MAAM,kBAAkB;AAExB,KAAI,gBAAgB,WAAW,GAAG;AACjC,MAAI,KAAK,uBAAuB;AAChC,QAAM,yBAAyB;AAC/B,UAAQ,KAAK,EAAE;;CAIhB,MAAM,UAAU,MAAM,iBACrB,SACA,cACA,gBACA;CAED,MAAMA,aAAuB,EAAE;CAC/B,MAAMC,SAAmB,EAAE;AAE3B,MAAK,MAAM,CAAC,QAAQ,WAAW,QAAQ,SAAS,CAC/C,KAAI,WAAW,UACd,YAAW,KAAK,OAAO;KAEvB,QAAO,KAAK,OAAO;CAGrB,MAAM,cAAc,WAAW,KAAK,MAAM;AAC1C,KAAI,WAAW,SAAS,GAAG;AAC1B,MAAI,KAAK,4BAA4B;AACrC,MAAI,KAAK,0CAA0C,YAAY,KAAK;;AAGrE,KAAI,OAAO,SAAS,EACnB,KAAI,MAAM,wBAAwB,OAAO,KAAK,MAAM,GAAG;AAIxD,KAAI,WAAW,WAAW,GAAG;AAC5B,QACC,iFACA;AACD,UAAQ,KAAK,EAAE;;AAGhB,MACC,kBAAkB,YAAY,gBAAgB,KAAK,KAAK,wBAAwB,CAAC,CAAC,uBAClF,eACA;AAED,OAAM,gDAAgD"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n\tconfirm,\n\tintro,\n\tisCancel,\n\tlog,\n\tmultiselect,\n\tnote,\n\toutro,\n} from \"@clack/prompts\";\nimport { bold, cyan } from \"yoctocolors\";\nimport { detectAvailableEditors } from \"./lib/editors.js\";\nimport { usesExtension } from \"./lib/extension.js\";\nimport { installNeon } from \"./lib/install.js\";\nimport type { Editor } from \"./lib/types.js\";\n\n/**\n * Initialize Neon projects with MCP Server\n */\nexport async function init(): Promise<void> {\n\tintro(\"Adding Neon to your project\");\n\n\t// Get the home directory\n\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\tif (!homeDir) {\n\t\tlog.error(\"Could not determine home directory\");\n\t\toutro(\"📣 Is this unexpected? Email us at feedback@neon.tech\");\n\t\tprocess.exit(1);\n\t}\n\n\t// Get the current workspace directory\n\tconst workspaceDir = process.cwd();\n\n\t// Detect available editors\n\tconst availableEditors = await detectAvailableEditors(homeDir);\n\n\t// If no editors detected, offer to continue anyway\n\tif (availableEditors.length === 0) {\n\t\tlog.warn(\"No supported editors detected on your system.\");\n\t\tlog.info(\"Supported editors:\");\n\t\tlog.info(\" • VS Code (with Neon Local Connect extension)\");\n\t\tlog.info(\" • Cursor (with Neon Local Connect extension)\");\n\t\tlog.info(\" • Claude CLI (with MCP Server)\");\n\n\t\tconst continueAnyway = await confirm({\n\t\t\tmessage:\n\t\t\t\t\"Would you like to configure Neon anyway? (You can manually select your editor)\",\n\t\t\tinitialValue: true,\n\t\t});\n\n\t\tif (isCancel(continueAnyway) || !continueAnyway) {\n\t\t\toutro(\"Installation cancelled\");\n\t\t\tprocess.exit(0);\n\t\t}\n\t}\n\n\t// Determine which editors to configure\n\tconst response = await multiselect({\n\t\tmessage:\n\t\t\t\"Which editor(s) would you like to configure? (Space to toggle each option, Enter to confirm your selection)\",\n\t\toptions: [\"Cursor\", \"VS Code\", \"Claude CLI\"].map((editor) => ({\n\t\t\tvalue: editor,\n\t\t\tlabel: editor,\n\t\t\thint:\n\t\t\t\teditor === \"Claude CLI\"\n\t\t\t\t\t? \"MCP Server\"\n\t\t\t\t\t: \"Neon Local Connect extension\",\n\t\t})),\n\t\tinitialValues: availableEditors, // Select detected editors by default\n\t\trequired: true,\n\t});\n\n\tif (isCancel(response)) {\n\t\toutro(\"Installation cancelled\");\n\t\tprocess.exit(0);\n\t}\n\n\tconst selectedEditors = response as Editor[];\n\n\tif (selectedEditors.length === 0) {\n\t\tlog.warn(\"No editors selected.\");\n\t\toutro(\"Installation cancelled\");\n\t\tprocess.exit(0);\n\t}\n\n\t// Install Neon for selected editors\n\tconst results = await installNeon(homeDir, workspaceDir, selectedEditors);\n\n\tconst successful: Editor[] = [];\n\tconst failed: Editor[] = [];\n\n\tfor (const [editor, status] of results.entries()) {\n\t\tif (status === \"success\") {\n\t\t\tsuccessful.push(editor);\n\t\t} else {\n\t\t\tfailed.push(editor);\n\t\t}\n\t}\n\n\t// Show different messages based on what was installed\n\tconst extensionEditors = successful.filter(usesExtension);\n\tconst mcpEditors = successful.filter((e) => !usesExtension(e));\n\tconst failedExtensionEditors = failed.filter(usesExtension);\n\tconst failedMcpEditors = failed.filter((e) => !usesExtension(e));\n\n\tif (extensionEditors.length > 0) {\n\t\tconst extSuccessList = extensionEditors.join(\" / \");\n\t\tlog.step(\n\t\t\t`Neon Local Connect extension installed for ${extSuccessList}.\\n`,\n\t\t);\n\t}\n\n\tif (mcpEditors.length > 0) {\n\t\tconst mcpSuccessList = mcpEditors.join(\" / \");\n\t\tlog.step(\n\t\t\t`Neon MCP Server is now ready to use with ${mcpSuccessList}.\\n`,\n\t\t);\n\t}\n\n\t// Show helpful installation links for failed extension installations\n\tif (failedExtensionEditors.length > 0) {\n\t\tlog.info(\n\t\t\t\"Failed to install extension. For the best local development experience, install Neon Local Connect manually:\",\n\t\t);\n\t\tfor (const editor of failedExtensionEditors) {\n\t\t\tif (editor === \"VS Code\") {\n\t\t\t\tlog.info(\n\t\t\t\t\t\" • VS Code: https://marketplace.visualstudio.com/items?itemName=databricks.neon-local-connect\",\n\t\t\t\t);\n\t\t\t} else if (editor === \"Cursor\") {\n\t\t\t\tlog.info(\n\t\t\t\t\t\" • Cursor: https://open-vsx.org/extension/databricks/neon-local-connect\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (failedMcpEditors.length > 0) {\n\t\tlog.error(\n\t\t\t`Failed to configure MCP Server for ${failedMcpEditors.join(\" / \")}`,\n\t\t);\n\t}\n\n\t// Exit with error if all installations failed\n\tif (successful.length === 0) {\n\t\toutro(\n\t\t\t\"Installation cancelled or failed. Please check the output above and try again.\",\n\t\t);\n\t\tprocess.exit(1);\n\t}\n\n\tif (extensionEditors.length > 0 && mcpEditors.length === 0) {\n\t\t// Only extension editors (VS Code/Cursor)\n\t\tconst extSuccessList = extensionEditors.join(\" / \");\n\t\tnote(\n\t\t\t`\\x1b[0mRestart ${extSuccessList}, open the Neon extension and type in \"${bold(cyan(\"Get started with Neon\"))}\\x1b[0m\" in your agent chat`,\n\t\t\t\"What's next?\",\n\t\t);\n\t} else if (mcpEditors.length > 0 && extensionEditors.length === 0) {\n\t\t// Only MCP editors (Claude CLI)\n\t\tconst mcpSuccessList = mcpEditors.join(\" / \");\n\t\tnote(\n\t\t\t`\\x1b[0mRestart ${mcpSuccessList} and type in \"${bold(cyan(\"Get started with Neon\"))}\\x1b[0m\" in the chat`,\n\t\t\t\"What's next?\",\n\t\t);\n\t} else {\n\t\t// Mixed editors\n\t\tnote(\n\t\t\t`\\x1b[0mFor ${extensionEditors.join(\" / \")}: Restart, open the Neon extension and type in \"${bold(cyan(\"Get started with Neon\"))}\\x1b[0m\" in your agent chat\\n\\x1b[0mFor ${mcpEditors.join(\" / \")}: Restart and type in \"${bold(cyan(\"Get started with Neon\"))}\\x1b[0m\" in the chat`,\n\t\t\t\"What's next?\",\n\t\t);\n\t}\n\n\toutro(\"Have feedback? Email us at feedback@neon.tech\");\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"editors.js","names":["editors: Editor[]"],"sources":["../../src/lib/editors.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { execa } from \"execa\";\nimport type { Editor } from \"./types.js\";\n\n/**\n * Gets VS Code's global config directory based on the platform\n */\nexport function getVSCodeGlobalConfigDir(homeDir: string): string | null {\n\tconst platform = process.platform;\n\n\tif (platform === \"darwin\") {\n\t\t// macOS: ~/Library/Application Support/Code/User\n\t\treturn resolve(\n\t\t\thomeDir,\n\t\t\t\"Library\",\n\t\t\t\"Application Support\",\n\t\t\t\"Code\",\n\t\t\t\"User\",\n\t\t);\n\t}\n\tif (platform === \"linux\") {\n\t\t// Linux: ~/.config/Code/User\n\t\treturn resolve(homeDir, \".config\", \"Code\", \"User\");\n\t}\n\tif (platform === \"win32\") {\n\t\t// Windows: %APPDATA%\\Code\\User\n\t\tconst appData = process.env.APPDATA;\n\t\tif (appData) {\n\t\t\treturn resolve(appData, \"Code\", \"User\");\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Checks if Claude CLI is installed\n */\nasync function isClaudeCLIInstalled(): Promise<boolean> {\n\ttry {\n\t\tawait execa(\"claude\", [\"--version\"], {\n\t\t\tstdio: \"ignore\",\n\t\t\ttimeout: 5000,\n\t\t});\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Detects which editors are installed on the system\n */\nexport async function detectAvailableEditors(\n\thomeDir: string,\n): Promise<Editor[]> {\n\tconst editors: Editor[] = [];\n\n\t// Check for Cursor (global config directory)\n\tconst cursorDir = resolve(homeDir, \".cursor\");\n\tif (existsSync(cursorDir)) {\n\t\teditors.push(\"Cursor\");\n\t}\n\n\t// Check if VS Code's global config directory exists\n\tconst vscodeGlobalDir = getVSCodeGlobalConfigDir(homeDir);\n\tif (vscodeGlobalDir && existsSync(vscodeGlobalDir)) {\n\t\teditors.push(\"VS Code\");\n\t}\n\n\t// Check for Claude CLI by running the command\n\tconst claudeInstalled = await isClaudeCLIInstalled();\n\tif (claudeInstalled) {\n\t\teditors.push(\"Claude CLI\");\n\t}\n\n\treturn editors;\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,yBAAyB,SAAgC;CACxE,MAAM,WAAW,QAAQ;AAEzB,KAAI,aAAa,SAEhB,QAAO,QACN,SACA,WACA,uBACA,QACA,OACA;AAEF,KAAI,aAAa,QAEhB,QAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO;AAEnD,KAAI,aAAa,SAAS;EAEzB,MAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,QACH,QAAO,QAAQ,SAAS,QAAQ,OAAO;;AAIzC,QAAO;;;;;AAMR,eAAe,uBAAyC;AACvD,KAAI;AACH,QAAM,MAAM,UAAU,CAAC,YAAY,EAAE;GACpC,OAAO;GACP,SAAS;GACT,CAAC;AACF,SAAO;SACA;AACP,SAAO;;;;;;AAOT,eAAsB,uBACrB,SACoB;CACpB,MAAMA,UAAoB,EAAE;AAI5B,KAAI,WADc,QAAQ,SAAS,UAAU,CACpB,CACxB,SAAQ,KAAK,SAAS;CAIvB,MAAM,kBAAkB,yBAAyB,QAAQ;AACzD,KAAI,mBAAmB,WAAW,gBAAgB,CACjD,SAAQ,KAAK,UAAU;AAKxB,KADwB,MAAM,sBAAsB,CAEnD,SAAQ,KAAK,aAAa;AAG3B,QAAO"}
1
+ {"version":3,"file":"editors.js","names":[],"sources":["../../src/lib/editors.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { execa } from \"execa\";\nimport type { Editor } from \"./types.js\";\n\n/**\n * Gets VS Code's global config directory based on the platform\n */\nexport function getVSCodeGlobalConfigDir(homeDir: string): string | null {\n\tconst platform = process.platform;\n\n\tif (platform === \"darwin\") {\n\t\t// macOS: ~/Library/Application Support/Code/User\n\t\treturn resolve(\n\t\t\thomeDir,\n\t\t\t\"Library\",\n\t\t\t\"Application Support\",\n\t\t\t\"Code\",\n\t\t\t\"User\",\n\t\t);\n\t}\n\tif (platform === \"linux\") {\n\t\t// Linux: ~/.config/Code/User\n\t\treturn resolve(homeDir, \".config\", \"Code\", \"User\");\n\t}\n\tif (platform === \"win32\") {\n\t\t// Windows: %APPDATA%\\Code\\User\n\t\tconst appData = process.env.APPDATA;\n\t\tif (appData) {\n\t\t\treturn resolve(appData, \"Code\", \"User\");\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Checks if Claude CLI is installed\n */\nasync function isClaudeCLIInstalled(): Promise<boolean> {\n\ttry {\n\t\tawait execa(\"claude\", [\"--version\"], {\n\t\t\tstdio: \"ignore\",\n\t\t\ttimeout: 5000,\n\t\t});\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Detects which editors are installed on the system\n */\nexport async function detectAvailableEditors(\n\thomeDir: string,\n): Promise<Editor[]> {\n\tconst editors: Editor[] = [];\n\n\t// Check for Cursor (global config directory)\n\tconst cursorDir = resolve(homeDir, \".cursor\");\n\tif (existsSync(cursorDir)) {\n\t\teditors.push(\"Cursor\");\n\t}\n\n\t// Check if VS Code's global config directory exists\n\tconst vscodeGlobalDir = getVSCodeGlobalConfigDir(homeDir);\n\tif (vscodeGlobalDir && existsSync(vscodeGlobalDir)) {\n\t\teditors.push(\"VS Code\");\n\t}\n\n\t// Check for Claude CLI by running the command\n\tconst claudeInstalled = await isClaudeCLIInstalled();\n\tif (claudeInstalled) {\n\t\teditors.push(\"Claude CLI\");\n\t}\n\n\treturn editors;\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,yBAAyB,SAAgC;CACxE,MAAM,WAAW,QAAQ;AAEzB,KAAI,aAAa,SAEhB,QAAO,QACN,SACA,WACA,uBACA,QACA,OACA;AAEF,KAAI,aAAa,QAEhB,QAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO;AAEnD,KAAI,aAAa,SAAS;EAEzB,MAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,QACH,QAAO,QAAQ,SAAS,QAAQ,OAAO;;AAIzC,QAAO;;;;;AAMR,eAAe,uBAAyC;AACvD,KAAI;AACH,QAAM,MAAM,UAAU,CAAC,YAAY,EAAE;GACpC,OAAO;GACP,SAAS;GACT,CAAC;AACF,SAAO;SACA;AACP,SAAO;;;;;;AAOT,eAAsB,uBACrB,SACoB;CACpB,MAAM,UAAoB,EAAE;AAI5B,KAAI,WADc,QAAQ,SAAS,UAAU,CACpB,CACxB,SAAQ,KAAK,SAAS;CAIvB,MAAM,kBAAkB,yBAAyB,QAAQ;AACzD,KAAI,mBAAmB,WAAW,gBAAgB,CACjD,SAAQ,KAAK,UAAU;AAKxB,KADwB,MAAM,sBAAsB,CAEnD,SAAQ,KAAK,aAAa;AAG3B,QAAO"}
@@ -0,0 +1,27 @@
1
+ import { Editor } from "./types.js";
2
+
3
+ //#region src/lib/extension.d.ts
4
+
5
+ /**
6
+ * Finds the CLI command for an editor by checking known installation paths
7
+ * On macOS, also uses mdfind to locate the app if standard paths fail
8
+ * Falls back to the simple command name if no full path is found (in case it's in PATH)
9
+ */
10
+ declare function findEditorCommand(editor: Editor): Promise<string | null>;
11
+ /**
12
+ * Installs the Neon Local Connect extension for VS Code or Cursor
13
+ * Returns success only if installation succeeds, fails silently otherwise
14
+ */
15
+ declare function installExtension(editor: Editor): Promise<boolean>;
16
+ /**
17
+ * Configures the Neon Local Connect extension with the API key
18
+ * Uses the extension's URI handler to trigger the import-api-key command
19
+ */
20
+ declare function configureExtension(editor: Editor, apiKey: string): Promise<boolean>;
21
+ /**
22
+ * Returns the editor types that should use extension installation (vs MCP)
23
+ */
24
+ declare function usesExtension(editor: Editor): boolean;
25
+ //#endregion
26
+ export { configureExtension, findEditorCommand, installExtension, usesExtension };
27
+ //# sourceMappingURL=extension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.ts","names":[],"sources":["../../src/lib/extension.ts"],"sourcesContent":[],"mappings":";;;;;;AAuGA;;;AAEG,iBAFmB,iBAAA,CAEnB,MAAA,EADM,MACN,CAAA,EAAA,OAAA,CAAA,MAAA,GAAA,IAAA,CAAA;;AAsDH;;;AAAwD,iBAAlC,gBAAA,CAAkC,MAAA,EAAT,MAAS,CAAA,EAAA,OAAA,CAAA,OAAA,CAAA;;AAsBxD;;;AAGG,iBAHmB,kBAAA,CAGnB,MAAA,EAFM,MAEN,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,OAAA,CAAA,OAAA,CAAA;;AAoCH;;iBAAgB,aAAA,SAAsB"}
@@ -0,0 +1,160 @@
1
+ import { existsSync } from "node:fs";
2
+ import { execa } from "execa";
3
+
4
+ //#region src/lib/extension.ts
5
+ const NEON_EXTENSION_ID = "databricks.neon-local-connect";
6
+ /**
7
+ * Uses macOS mdfind to locate an app by bundle identifier
8
+ */
9
+ async function findAppWithMdfind(bundleId) {
10
+ try {
11
+ return (await execa("mdfind", [`kMDItemCFBundleIdentifier == '${bundleId}'`], { timeout: 5e3 })).stdout.trim().split("\n").filter(Boolean)[0] || null;
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+ /**
17
+ * Known installation paths for VS Code CLI
18
+ */
19
+ function getVSCodePaths() {
20
+ const platform = process.platform;
21
+ const home = process.env.HOME || "";
22
+ if (platform === "darwin") return [
23
+ "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
24
+ "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders",
25
+ `${home}/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code`,
26
+ `${home}/Downloads/Visual Studio Code.app/Contents/Resources/app/bin/code`
27
+ ];
28
+ if (platform === "linux") return [
29
+ "/usr/share/code/bin/code",
30
+ "/usr/bin/code",
31
+ "/snap/bin/code",
32
+ "/usr/share/code-insiders/bin/code-insiders"
33
+ ];
34
+ if (platform === "win32") {
35
+ const localAppData = process.env.LOCALAPPDATA || "";
36
+ const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
37
+ return [
38
+ `${localAppData}\\Programs\\Microsoft VS Code\\bin\\code.cmd`,
39
+ `${programFiles}\\Microsoft VS Code\\bin\\code.cmd`,
40
+ `${localAppData}\\Programs\\Microsoft VS Code Insiders\\bin\\code-insiders.cmd`
41
+ ];
42
+ }
43
+ return [];
44
+ }
45
+ /**
46
+ * Known installation paths for Cursor CLI
47
+ */
48
+ function getCursorPaths() {
49
+ const platform = process.platform;
50
+ const home = process.env.HOME || "";
51
+ if (platform === "darwin") return [
52
+ "/Applications/Cursor.app/Contents/Resources/app/bin/cursor",
53
+ `${home}/Applications/Cursor.app/Contents/Resources/app/bin/cursor`,
54
+ `${home}/Downloads/Cursor.app/Contents/Resources/app/bin/cursor`
55
+ ];
56
+ if (platform === "linux") return [
57
+ "/usr/share/cursor/bin/cursor",
58
+ "/usr/bin/cursor",
59
+ `${home}/.local/bin/cursor`,
60
+ "/opt/cursor/bin/cursor"
61
+ ];
62
+ if (platform === "win32") {
63
+ const localAppData = process.env.LOCALAPPDATA || "";
64
+ const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
65
+ return [
66
+ `${localAppData}\\Programs\\Cursor\\resources\\app\\bin\\cursor.cmd`,
67
+ `${localAppData}\\cursor\\Cursor.exe`,
68
+ `${programFiles}\\Cursor\\resources\\app\\bin\\cursor.cmd`
69
+ ];
70
+ }
71
+ return [];
72
+ }
73
+ /**
74
+ * Finds the CLI command for an editor by checking known installation paths
75
+ * On macOS, also uses mdfind to locate the app if standard paths fail
76
+ * Falls back to the simple command name if no full path is found (in case it's in PATH)
77
+ */
78
+ async function findEditorCommand(editor) {
79
+ let paths;
80
+ let fallbackCommand;
81
+ let bundleId = null;
82
+ if (editor === "VS Code") {
83
+ paths = getVSCodePaths();
84
+ fallbackCommand = "code";
85
+ bundleId = "com.microsoft.VSCode";
86
+ } else if (editor === "Cursor") {
87
+ paths = getCursorPaths();
88
+ fallbackCommand = "cursor";
89
+ bundleId = "com.todesktop.230313mzl4w4u92";
90
+ } else return null;
91
+ for (const path of paths) if (existsSync(path)) return path;
92
+ if (process.platform === "darwin" && bundleId) {
93
+ const appPath = await findAppWithMdfind(bundleId);
94
+ if (appPath) {
95
+ const cliPath = `${appPath}/Contents/Resources/app/bin/${fallbackCommand}`;
96
+ if (existsSync(cliPath)) return cliPath;
97
+ }
98
+ }
99
+ return fallbackCommand;
100
+ }
101
+ /**
102
+ * Gets the URI scheme for an editor
103
+ */
104
+ function getEditorUriScheme(editor) {
105
+ if (editor === "VS Code") return "vscode";
106
+ if (editor === "Cursor") return "cursor";
107
+ return null;
108
+ }
109
+ /**
110
+ * Installs the Neon Local Connect extension for VS Code or Cursor
111
+ * Returns success only if installation succeeds, fails silently otherwise
112
+ */
113
+ async function installExtension(editor) {
114
+ const command = await findEditorCommand(editor);
115
+ if (!command) return false;
116
+ try {
117
+ await execa(command, [
118
+ "--install-extension",
119
+ NEON_EXTENSION_ID,
120
+ "--pre-release"
121
+ ]);
122
+ return true;
123
+ } catch {
124
+ return false;
125
+ }
126
+ }
127
+ /**
128
+ * Configures the Neon Local Connect extension with the API key
129
+ * Uses the extension's URI handler to trigger the import-api-key command
130
+ */
131
+ async function configureExtension(editor, apiKey) {
132
+ const scheme = getEditorUriScheme(editor);
133
+ if (!scheme) return false;
134
+ const uri = `${scheme}://${NEON_EXTENSION_ID}/import-api-key?token=${encodeURIComponent(apiKey)}`;
135
+ try {
136
+ const platform = process.platform;
137
+ if (platform === "darwin") await execa("open", [uri], { timeout: 1e4 });
138
+ else if (platform === "linux") await execa("xdg-open", [uri], { timeout: 1e4 });
139
+ else if (platform === "win32") await execa("cmd", [
140
+ "/c",
141
+ "start",
142
+ "",
143
+ uri
144
+ ], { timeout: 1e4 });
145
+ else return false;
146
+ return true;
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
151
+ /**
152
+ * Returns the editor types that should use extension installation (vs MCP)
153
+ */
154
+ function usesExtension(editor) {
155
+ return editor === "VS Code" || editor === "Cursor";
156
+ }
157
+
158
+ //#endregion
159
+ export { configureExtension, findEditorCommand, installExtension, usesExtension };
160
+ //# sourceMappingURL=extension.js.map
@@ -0,0 +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 * 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, [\n\t\t\t\"--install-extension\",\n\t\t\tNEON_EXTENSION_ID,\n\t\t\t\"--pre-release\",\n\t\t]);\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":";;;;AAIA,MAAM,oBAAoB;;;;AAK1B,eAAe,kBAAkB,UAA0C;AAC1E,KAAI;AAOH,UANe,MAAM,MACpB,UACA,CAAC,iCAAiC,SAAS,GAAG,EAC9C,EAAE,SAAS,KAAM,CACjB,EACoB,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,OAAO,QAAQ,CACjD,MAAM;SACZ;AACP,SAAO;;;;;;AAOT,SAAS,iBAA2B;CACnC,MAAM,WAAW,QAAQ;CACzB,MAAM,OAAO,QAAQ,IAAI,QAAQ;AAEjC,KAAI,aAAa,SAChB,QAAO;EACN;EACA;EACA,GAAG,KAAK;EACR,GAAG,KAAK;EACR;AAGF,KAAI,aAAa,QAChB,QAAO;EACN;EACA;EACA;EACA;EACA;AAGF,KAAI,aAAa,SAAS;EACzB,MAAM,eAAe,QAAQ,IAAI,gBAAgB;EACjD,MAAM,eAAe,QAAQ,IAAI,gBAAgB;AACjD,SAAO;GACN,GAAG,aAAa;GAChB,GAAG,aAAa;GAChB,GAAG,aAAa;GAChB;;AAGF,QAAO,EAAE;;;;;AAMV,SAAS,iBAA2B;CACnC,MAAM,WAAW,QAAQ;CACzB,MAAM,OAAO,QAAQ,IAAI,QAAQ;AAEjC,KAAI,aAAa,SAChB,QAAO;EACN;EACA,GAAG,KAAK;EACR,GAAG,KAAK;EACR;AAGF,KAAI,aAAa,QAChB,QAAO;EACN;EACA;EACA,GAAG,KAAK;EACR;EACA;AAGF,KAAI,aAAa,SAAS;EACzB,MAAM,eAAe,QAAQ,IAAI,gBAAgB;EACjD,MAAM,eAAe,QAAQ,IAAI,gBAAgB;AACjD,SAAO;GACN,GAAG,aAAa;GAChB,GAAG,aAAa;GAChB,GAAG,aAAa;GAChB;;AAGF,QAAO,EAAE;;;;;;;AAQV,eAAsB,kBACrB,QACyB;CACzB,IAAI;CACJ,IAAI;CACJ,IAAI,WAA0B;AAE9B,KAAI,WAAW,WAAW;AACzB,UAAQ,gBAAgB;AACxB,oBAAkB;AAClB,aAAW;YACD,WAAW,UAAU;AAC/B,UAAQ,gBAAgB;AACxB,oBAAkB;AAClB,aAAW;OAEX,QAAO;AAGR,MAAK,MAAM,QAAQ,MAClB,KAAI,WAAW,KAAK,CACnB,QAAO;AAKT,KAAI,QAAQ,aAAa,YAAY,UAAU;EAC9C,MAAM,UAAU,MAAM,kBAAkB,SAAS;AACjD,MAAI,SAAS;GACZ,MAAM,UAAU,GAAG,QAAQ,8BAA8B;AACzD,OAAI,WAAW,QAAQ,CACtB,QAAO;;;AAKV,QAAO;;;;;AAMR,SAAS,mBAAmB,QAA+B;AAC1D,KAAI,WAAW,UACd,QAAO;AAER,KAAI,WAAW,SACd,QAAO;AAER,QAAO;;;;;;AAOR,eAAsB,iBAAiB,QAAkC;CACxE,MAAM,UAAU,MAAM,kBAAkB,OAAO;AAC/C,KAAI,CAAC,QACJ,QAAO;AAGR,KAAI;AACH,QAAM,MAAM,SAAS;GACpB;GACA;GACA;GACA,CAAC;AACF,SAAO;SACA;AACP,SAAO;;;;;;;AAQT,eAAsB,mBACrB,QACA,QACmB;CACnB,MAAM,SAAS,mBAAmB,OAAO;AACzC,KAAI,CAAC,OACJ,QAAO;CAMR,MAAM,MAAM,GAAG,OAAO,KAAK,kBAAkB,wBADvB,mBAAmB,OAAO;AAGhD,KAAI;EACH,MAAM,WAAW,QAAQ;AAEzB,MAAI,aAAa,SAEhB,OAAM,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,SAAS,KAAO,CAAC;WACpC,aAAa,QAEvB,OAAM,MAAM,YAAY,CAAC,IAAI,EAAE,EAAE,SAAS,KAAO,CAAC;WACxC,aAAa,QAEvB,OAAM,MAAM,OAAO;GAAC;GAAM;GAAS;GAAI;GAAI,EAAE,EAAE,SAAS,KAAO,CAAC;MAEhE,QAAO;AAGR,SAAO;SACA;AACP,SAAO;;;;;;AAOT,SAAgB,cAAc,QAAyB;AACtD,QAAO,WAAW,aAAa,WAAW"}
@@ -3,12 +3,9 @@ import { Editor, InstallStatus } from "./types.js";
3
3
  //#region src/lib/install.d.ts
4
4
 
5
5
  /**
6
- * Installs Neon's MCP Server
7
- * - Cursor: Global config
8
- * - VS Code: Global config (preferred) or workspace config (fallback)
9
- * - Claude CLI: Global config
6
+ * Installs Neon's Local Connect extension or MCP Server for specific editors
10
7
  */
11
- declare function installMCPServer(homeDir: string, workspaceDir: string, selectedEditors: Editor[]): Promise<Map<Editor, InstallStatus>>;
8
+ declare function installNeon(homeDir: string, workspaceDir: string, selectedEditors: Editor[]): Promise<Map<Editor, InstallStatus>>;
12
9
  //#endregion
13
- export { installMCPServer };
10
+ export { installNeon };
14
11
  //# sourceMappingURL=install.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":[],"mappings":";;;;;;AAmFA;;;;AAIuB,iBAJD,gBAAA,CAIC,OAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,eAAA,EADL,MACK,EAAA,CAAA,EAApB,OAAoB,CAAZ,GAAY,CAAR,MAAQ,EAAA,aAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"install.d.ts","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":[],"mappings":";;;;;;AA2FA;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,40 +1,41 @@
1
+ import { configureExtension, installExtension, usesExtension } from "./extension.js";
1
2
  import { createApiKeyFromNeonctl, ensureNeonctlAuth } from "./auth.js";
2
3
  import { getMCPConfig, writeMCPConfig } from "./mcp-config.js";
3
4
  import { confirm, isCancel, log, spinner } from "@clack/prompts";
4
5
 
5
6
  //#region src/lib/install.ts
6
7
  /**
7
- * Installs Neon's MCP Server for a specific editor
8
- * - Cursor: Global config
9
- * - VS Code: Global config (preferred) or workspace config (fallback)
10
- * - Claude CLI: Global config
8
+ * Checks if an editor needs MCP configuration
9
+ * Returns true if configuration is needed, false otherwise
11
10
  */
12
- async function installMCPServerForEditor(homeDir, workspaceDir, editor, apiKey) {
13
- const { config, configPath } = getMCPConfig(homeDir, workspaceDir, editor);
11
+ async function shouldConfigureMCP(homeDir, workspaceDir, editor) {
12
+ const { config } = getMCPConfig(homeDir, workspaceDir, editor);
14
13
  const serverKey = editor === "VS Code" ? config.servers : config.mcpServers;
15
14
  if (Boolean(serverKey?.Neon)) {
16
15
  const response = await confirm({
17
16
  message: `Neon MCP Server is already configured for ${editor}. Would you like to reconfigure it? (Y/n)`,
18
17
  initialValue: true
19
18
  });
20
- if (isCancel(response)) return "failed";
19
+ if (isCancel(response)) return false;
21
20
  if (!response) {
22
- log.info(`Keeping existing configuration for ${editor}.`);
23
- return "success";
21
+ log.info(`Keeping existing MCP server configuration for ${editor}.`);
22
+ return false;
24
23
  }
25
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);
26
32
  const neonServerConfig = {
27
33
  type: "http",
28
34
  url: "https://mcp.neon.tech/mcp",
29
35
  headers: { Authorization: `Bearer ${apiKey}` }
30
36
  };
31
- if (editor === "VS Code") {
32
- if (!config.servers) config.servers = {};
33
- config.servers.Neon = neonServerConfig;
34
- } else {
35
- if (!config.mcpServers) config.mcpServers = {};
36
- config.mcpServers.Neon = neonServerConfig;
37
- }
37
+ if (!config.mcpServers) config.mcpServers = {};
38
+ config.mcpServers.Neon = neonServerConfig;
38
39
  try {
39
40
  writeMCPConfig(configPath, config);
40
41
  return "success";
@@ -44,18 +45,26 @@ async function installMCPServerForEditor(homeDir, workspaceDir, editor, apiKey)
44
45
  }
45
46
  }
46
47
  /**
47
- * Installs Neon's MCP Server
48
- * - Cursor: Global config
49
- * - VS Code: Global config (preferred) or workspace config (fallback)
50
- * - Claude CLI: Global config
48
+ * Installs Neon's Local Connect extension or MCP Server for specific editors
51
49
  */
52
- async function installMCPServer(homeDir, workspaceDir, selectedEditors) {
50
+ async function installNeon(homeDir, workspaceDir, selectedEditors) {
53
51
  const results = /* @__PURE__ */ new Map();
52
+ const extensionEditors = selectedEditors.filter(usesExtension);
53
+ 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;
54
63
  const authSpinner = spinner();
55
64
  authSpinner.start("Authenticating...");
56
65
  if (!await ensureNeonctlAuth()) {
57
66
  authSpinner.stop("Authentication failed");
58
- for (const editor of selectedEditors) results.set(editor, "failed");
67
+ for (const editor of [...extensionsToConfigure, ...mcpToConfigure]) results.set(editor, "failed");
59
68
  return results;
60
69
  }
61
70
  authSpinner.stop("Authentication successful ✓");
@@ -63,10 +72,18 @@ async function installMCPServer(homeDir, workspaceDir, selectedEditors) {
63
72
  if (!apiKey) {
64
73
  log.error("Could not create API key after authentication.");
65
74
  log.info("You can manually create one at: https://console.neon.tech/app/settings/api-keys");
66
- for (const editor of selectedEditors) results.set(editor, "failed");
75
+ for (const editor of [...extensionsToConfigure, ...mcpToConfigure]) results.set(editor, "failed");
67
76
  return results;
68
77
  }
69
- for (const editor of selectedEditors) {
78
+ for (const editor of extensionsToConfigure) {
79
+ if (!await installExtension(editor)) {
80
+ results.set(editor, "failed");
81
+ continue;
82
+ }
83
+ if (await configureExtension(editor, apiKey)) results.set(editor, "success");
84
+ else results.set(editor, "success");
85
+ }
86
+ for (const editor of mcpToConfigure) {
70
87
  const status = await installMCPServerForEditor(homeDir, workspaceDir, editor, apiKey);
71
88
  results.set(editor, status);
72
89
  }
@@ -74,5 +91,5 @@ async function installMCPServer(homeDir, workspaceDir, selectedEditors) {
74
91
  }
75
92
 
76
93
  //#endregion
77
- export { installMCPServer };
94
+ export { installNeon };
78
95
  //# sourceMappingURL=install.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"install.js","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":["import { confirm, isCancel, log, spinner } from \"@clack/prompts\";\nimport { createApiKeyFromNeonctl, ensureNeonctlAuth } from \"./auth.js\";\nimport { getMCPConfig, writeMCPConfig } from \"./mcp-config.js\";\nimport type { Editor, InstallStatus } from \"./types.js\";\n\n/**\n * Installs Neon's MCP Server for a specific editor\n * - Cursor: Global config\n * - VS Code: Global config (preferred) or workspace config (fallback)\n * - Claude CLI: Global config\n */\nasync function installMCPServerForEditor(\n\thomeDir: string,\n\tworkspaceDir: string,\n\teditor: Editor,\n\tapiKey: string,\n): Promise<InstallStatus> {\n\tconst { config, configPath } = getMCPConfig(homeDir, workspaceDir, editor);\n\n\t// Check if already configured\n\tconst serverKey = editor === \"VS Code\" ? config.servers : config.mcpServers;\n\tconst alreadyConfigured = Boolean(serverKey?.Neon);\n\n\tif (alreadyConfigured) {\n\t\tconst response = await confirm({\n\t\t\tmessage: `Neon MCP Server is already configured for ${editor}. Would you like to reconfigure it? (Y/n)`,\n\t\t\tinitialValue: true,\n\t\t});\n\n\t\tif (isCancel(response)) {\n\t\t\treturn \"failed\";\n\t\t}\n\n\t\tconst shouldReconfigure = response as boolean;\n\n\t\tif (!shouldReconfigure) {\n\t\t\tlog.info(`Keeping existing configuration for ${editor}.`);\n\t\t\treturn \"success\";\n\t\t}\n\t}\n\n\t// Configure Neon MCP Server\n\t// Using remote MCP server with API key authentication\n\t// Ref: https://neon.com/docs/ai/neon-mcp-server#api-key-based-authentication\n\tconst neonServerConfig = {\n\t\ttype: \"http\",\n\t\turl: \"https://mcp.neon.tech/mcp\",\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t},\n\t};\n\n\t// VS Code uses \"servers\" key, Cursor and Claude CLI use \"mcpServers\" key\n\tif (editor === \"VS Code\") {\n\t\tif (!config.servers) {\n\t\t\tconfig.servers = {};\n\t\t}\n\t\tconfig.servers.Neon = neonServerConfig;\n\t} else {\n\t\tif (!config.mcpServers) {\n\t\t\tconfig.mcpServers = {};\n\t\t}\n\t\tconfig.mcpServers.Neon = neonServerConfig;\n\t}\n\n\t// Write configuration\n\ttry {\n\t\twriteMCPConfig(configPath, config);\n\t\treturn \"success\";\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Failed to write configuration for ${editor}: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn \"failed\";\n\t}\n}\n\n/**\n * Installs Neon's MCP Server\n * - Cursor: Global config\n * - VS Code: Global config (preferred) or workspace config (fallback)\n * - Claude CLI: Global config\n */\nexport async function installMCPServer(\n\thomeDir: string,\n\tworkspaceDir: string,\n\tselectedEditors: Editor[],\n): Promise<Map<Editor, InstallStatus>> {\n\tconst results = new Map<Editor, InstallStatus>();\n\n\t// Ensure authentication (will trigger OAuth if needed)\n\tconst authSpinner = spinner();\n\tauthSpinner.start(\"Authenticating...\");\n\n\tconst authSuccess = await ensureNeonctlAuth();\n\n\tif (!authSuccess) {\n\t\tauthSpinner.stop(\"Authentication failed\");\n\t\t// Mark all editors as failed due to auth failure\n\t\tfor (const editor of selectedEditors) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn results;\n\t}\n\n\tauthSpinner.stop(\"Authentication successful ✓\");\n\n\t// Create API key using the OAuth token\n\tconst apiKey = await createApiKeyFromNeonctl();\n\n\tif (!apiKey) {\n\t\tlog.error(\"Could not create API key after authentication.\");\n\t\tlog.info(\n\t\t\t\"You can manually create one at: https://console.neon.tech/app/settings/api-keys\",\n\t\t);\n\t\t// Mark all editors as failed due to API key creation failure\n\t\tfor (const editor of selectedEditors) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn results;\n\t}\n\n\t// Install MCP server for each selected editor\n\tfor (const editor of selectedEditors) {\n\t\tconst status = await installMCPServerForEditor(\n\t\t\thomeDir,\n\t\t\tworkspaceDir,\n\t\t\teditor,\n\t\t\tapiKey,\n\t\t);\n\t\tresults.set(editor, status);\n\t}\n\n\treturn results;\n}\n"],"mappings":";;;;;;;;;;;AAWA,eAAe,0BACd,SACA,cACA,QACA,QACyB;CACzB,MAAM,EAAE,QAAQ,eAAe,aAAa,SAAS,cAAc,OAAO;CAG1E,MAAM,YAAY,WAAW,YAAY,OAAO,UAAU,OAAO;AAGjE,KAF0B,QAAQ,WAAW,KAAK,EAE3B;EACtB,MAAM,WAAW,MAAM,QAAQ;GAC9B,SAAS,6CAA6C,OAAO;GAC7D,cAAc;GACd,CAAC;AAEF,MAAI,SAAS,SAAS,CACrB,QAAO;AAKR,MAAI,CAFsB,UAEF;AACvB,OAAI,KAAK,sCAAsC,OAAO,GAAG;AACzD,UAAO;;;CAOT,MAAM,mBAAmB;EACxB,MAAM;EACN,KAAK;EACL,SAAS,EACR,eAAe,UAAU,UACzB;EACD;AAGD,KAAI,WAAW,WAAW;AACzB,MAAI,CAAC,OAAO,QACX,QAAO,UAAU,EAAE;AAEpB,SAAO,QAAQ,OAAO;QAChB;AACN,MAAI,CAAC,OAAO,WACX,QAAO,aAAa,EAAE;AAEvB,SAAO,WAAW,OAAO;;AAI1B,KAAI;AACH,iBAAe,YAAY,OAAO;AAClC,SAAO;UACC,OAAO;AACf,MAAI,MACH,qCAAqC,OAAO,IAAI,iBAAiB,QAAQ,MAAM,UAAU,kBACzF;AACD,SAAO;;;;;;;;;AAUT,eAAsB,iBACrB,SACA,cACA,iBACsC;CACtC,MAAM,0BAAU,IAAI,KAA4B;CAGhD,MAAM,cAAc,SAAS;AAC7B,aAAY,MAAM,oBAAoB;AAItC,KAAI,CAFgB,MAAM,mBAAmB,EAE3B;AACjB,cAAY,KAAK,wBAAwB;AAEzC,OAAK,MAAM,UAAU,gBACpB,SAAQ,IAAI,QAAQ,SAAS;AAE9B,SAAO;;AAGR,aAAY,KAAK,8BAA8B;CAG/C,MAAM,SAAS,MAAM,yBAAyB;AAE9C,KAAI,CAAC,QAAQ;AACZ,MAAI,MAAM,iDAAiD;AAC3D,MAAI,KACH,kFACA;AAED,OAAK,MAAM,UAAU,gBACpB,SAAQ,IAAI,QAAQ,SAAS;AAE9B,SAAO;;AAIR,MAAK,MAAM,UAAU,iBAAiB;EACrC,MAAM,SAAS,MAAM,0BACpB,SACA,cACA,QACA,OACA;AACD,UAAQ,IAAI,QAAQ,OAAO;;AAG5B,QAAO"}
1
+ {"version":3,"file":"install.js","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":["import { confirm, isCancel, log, spinner } from \"@clack/prompts\";\nimport { createApiKeyFromNeonctl, ensureNeonctlAuth } from \"./auth.js\";\nimport {\n\tconfigureExtension,\n\tinstallExtension,\n\tusesExtension,\n} from \"./extension.js\";\nimport { getMCPConfig, writeMCPConfig } from \"./mcp-config.js\";\nimport type { Editor, InstallStatus } from \"./types.js\";\n\n/**\n * Checks if an editor needs MCP configuration\n * Returns true if configuration is needed, false otherwise\n */\nasync function shouldConfigureMCP(\n\thomeDir: string,\n\tworkspaceDir: string,\n\teditor: Editor,\n): Promise<boolean> {\n\tconst { config } = getMCPConfig(homeDir, workspaceDir, editor);\n\n\t// Check if already configured\n\tconst serverKey = editor === \"VS Code\" ? config.servers : config.mcpServers;\n\tconst alreadyConfigured = Boolean(serverKey?.Neon);\n\n\tif (alreadyConfigured) {\n\t\tconst response = await confirm({\n\t\t\tmessage: `Neon MCP Server is already configured for ${editor}. Would you like to reconfigure it? (Y/n)`,\n\t\t\tinitialValue: true,\n\t\t});\n\n\t\tif (isCancel(response)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst shouldReconfigure = response as boolean;\n\n\t\tif (!shouldReconfigure) {\n\t\t\tlog.info(\n\t\t\t\t`Keeping existing MCP server configuration for ${editor}.`,\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/**\n * Installs Neon's MCP Server for specific editors\n */\nasync function installMCPServerForEditor(\n\thomeDir: string,\n\tworkspaceDir: string,\n\teditor: Editor,\n\tapiKey: string,\n): Promise<InstallStatus> {\n\tconst { config, configPath } = getMCPConfig(homeDir, workspaceDir, editor);\n\n\t// Configure Neon MCP Server\n\t// Using remote MCP server with API key authentication\n\t// Ref: https://neon.com/docs/ai/neon-mcp-server#api-key-based-authentication\n\tconst neonServerConfig = {\n\t\ttype: \"http\",\n\t\turl: \"https://mcp.neon.tech/mcp\",\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t},\n\t};\n\n\t// Claude CLI uses \"mcpServers\" key\n\tif (!config.mcpServers) {\n\t\tconfig.mcpServers = {};\n\t}\n\tconfig.mcpServers.Neon = neonServerConfig;\n\n\t// Write configuration\n\ttry {\n\t\twriteMCPConfig(configPath, config);\n\t\treturn \"success\";\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Failed to write configuration for ${editor}: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn \"failed\";\n\t}\n}\n\n/**\n * Installs Neon's Local Connect extension or MCP Server for specific editors\n */\nexport async function installNeon(\n\thomeDir: string,\n\tworkspaceDir: string,\n\tselectedEditors: Editor[],\n): Promise<Map<Editor, InstallStatus>> {\n\tconst results = new Map<Editor, InstallStatus>();\n\n\tconst extensionEditors = selectedEditors.filter(usesExtension);\n\tconst mcpEditors = selectedEditors.filter((e) => !usesExtension(e));\n\n\t// Check which MCP editors need configuration\n\tconst mcpEditorsToConfigureMap = new Map<Editor, boolean>();\n\tfor (const editor of mcpEditors) {\n\t\tconst needsConfig = await shouldConfigureMCP(\n\t\t\thomeDir,\n\t\t\tworkspaceDir,\n\t\t\teditor,\n\t\t);\n\t\tmcpEditorsToConfigureMap.set(editor, needsConfig);\n\n\t\tif (!needsConfig) {\n\t\t\tresults.set(editor, \"success\");\n\t\t}\n\t}\n\n\tconst mcpToConfigure = mcpEditors.filter(\n\t\t(editor) => mcpEditorsToConfigureMap.get(editor) === true,\n\t);\n\n\t// Extension editors always get processed (silent installation)\n\tconst extensionsToConfigure = extensionEditors;\n\n\t// If nothing needs configuration, return early\n\tif (extensionsToConfigure.length === 0 && mcpToConfigure.length === 0) {\n\t\treturn results;\n\t}\n\n\tconst authSpinner = spinner();\n\tauthSpinner.start(\"Authenticating...\");\n\n\tconst authSuccess = await ensureNeonctlAuth();\n\n\tif (!authSuccess) {\n\t\tauthSpinner.stop(\"Authentication failed\");\n\t\t// Mark all editors that need configuration as failed\n\t\tfor (const editor of [...extensionsToConfigure, ...mcpToConfigure]) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn results;\n\t}\n\n\tauthSpinner.stop(\"Authentication successful ✓\");\n\n\t// Create API key using the OAuth token\n\tconst apiKey = await createApiKeyFromNeonctl();\n\n\tif (!apiKey) {\n\t\tlog.error(\"Could not create API key after authentication.\");\n\t\tlog.info(\n\t\t\t\"You can manually create one at: https://console.neon.tech/app/settings/api-keys\",\n\t\t);\n\t\t// Mark all editors that need configuration as failed\n\t\tfor (const editor of [...extensionsToConfigure, ...mcpToConfigure]) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn results;\n\t}\n\n\t// Install and configure extension for Cursor/VS Code (silently)\n\tfor (const editor of extensionsToConfigure) {\n\t\tconst installSuccess = await installExtension(editor);\n\n\t\tif (!installSuccess) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Configure the extension with the API key\n\t\tconst configSuccess = await configureExtension(editor, apiKey);\n\n\t\tif (configSuccess) {\n\t\t\tresults.set(editor, \"success\");\n\t\t} else {\n\t\t\t// Extension installed but auth failed but user can manually configure later\n\t\t\tresults.set(editor, \"success\");\n\t\t}\n\t}\n\n\tfor (const editor of mcpToConfigure) {\n\t\tconst status = await installMCPServerForEditor(\n\t\t\thomeDir,\n\t\t\tworkspaceDir,\n\t\t\teditor,\n\t\t\tapiKey,\n\t\t);\n\t\tresults.set(editor, status);\n\t}\n\n\treturn results;\n}\n"],"mappings":";;;;;;;;;;AAcA,eAAe,mBACd,SACA,cACA,QACmB;CACnB,MAAM,EAAE,WAAW,aAAa,SAAS,cAAc,OAAO;CAG9D,MAAM,YAAY,WAAW,YAAY,OAAO,UAAU,OAAO;AAGjE,KAF0B,QAAQ,WAAW,KAAK,EAE3B;EACtB,MAAM,WAAW,MAAM,QAAQ;GAC9B,SAAS,6CAA6C,OAAO;GAC7D,cAAc;GACd,CAAC;AAEF,MAAI,SAAS,SAAS,CACrB,QAAO;AAKR,MAAI,CAFsB,UAEF;AACvB,OAAI,KACH,iDAAiD,OAAO,GACxD;AACD,UAAO;;;AAIT,QAAO;;;;;AAMR,eAAe,0BACd,SACA,cACA,QACA,QACyB;CACzB,MAAM,EAAE,QAAQ,eAAe,aAAa,SAAS,cAAc,OAAO;CAK1E,MAAM,mBAAmB;EACxB,MAAM;EACN,KAAK;EACL,SAAS,EACR,eAAe,UAAU,UACzB;EACD;AAGD,KAAI,CAAC,OAAO,WACX,QAAO,aAAa,EAAE;AAEvB,QAAO,WAAW,OAAO;AAGzB,KAAI;AACH,iBAAe,YAAY,OAAO;AAClC,SAAO;UACC,OAAO;AACf,MAAI,MACH,qCAAqC,OAAO,IAAI,iBAAiB,QAAQ,MAAM,UAAU,kBACzF;AACD,SAAO;;;;;;AAOT,eAAsB,YACrB,SACA,cACA,iBACsC;CACtC,MAAM,0BAAU,IAAI,KAA4B;CAEhD,MAAM,mBAAmB,gBAAgB,OAAO,cAAc;CAC9D,MAAM,aAAa,gBAAgB,QAAQ,MAAM,CAAC,cAAc,EAAE,CAAC;CAGnE,MAAM,2CAA2B,IAAI,KAAsB;AAC3D,MAAK,MAAM,UAAU,YAAY;EAChC,MAAM,cAAc,MAAM,mBACzB,SACA,cACA,OACA;AACD,2BAAyB,IAAI,QAAQ,YAAY;AAEjD,MAAI,CAAC,YACJ,SAAQ,IAAI,QAAQ,UAAU;;CAIhC,MAAM,iBAAiB,WAAW,QAChC,WAAW,yBAAyB,IAAI,OAAO,KAAK,KACrD;CAGD,MAAM,wBAAwB;AAG9B,KAAI,sBAAsB,WAAW,KAAK,eAAe,WAAW,EACnE,QAAO;CAGR,MAAM,cAAc,SAAS;AAC7B,aAAY,MAAM,oBAAoB;AAItC,KAAI,CAFgB,MAAM,mBAAmB,EAE3B;AACjB,cAAY,KAAK,wBAAwB;AAEzC,OAAK,MAAM,UAAU,CAAC,GAAG,uBAAuB,GAAG,eAAe,CACjE,SAAQ,IAAI,QAAQ,SAAS;AAE9B,SAAO;;AAGR,aAAY,KAAK,8BAA8B;CAG/C,MAAM,SAAS,MAAM,yBAAyB;AAE9C,KAAI,CAAC,QAAQ;AACZ,MAAI,MAAM,iDAAiD;AAC3D,MAAI,KACH,kFACA;AAED,OAAK,MAAM,UAAU,CAAC,GAAG,uBAAuB,GAAG,eAAe,CACjE,SAAQ,IAAI,QAAQ,SAAS;AAE9B,SAAO;;AAIR,MAAK,MAAM,UAAU,uBAAuB;AAG3C,MAAI,CAFmB,MAAM,iBAAiB,OAAO,EAEhC;AACpB,WAAQ,IAAI,QAAQ,SAAS;AAC7B;;AAMD,MAFsB,MAAM,mBAAmB,QAAQ,OAAO,CAG7D,SAAQ,IAAI,QAAQ,UAAU;MAG9B,SAAQ,IAAI,QAAQ,UAAU;;AAIhC,MAAK,MAAM,UAAU,gBAAgB;EACpC,MAAM,SAAS,MAAM,0BACpB,SACA,cACA,QACA,OACA;AACD,UAAQ,IAAI,QAAQ,OAAO;;AAG5B,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-config.js","names":["mcpConfigPath: string"],"sources":["../../src/lib/mcp-config.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { getVSCodeGlobalConfigDir } from \"./editors.js\";\nimport type { Editor, MCPConfig } from \"./types.js\";\n\n/**\n * Gets or creates the MCP configuration for a specific editor\n * - Cursor: Global config at ~/.cursor/mcp.json\n * - VS Code: Try global config first, then workspace\n * - Claude CLI: Global config at ~/.claude.json\n */\nexport function getMCPConfig(\n\thomeDir: string,\n\tworkspaceDir: string,\n\teditor: Editor,\n): { config: MCPConfig; configPath: string } {\n\tlet mcpConfigPath: string;\n\n\tif (editor === \"VS Code\") {\n\t\t// Try global config first\n\t\tconst vscodeGlobalDir = getVSCodeGlobalConfigDir(homeDir);\n\t\tif (vscodeGlobalDir && existsSync(vscodeGlobalDir)) {\n\t\t\tmcpConfigPath = resolve(vscodeGlobalDir, \"mcp.json\");\n\t\t} else {\n\t\t\t// Fall back to workspace\n\t\t\tmcpConfigPath = resolve(workspaceDir, \".vscode\", \"mcp.json\");\n\t\t}\n\t} else if (editor === \"Claude CLI\") {\n\t\t// Claude CLI uses ~/.claude.json\n\t\tmcpConfigPath = resolve(homeDir, \".claude.json\");\n\t} else {\n\t\t// Cursor uses ~/.cursor/mcp.json\n\t\tmcpConfigPath = resolve(homeDir, \".cursor\", \"mcp.json\");\n\t}\n\n\tif (existsSync(mcpConfigPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(mcpConfigPath, \"utf-8\");\n\t\t\treturn {\n\t\t\t\tconfig: JSON.parse(content),\n\t\t\t\tconfigPath: mcpConfigPath,\n\t\t\t};\n\t\t} catch (_error) {\n\t\t\treturn {\n\t\t\t\tconfig:\n\t\t\t\t\teditor === \"VS Code\" ? { servers: {} } : { mcpServers: {} },\n\t\t\t\tconfigPath: mcpConfigPath,\n\t\t\t};\n\t\t}\n\t}\n\n\treturn {\n\t\tconfig: editor === \"VS Code\" ? { servers: {} } : { mcpServers: {} },\n\t\tconfigPath: mcpConfigPath,\n\t};\n}\n\n/**\n * Writes the MCP configuration to the appropriate location\n * - Cursor: Global config at ~/.cursor/mcp.json\n * - VS Code: Global config (preferred) or workspace config (fallback)\n * - Claude CLI: Global config at ~/.claude.json\n */\nexport function writeMCPConfig(configPath: string, config: MCPConfig): void {\n\tconst editorDir = dirname(configPath);\n\n\tif (!existsSync(editorDir)) {\n\t\tmkdirSync(editorDir, { recursive: true });\n\t}\n\n\twriteFileSync(configPath, JSON.stringify(config, null, 2), \"utf-8\");\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAgB,aACf,SACA,cACA,QAC4C;CAC5C,IAAIA;AAEJ,KAAI,WAAW,WAAW;EAEzB,MAAM,kBAAkB,yBAAyB,QAAQ;AACzD,MAAI,mBAAmB,WAAW,gBAAgB,CACjD,iBAAgB,QAAQ,iBAAiB,WAAW;MAGpD,iBAAgB,QAAQ,cAAc,WAAW,WAAW;YAEnD,WAAW,aAErB,iBAAgB,QAAQ,SAAS,eAAe;KAGhD,iBAAgB,QAAQ,SAAS,WAAW,WAAW;AAGxD,KAAI,WAAW,cAAc,CAC5B,KAAI;EACH,MAAM,UAAU,aAAa,eAAe,QAAQ;AACpD,SAAO;GACN,QAAQ,KAAK,MAAM,QAAQ;GAC3B,YAAY;GACZ;UACO,QAAQ;AAChB,SAAO;GACN,QACC,WAAW,YAAY,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE;GAC5D,YAAY;GACZ;;AAIH,QAAO;EACN,QAAQ,WAAW,YAAY,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE;EACnE,YAAY;EACZ;;;;;;;;AASF,SAAgB,eAAe,YAAoB,QAAyB;CAC3E,MAAM,YAAY,QAAQ,WAAW;AAErC,KAAI,CAAC,WAAW,UAAU,CACzB,WAAU,WAAW,EAAE,WAAW,MAAM,CAAC;AAG1C,eAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,EAAE,EAAE,QAAQ"}
1
+ {"version":3,"file":"mcp-config.js","names":[],"sources":["../../src/lib/mcp-config.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { getVSCodeGlobalConfigDir } from \"./editors.js\";\nimport type { Editor, MCPConfig } from \"./types.js\";\n\n/**\n * Gets or creates the MCP configuration for a specific editor\n * - Cursor: Global config at ~/.cursor/mcp.json\n * - VS Code: Try global config first, then workspace\n * - Claude CLI: Global config at ~/.claude.json\n */\nexport function getMCPConfig(\n\thomeDir: string,\n\tworkspaceDir: string,\n\teditor: Editor,\n): { config: MCPConfig; configPath: string } {\n\tlet mcpConfigPath: string;\n\n\tif (editor === \"VS Code\") {\n\t\t// Try global config first\n\t\tconst vscodeGlobalDir = getVSCodeGlobalConfigDir(homeDir);\n\t\tif (vscodeGlobalDir && existsSync(vscodeGlobalDir)) {\n\t\t\tmcpConfigPath = resolve(vscodeGlobalDir, \"mcp.json\");\n\t\t} else {\n\t\t\t// Fall back to workspace\n\t\t\tmcpConfigPath = resolve(workspaceDir, \".vscode\", \"mcp.json\");\n\t\t}\n\t} else if (editor === \"Claude CLI\") {\n\t\t// Claude CLI uses ~/.claude.json\n\t\tmcpConfigPath = resolve(homeDir, \".claude.json\");\n\t} else {\n\t\t// Cursor uses ~/.cursor/mcp.json\n\t\tmcpConfigPath = resolve(homeDir, \".cursor\", \"mcp.json\");\n\t}\n\n\tif (existsSync(mcpConfigPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(mcpConfigPath, \"utf-8\");\n\t\t\treturn {\n\t\t\t\tconfig: JSON.parse(content),\n\t\t\t\tconfigPath: mcpConfigPath,\n\t\t\t};\n\t\t} catch (_error) {\n\t\t\treturn {\n\t\t\t\tconfig:\n\t\t\t\t\teditor === \"VS Code\" ? { servers: {} } : { mcpServers: {} },\n\t\t\t\tconfigPath: mcpConfigPath,\n\t\t\t};\n\t\t}\n\t}\n\n\treturn {\n\t\tconfig: editor === \"VS Code\" ? { servers: {} } : { mcpServers: {} },\n\t\tconfigPath: mcpConfigPath,\n\t};\n}\n\n/**\n * Writes the MCP configuration to the appropriate location\n * - Cursor: Global config at ~/.cursor/mcp.json\n * - VS Code: Global config (preferred) or workspace config (fallback)\n * - Claude CLI: Global config at ~/.claude.json\n */\nexport function writeMCPConfig(configPath: string, config: MCPConfig): void {\n\tconst editorDir = dirname(configPath);\n\n\tif (!existsSync(editorDir)) {\n\t\tmkdirSync(editorDir, { recursive: true });\n\t}\n\n\twriteFileSync(configPath, JSON.stringify(config, null, 2), \"utf-8\");\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAgB,aACf,SACA,cACA,QAC4C;CAC5C,IAAI;AAEJ,KAAI,WAAW,WAAW;EAEzB,MAAM,kBAAkB,yBAAyB,QAAQ;AACzD,MAAI,mBAAmB,WAAW,gBAAgB,CACjD,iBAAgB,QAAQ,iBAAiB,WAAW;MAGpD,iBAAgB,QAAQ,cAAc,WAAW,WAAW;YAEnD,WAAW,aAErB,iBAAgB,QAAQ,SAAS,eAAe;KAGhD,iBAAgB,QAAQ,SAAS,WAAW,WAAW;AAGxD,KAAI,WAAW,cAAc,CAC5B,KAAI;EACH,MAAM,UAAU,aAAa,eAAe,QAAQ;AACpD,SAAO;GACN,QAAQ,KAAK,MAAM,QAAQ;GAC3B,YAAY;GACZ;UACO,QAAQ;AAChB,SAAO;GACN,QACC,WAAW,YAAY,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE;GAC5D,YAAY;GACZ;;AAIH,QAAO;EACN,QAAQ,WAAW,YAAY,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE;EACnE,YAAY;EACZ;;;;;;;;AASF,SAAgB,eAAe,YAAoB,QAAyB;CAC3E,MAAM,YAAY,QAAQ,WAAW;AAErC,KAAI,CAAC,WAAW,UAAU,CACzB,WAAU,WAAW,EAAE,WAAW,MAAM,CAAC;AAG1C,eAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,EAAE,EAAE,QAAQ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon-init",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "Initialize Neon projects",
5
5
  "keywords": [
6
6
  "neon",
@@ -47,7 +47,7 @@
47
47
  "yoctocolors": "^2.1.2"
48
48
  },
49
49
  "engines": {
50
- "node": ">=20.19.0"
50
+ "node": "22"
51
51
  },
52
52
  "publishConfig": {
53
53
  "provenance": false