neon-init 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/cli.js +366 -30
  2. package/dist/cli.js.map +1 -1
  3. package/dist/index.d.ts +15 -3
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +207 -13
  6. package/dist/index.js.map +1 -1
  7. package/dist/interactive.d.ts +12 -0
  8. package/dist/interactive.d.ts.map +1 -0
  9. package/dist/interactive.js +495 -0
  10. package/dist/interactive.js.map +1 -0
  11. package/dist/lib/agents.d.ts +6 -1
  12. package/dist/lib/agents.d.ts.map +1 -1
  13. package/dist/lib/agents.js +62 -1
  14. package/dist/lib/agents.js.map +1 -1
  15. package/dist/lib/auth.d.ts +10 -3
  16. package/dist/lib/auth.d.ts.map +1 -1
  17. package/dist/lib/auth.js +19 -11
  18. package/dist/lib/auth.js.map +1 -1
  19. package/dist/lib/bootstrap.d.ts +30 -0
  20. package/dist/lib/bootstrap.d.ts.map +1 -0
  21. package/dist/lib/bootstrap.js +61 -0
  22. package/dist/lib/bootstrap.js.map +1 -0
  23. package/dist/lib/build-config.d.ts +5 -0
  24. package/dist/lib/build-config.d.ts.map +1 -0
  25. package/dist/lib/build-config.js +6 -0
  26. package/dist/lib/build-config.js.map +1 -0
  27. package/dist/lib/detect-agent.d.ts +22 -0
  28. package/dist/lib/detect-agent.d.ts.map +1 -0
  29. package/dist/lib/detect-agent.js +65 -0
  30. package/dist/lib/detect-agent.js.map +1 -0
  31. package/dist/lib/editors.d.ts.map +1 -1
  32. package/dist/lib/editors.js.map +1 -1
  33. package/dist/lib/extension.d.ts +11 -3
  34. package/dist/lib/extension.d.ts.map +1 -1
  35. package/dist/lib/extension.js +28 -7
  36. package/dist/lib/extension.js.map +1 -1
  37. package/dist/lib/inspect.d.ts +28 -0
  38. package/dist/lib/inspect.d.ts.map +1 -0
  39. package/dist/lib/inspect.js +190 -0
  40. package/dist/lib/inspect.js.map +1 -0
  41. package/dist/lib/install.d.ts +10 -4
  42. package/dist/lib/install.d.ts.map +1 -1
  43. package/dist/lib/install.js +37 -20
  44. package/dist/lib/install.js.map +1 -1
  45. package/dist/lib/neonctl.d.ts +32 -0
  46. package/dist/lib/neonctl.d.ts.map +1 -0
  47. package/dist/lib/neonctl.js +149 -0
  48. package/dist/lib/neonctl.js.map +1 -0
  49. package/dist/lib/phases/auth.d.ts +12 -0
  50. package/dist/lib/phases/auth.d.ts.map +1 -0
  51. package/dist/lib/phases/auth.js +188 -0
  52. package/dist/lib/phases/auth.js.map +1 -0
  53. package/dist/lib/phases/cleanup.d.ts +12 -0
  54. package/dist/lib/phases/cleanup.d.ts.map +1 -0
  55. package/dist/lib/phases/cleanup.js +29 -0
  56. package/dist/lib/phases/cleanup.js.map +1 -0
  57. package/dist/lib/phases/db.d.ts +17 -0
  58. package/dist/lib/phases/db.d.ts.map +1 -0
  59. package/dist/lib/phases/db.js +258 -0
  60. package/dist/lib/phases/db.js.map +1 -0
  61. package/dist/lib/phases/getting-started.d.ts +26 -0
  62. package/dist/lib/phases/getting-started.d.ts.map +1 -0
  63. package/dist/lib/phases/getting-started.js +195 -0
  64. package/dist/lib/phases/getting-started.js.map +1 -0
  65. package/dist/lib/phases/mcp.d.ts +15 -0
  66. package/dist/lib/phases/mcp.d.ts.map +1 -0
  67. package/dist/lib/phases/mcp.js +179 -0
  68. package/dist/lib/phases/mcp.js.map +1 -0
  69. package/dist/lib/phases/migrations.d.ts +14 -0
  70. package/dist/lib/phases/migrations.d.ts.map +1 -0
  71. package/dist/lib/phases/migrations.js +239 -0
  72. package/dist/lib/phases/migrations.js.map +1 -0
  73. package/dist/lib/phases/neon-auth.d.ts +13 -0
  74. package/dist/lib/phases/neon-auth.d.ts.map +1 -0
  75. package/dist/lib/phases/neon-auth.js +117 -0
  76. package/dist/lib/phases/neon-auth.js.map +1 -0
  77. package/dist/lib/phases/setup.d.ts +41 -0
  78. package/dist/lib/phases/setup.d.ts.map +1 -0
  79. package/dist/lib/phases/setup.js +689 -0
  80. package/dist/lib/phases/setup.js.map +1 -0
  81. package/dist/lib/phases/skills.d.ts +14 -0
  82. package/dist/lib/phases/skills.d.ts.map +1 -0
  83. package/dist/lib/phases/skills.js +80 -0
  84. package/dist/lib/phases/skills.js.map +1 -0
  85. package/dist/lib/phases/status.d.ts +10 -0
  86. package/dist/lib/phases/status.d.ts.map +1 -0
  87. package/dist/lib/phases/status.js +65 -0
  88. package/dist/lib/phases/status.js.map +1 -0
  89. package/dist/lib/resolve-context.d.ts +19 -0
  90. package/dist/lib/resolve-context.d.ts.map +1 -0
  91. package/dist/lib/resolve-context.js +112 -0
  92. package/dist/lib/resolve-context.js.map +1 -0
  93. package/dist/lib/route-command.d.ts +8 -0
  94. package/dist/lib/route-command.d.ts.map +1 -0
  95. package/dist/lib/route-command.js +195 -0
  96. package/dist/lib/route-command.js.map +1 -0
  97. package/dist/lib/skills.d.ts +20 -3
  98. package/dist/lib/skills.d.ts.map +1 -1
  99. package/dist/lib/skills.js +116 -12
  100. package/dist/lib/skills.js.map +1 -1
  101. package/dist/lib/types.d.ts +150 -1
  102. package/dist/lib/types.d.ts.map +1 -1
  103. package/dist/lib/vsix.d.ts +15 -0
  104. package/dist/lib/vsix.d.ts.map +1 -0
  105. package/dist/lib/vsix.js +91 -0
  106. package/dist/lib/vsix.js.map +1 -0
  107. package/dist/v2.d.ts +31 -0
  108. package/dist/v2.d.ts.map +1 -0
  109. package/dist/v2.js +147 -0
  110. package/dist/v2.js.map +1 -0
  111. package/package.json +7 -3
@@ -1 +1 @@
1
- {"version":3,"file":"agents.js","names":[],"sources":["../../src/lib/agents.ts"],"sourcesContent":["import type { Editor } from \"./types.js\";\n\nexport interface AgentConfig {\n\teditor: Editor;\n\taddMcpId: string;\n\thint: string;\n}\n\n/**\n * All agents that can be configured via neon-init.\n * Aligns with add-mcp's supported agents table.\n * https://github.com/neondatabase/add-mcp#supported-agents\n */\nexport const ALL_CONFIGURABLE_AGENTS: AgentConfig[] = [\n\t{\n\t\teditor: \"Cursor\",\n\t\taddMcpId: \"cursor\",\n\t\thint: \"Neon Local Connect extension\",\n\t},\n\t{\n\t\teditor: \"VS Code\",\n\t\taddMcpId: \"vscode\",\n\t\thint: \"Neon Local Connect extension\",\n\t},\n\t{ editor: \"Claude CLI\", addMcpId: \"claude-code\", hint: \"MCP Server\" },\n\t{\n\t\teditor: \"Claude Desktop\",\n\t\taddMcpId: \"claude-desktop\",\n\t\thint: \"MCP Server\",\n\t},\n\t{ editor: \"Codex\", addMcpId: \"codex\", hint: \"MCP Server\" },\n\t{ editor: \"OpenCode\", addMcpId: \"opencode\", hint: \"MCP Server\" },\n\t{ editor: \"Antigravity\", addMcpId: \"antigravity\", hint: \"MCP Server\" },\n\t{ editor: \"Cline\", addMcpId: \"cline\", hint: \"MCP Server\" },\n\t{ editor: \"Cline CLI\", addMcpId: \"cline-cli\", hint: \"MCP Server\" },\n\t{ editor: \"Gemini CLI\", addMcpId: \"gemini-cli\", hint: \"MCP Server\" },\n\t{\n\t\teditor: \"GitHub Copilot CLI\",\n\t\taddMcpId: \"github-copilot-cli\",\n\t\thint: \"MCP Server\",\n\t},\n\t{ editor: \"Goose\", addMcpId: \"goose\", hint: \"MCP Server\" },\n\t{ editor: \"MCPorter\", addMcpId: \"mcporter\", hint: \"MCP Server\" },\n\t{ editor: \"Zed\", addMcpId: \"zed\", hint: \"MCP Server\" },\n];\n\nexport function getAddMcpAgentId(editor: Editor): string {\n\tconst agent = ALL_CONFIGURABLE_AGENTS.find((a) => a.editor === editor);\n\tif (!agent) {\n\t\tthrow new Error(`No add-mcp agent ID found for editor: ${editor}`);\n\t}\n\treturn agent.addMcpId;\n}\n"],"mappings":";;;;;;AAaA,MAAa,0BAAyC;CACrD;EACC,QAAQ;EACR,UAAU;EACV,MAAM;EACN;CACD;EACC,QAAQ;EACR,UAAU;EACV,MAAM;EACN;CACD;EAAE,QAAQ;EAAc,UAAU;EAAe,MAAM;EAAc;CACrE;EACC,QAAQ;EACR,UAAU;EACV,MAAM;EACN;CACD;EAAE,QAAQ;EAAS,UAAU;EAAS,MAAM;EAAc;CAC1D;EAAE,QAAQ;EAAY,UAAU;EAAY,MAAM;EAAc;CAChE;EAAE,QAAQ;EAAe,UAAU;EAAe,MAAM;EAAc;CACtE;EAAE,QAAQ;EAAS,UAAU;EAAS,MAAM;EAAc;CAC1D;EAAE,QAAQ;EAAa,UAAU;EAAa,MAAM;EAAc;CAClE;EAAE,QAAQ;EAAc,UAAU;EAAc,MAAM;EAAc;CACpE;EACC,QAAQ;EACR,UAAU;EACV,MAAM;EACN;CACD;EAAE,QAAQ;EAAS,UAAU;EAAS,MAAM;EAAc;CAC1D;EAAE,QAAQ;EAAY,UAAU;EAAY,MAAM;EAAc;CAChE;EAAE,QAAQ;EAAO,UAAU;EAAO,MAAM;EAAc;CACtD;AAED,SAAgB,iBAAiB,QAAwB;CACxD,MAAM,QAAQ,wBAAwB,MAAM,MAAM,EAAE,WAAW,OAAO;AACtE,KAAI,CAAC,MACJ,OAAM,IAAI,MAAM,yCAAyC,SAAS;AAEnE,QAAO,MAAM"}
1
+ {"version":3,"file":"agents.js","names":[],"sources":["../../src/lib/agents.ts"],"sourcesContent":["import type { Editor } from \"./types.js\";\n\nexport interface AgentConfig {\n\teditor: Editor;\n\taddMcpId: string;\n\thint: string;\n}\n\n/**\n * All agents that can be configured via neon-init.\n * Aligns with add-mcp's supported agents table.\n * https://github.com/neondatabase/add-mcp#supported-agents\n */\nexport const ALL_CONFIGURABLE_AGENTS: AgentConfig[] = [\n\t{\n\t\teditor: \"Cursor\",\n\t\taddMcpId: \"cursor\",\n\t\thint: \"Neon Local Connect extension\",\n\t},\n\t{\n\t\teditor: \"VS Code\",\n\t\taddMcpId: \"vscode\",\n\t\thint: \"Neon Local Connect extension\",\n\t},\n\t{ editor: \"Claude CLI\", addMcpId: \"claude-code\", hint: \"MCP Server\" },\n\t{\n\t\teditor: \"Claude Desktop\",\n\t\taddMcpId: \"claude-desktop\",\n\t\thint: \"MCP Server\",\n\t},\n\t{ editor: \"Codex\", addMcpId: \"codex\", hint: \"MCP Server\" },\n\t{ editor: \"OpenCode\", addMcpId: \"opencode\", hint: \"MCP Server\" },\n\t{ editor: \"Antigravity\", addMcpId: \"antigravity\", hint: \"MCP Server\" },\n\t{ editor: \"Cline\", addMcpId: \"cline\", hint: \"MCP Server\" },\n\t{ editor: \"Cline CLI\", addMcpId: \"cline-cli\", hint: \"MCP Server\" },\n\t{ editor: \"Gemini CLI\", addMcpId: \"gemini-cli\", hint: \"MCP Server\" },\n\t{\n\t\teditor: \"GitHub Copilot CLI\",\n\t\taddMcpId: \"github-copilot-cli\",\n\t\thint: \"MCP Server\",\n\t},\n\t{ editor: \"Goose\", addMcpId: \"goose\", hint: \"MCP Server\" },\n\t{ editor: \"MCPorter\", addMcpId: \"mcporter\", hint: \"MCP Server\" },\n\t{ editor: \"Zed\", addMcpId: \"zed\", hint: \"MCP Server\" },\n];\n\nexport function getAddMcpAgentId(editor: Editor): string {\n\tconst agent = ALL_CONFIGURABLE_AGENTS.find((a) => a.editor === editor);\n\tif (!agent) {\n\t\tthrow new Error(`No add-mcp agent ID found for editor: ${editor}`);\n\t}\n\treturn agent.addMcpId;\n}\n\n/**\n * Maps a raw agent identifier (as reported by agents or passed via --agent)\n * to the add-mcp compatible agent ID.\n *\n * This handles aliases like \"copilot\" → \"vscode\", \"claude\" → \"claude-code\", etc.\n */\nconst AGENT_ALIAS_TO_MCP_ID: Record<string, string> = {\n\tcursor: \"cursor\",\n\tcopilot: \"vscode\",\n\t\"github-copilot\": \"vscode\",\n\t\"vs-code\": \"vscode\",\n\tvscode: \"vscode\",\n\tclaude: \"claude-code\",\n\t\"claude-code\": \"claude-code\",\n\t\"claude-desktop\": \"claude-desktop\",\n\tcodex: \"codex\",\n\topencode: \"opencode\",\n\tantigravity: \"antigravity\",\n\tcline: \"cline\",\n\t\"cline-cli\": \"cline-cli\",\n\t\"gemini-cli\": \"gemini-cli\",\n\tgemini: \"gemini-cli\",\n\tgoose: \"goose\",\n\twindsurf: \"windsurf\",\n\t\"github-copilot-cli\": \"github-copilot-cli\",\n\tmcporter: \"mcporter\",\n\tzed: \"zed\",\n};\n\nexport function resolveAddMcpAgentId(rawAgent: string): string {\n\tconst resolved = AGENT_ALIAS_TO_MCP_ID[rawAgent.toLowerCase()];\n\tif (!resolved) {\n\t\tthrow new Error(\n\t\t\t`Unknown agent: \"${rawAgent}\". Supported agents: ${Object.keys(AGENT_ALIAS_TO_MCP_ID).join(\", \")}`,\n\t\t);\n\t}\n\treturn resolved;\n}\n\n/**\n * Maps a raw agent identifier to the skills CLI agent name.\n */\nexport function getSkillsAgentName(agent: string): string {\n\tswitch (agent.toLowerCase()) {\n\t\tcase \"cursor\":\n\t\t\treturn \"cursor\";\n\t\tcase \"copilot\":\n\t\tcase \"vscode\":\n\t\tcase \"vs-code\":\n\t\tcase \"github-copilot\":\n\t\t\treturn \"github-copilot\";\n\t\tcase \"claude\":\n\t\tcase \"claude-code\":\n\t\t\treturn \"claude-code\";\n\t\tcase \"codex\":\n\t\t\treturn \"codex\";\n\t\tcase \"opencode\":\n\t\t\treturn \"opencode\";\n\t\tcase \"antigravity\":\n\t\t\treturn \"antigravity\";\n\t\tcase \"cline\":\n\t\t\treturn \"cline\";\n\t\tcase \"gemini-cli\":\n\t\t\treturn \"gemini-cli\";\n\t\tcase \"goose\":\n\t\t\treturn \"goose\";\n\t\tcase \"claude-desktop\":\n\t\t\treturn \"claude-code\";\n\t\tcase \"cline-cli\":\n\t\t\treturn \"cline\";\n\t\tcase \"gemini\":\n\t\t\treturn \"gemini-cli\";\n\t\tcase \"windsurf\":\n\t\t\treturn \"windsurf\";\n\t\tcase \"github-copilot-cli\":\n\t\t\treturn \"github-copilot\";\n\t\tcase \"mcporter\":\n\t\t\treturn \"mcporter\";\n\t\tcase \"zed\":\n\t\t\treturn \"zed\";\n\t\tdefault:\n\t\t\tthrow new Error(\n\t\t\t\t`Unknown agent for skills: \"${agent}\". Use a known agent identifier.`,\n\t\t\t);\n\t}\n}\n"],"mappings":";;;;;;AAaA,MAAa,0BAAyC;CACrD;EACC,QAAQ;EACR,UAAU;EACV,MAAM;CACP;CACA;EACC,QAAQ;EACR,UAAU;EACV,MAAM;CACP;CACA;EAAE,QAAQ;EAAc,UAAU;EAAe,MAAM;CAAa;CACpE;EACC,QAAQ;EACR,UAAU;EACV,MAAM;CACP;CACA;EAAE,QAAQ;EAAS,UAAU;EAAS,MAAM;CAAa;CACzD;EAAE,QAAQ;EAAY,UAAU;EAAY,MAAM;CAAa;CAC/D;EAAE,QAAQ;EAAe,UAAU;EAAe,MAAM;CAAa;CACrE;EAAE,QAAQ;EAAS,UAAU;EAAS,MAAM;CAAa;CACzD;EAAE,QAAQ;EAAa,UAAU;EAAa,MAAM;CAAa;CACjE;EAAE,QAAQ;EAAc,UAAU;EAAc,MAAM;CAAa;CACnE;EACC,QAAQ;EACR,UAAU;EACV,MAAM;CACP;CACA;EAAE,QAAQ;EAAS,UAAU;EAAS,MAAM;CAAa;CACzD;EAAE,QAAQ;EAAY,UAAU;EAAY,MAAM;CAAa;CAC/D;EAAE,QAAQ;EAAO,UAAU;EAAO,MAAM;CAAa;AACtD;AAEA,SAAgB,iBAAiB,QAAwB;CACxD,MAAM,QAAQ,wBAAwB,MAAM,MAAM,EAAE,WAAW,MAAM;CACrE,IAAI,CAAC,OACJ,MAAM,IAAI,MAAM,yCAAyC,QAAQ;CAElE,OAAO,MAAM;AACd;;;;;;;AAQA,MAAM,wBAAgD;CACrD,QAAQ;CACR,SAAS;CACT,kBAAkB;CAClB,WAAW;CACX,QAAQ;CACR,QAAQ;CACR,eAAe;CACf,kBAAkB;CAClB,OAAO;CACP,UAAU;CACV,aAAa;CACb,OAAO;CACP,aAAa;CACb,cAAc;CACd,QAAQ;CACR,OAAO;CACP,UAAU;CACV,sBAAsB;CACtB,UAAU;CACV,KAAK;AACN;AAEA,SAAgB,qBAAqB,UAA0B;CAC9D,MAAM,WAAW,sBAAsB,SAAS,YAAY;CAC5D,IAAI,CAAC,UACJ,MAAM,IAAI,MACT,mBAAmB,SAAS,uBAAuB,OAAO,KAAK,qBAAqB,CAAC,CAAC,KAAK,IAAI,GAChG;CAED,OAAO;AACR;;;;AAKA,SAAgB,mBAAmB,OAAuB;CACzD,QAAQ,MAAM,YAAY,GAA1B;EACC,KAAK,UACJ,OAAO;EACR,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,kBACJ,OAAO;EACR,KAAK;EACL,KAAK,eACJ,OAAO;EACR,KAAK,SACJ,OAAO;EACR,KAAK,YACJ,OAAO;EACR,KAAK,eACJ,OAAO;EACR,KAAK,SACJ,OAAO;EACR,KAAK,cACJ,OAAO;EACR,KAAK,SACJ,OAAO;EACR,KAAK,kBACJ,OAAO;EACR,KAAK,aACJ,OAAO;EACR,KAAK,UACJ,OAAO;EACR,KAAK,YACJ,OAAO;EACR,KAAK,sBACJ,OAAO;EACR,KAAK,YACJ,OAAO;EACR,KAAK,OACJ,OAAO;EACR,SACC,MAAM,IAAI,MACT,8BAA8B,MAAM,iCACrC;CACF;AACD"}
@@ -1,13 +1,20 @@
1
1
  //#region src/lib/auth.d.ts
2
+ interface AuthOptions {
3
+ json?: boolean;
4
+ }
2
5
  /**
3
6
  * Ensures neonctl is authenticated by running a command that triggers auth if needed
4
7
  * This will automatically start the OAuth flow if the user isn't already authenticated
5
8
  */
6
- declare function ensureNeonctlAuth(): Promise<boolean>;
9
+ declare function ensureNeonctlAuth(options?: AuthOptions): Promise<boolean>;
10
+ /**
11
+ * Checks whether neonctl has stored OAuth credentials.
12
+ */
13
+ declare function isAuthenticated(): Promise<boolean>;
7
14
  /**
8
15
  * Creates an API key using the Neon API with the OAuth token from neonctl
9
16
  */
10
- declare function createApiKeyFromNeonctl(): Promise<string | null>;
17
+ declare function createApiKeyFromNeonctl(options?: AuthOptions): Promise<string | null>;
11
18
  //#endregion
12
- export { createApiKeyFromNeonctl, ensureNeonctlAuth };
19
+ export { AuthOptions, createApiKeyFromNeonctl, ensureNeonctlAuth, isAuthenticated };
13
20
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","names":[],"sources":["../../src/lib/auth.ts"],"sourcesContent":[],"mappings":";;AASA;AAqDA;;iBArDsB,iBAAA,CAAA,GAAqB;;;;iBAqDrB,uBAAA,CAAA,GAA2B"}
1
+ {"version":3,"file":"auth.d.ts","names":[],"sources":["../../src/lib/auth.ts"],"mappings":";UAKiB,WAAA;EAAA,IAAA,CAAA,EAAA,OAAW;AAQ5B;;;;AAEU;AAkCY,iBApCA,iBAAA,CAoC0B,OAAA,CAAA,EAnCrC,WAmCqC,CAAA,EAlC7C,OAkC6C,CAAA,OAAA,CAAA;AA+BhD;;;AAEG,iBAjCmB,eAAA,CAAA,CAiCnB,EAjCsC,OAiCtC,CAAA,OAAA,CAAA;AAAO;;;iBAFY,uBAAA,WACX,cACR"}
package/dist/lib/auth.js CHANGED
@@ -1,20 +1,20 @@
1
- import { log } from "@clack/prompts";
2
1
  import { existsSync, readFileSync } from "node:fs";
3
2
  import { resolve } from "node:path";
3
+ import { log } from "@clack/prompts";
4
4
  import { execa } from "execa";
5
5
  //#region src/lib/auth.ts
6
6
  /**
7
7
  * Ensures neonctl is authenticated by running a command that triggers auth if needed
8
8
  * This will automatically start the OAuth flow if the user isn't already authenticated
9
9
  */
10
- async function ensureNeonctlAuth() {
10
+ async function ensureNeonctlAuth(options) {
11
+ const quiet = options?.json === true;
11
12
  if (await getNeonctlAccessToken()) return true;
12
13
  try {
13
14
  await execa("npx", [
14
15
  "-y",
15
16
  "neonctl",
16
- "me",
17
- "--no-analytics"
17
+ "me"
18
18
  ], {
19
19
  stdio: "inherit",
20
20
  env: {
@@ -25,12 +25,18 @@ async function ensureNeonctlAuth() {
25
25
  return true;
26
26
  } catch (error) {
27
27
  const msg = error instanceof Error ? error.message : "Unknown error";
28
- if (msg.includes("interactive auth") || msg.includes("CI")) log.error("Auth requires an interactive terminal. Run neon-init in your system terminal (outside the chat) to sign in.");
28
+ if (!quiet) if (msg.includes("interactive auth") || msg.includes("CI")) log.error("Auth requires an interactive terminal. Run neon-init in your system terminal (outside the chat) to sign in.");
29
29
  else log.error(`Authentication failed: ${msg}`);
30
30
  return false;
31
31
  }
32
32
  }
33
33
  /**
34
+ * Checks whether neonctl has stored OAuth credentials.
35
+ */
36
+ async function isAuthenticated() {
37
+ return await getNeonctlAccessToken() !== null;
38
+ }
39
+ /**
34
40
  * Gets the OAuth access token from neonctl's stored credentials
35
41
  */
36
42
  async function getNeonctlAccessToken() {
@@ -47,11 +53,12 @@ async function getNeonctlAccessToken() {
47
53
  /**
48
54
  * Creates an API key using the Neon API with the OAuth token from neonctl
49
55
  */
50
- async function createApiKeyFromNeonctl() {
56
+ async function createApiKeyFromNeonctl(options) {
57
+ const quiet = options?.json === true;
51
58
  try {
52
59
  const accessToken = await getNeonctlAccessToken();
53
60
  if (!accessToken) {
54
- log.error("Could not find OAuth token from neonctl");
61
+ if (!quiet) log.error("Could not find OAuth token from neonctl");
55
62
  return null;
56
63
  }
57
64
  const keyName = `neonctl-init-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, -5)}`;
@@ -61,20 +68,21 @@ async function createApiKeyFromNeonctl() {
61
68
  Authorization: `Bearer ${accessToken}`,
62
69
  "Content-Type": "application/json"
63
70
  },
64
- body: JSON.stringify({ key_name: keyName })
71
+ body: JSON.stringify({ key_name: keyName }),
72
+ signal: AbortSignal.timeout(3e4)
65
73
  });
66
74
  if (!response.ok) {
67
75
  const errorText = await response.text();
68
- log.error(`Failed to create API key: ${response.status} ${errorText}`);
76
+ if (!quiet) log.error(`Failed to create API key: ${response.status} ${errorText}`);
69
77
  return null;
70
78
  }
71
79
  return (await response.json()).key || null;
72
80
  } catch (error) {
73
- log.error(`Failed to create API key: ${error instanceof Error ? error.message : "Unknown error"}`);
81
+ if (!quiet) log.error(`Failed to create API key: ${error instanceof Error ? error.message : "Unknown error"}`);
74
82
  return null;
75
83
  }
76
84
  }
77
85
  //#endregion
78
- export { createApiKeyFromNeonctl, ensureNeonctlAuth };
86
+ export { createApiKeyFromNeonctl, ensureNeonctlAuth, isAuthenticated };
79
87
 
80
88
  //# sourceMappingURL=auth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","names":[],"sources":["../../src/lib/auth.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { log } from \"@clack/prompts\";\nimport { execa } from \"execa\";\n\n/**\n * Ensures neonctl is authenticated by running a command that triggers auth if needed\n * This will automatically start the OAuth flow if the user isn't already authenticated\n */\nexport async function ensureNeonctlAuth(): Promise<boolean> {\n\t// If already authenticated (e.g. ran in a terminal before), we can proceed\n\tconst existingToken = await getNeonctlAccessToken();\n\tif (existingToken) return true;\n\n\ttry {\n\t\t// Use execa to authenticate with neonctl\n\t\tawait execa(\"npx\", [\"-y\", \"neonctl\", \"me\", \"--no-analytics\"], {\n\t\t\t// Shows OAuth URL and prompts to the user\n\t\t\tstdio: \"inherit\",\n\t\t\t// Unset CI so neonctl doesn't refuse to open the browser (e.g. when run from agent chat)\n\t\t\tenv: { ...process.env, CI: undefined },\n\t\t});\n\t\treturn true;\n\t} catch (error) {\n\t\tconst msg = error instanceof Error ? error.message : \"Unknown error\";\n\t\tif (msg.includes(\"interactive auth\") || msg.includes(\"CI\")) {\n\t\t\tlog.error(\n\t\t\t\t\"Auth requires an interactive terminal. Run neon-init in your system terminal (outside the chat) to sign in.\",\n\t\t\t);\n\t\t} else {\n\t\t\tlog.error(`Authentication failed: ${msg}`);\n\t\t}\n\t\treturn false;\n\t}\n}\n\n/**\n * Gets the OAuth access token from neonctl's stored credentials\n */\nasync function getNeonctlAccessToken(): Promise<string | null> {\n\ttry {\n\t\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\t\tif (!homeDir) return null;\n\n\t\tconst credentialsPath = resolve(\n\t\t\thomeDir,\n\t\t\t\".config\",\n\t\t\t\"neonctl\",\n\t\t\t\"credentials.json\",\n\t\t);\n\t\tif (!existsSync(credentialsPath)) return null;\n\n\t\tconst credentials = JSON.parse(readFileSync(credentialsPath, \"utf-8\"));\n\t\treturn credentials.access_token || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Creates an API key using the Neon API with the OAuth token from neonctl\n */\nexport async function createApiKeyFromNeonctl(): Promise<string | null> {\n\ttry {\n\t\tconst accessToken = await getNeonctlAccessToken();\n\t\tif (!accessToken) {\n\t\t\tlog.error(\"Could not find OAuth token from neonctl\");\n\t\t\treturn null;\n\t\t}\n\n\t\t// Generate a unique key name with timestamp\n\t\tconst timestamp = new Date()\n\t\t\t.toISOString()\n\t\t\t.replace(/[:.]/g, \"-\")\n\t\t\t.slice(0, -5); // e.g., 2024-10-08T15-30-45\n\t\tconst keyName = `neonctl-init-${timestamp}`;\n\n\t\t// Call Neon API to create an API key\n\t\tconst response = await fetch(\n\t\t\t\"https://console.neon.tech/api/v2/api_keys\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tkey_name: keyName,\n\t\t\t\t}),\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\t\t\tlog.error(\n\t\t\t\t`Failed to create API key: ${response.status} ${errorText}`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst data = await response.json();\n\t\treturn data.key || null;\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Failed to create API key: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn null;\n\t}\n}\n"],"mappings":";;;;;;;;;AASA,eAAsB,oBAAsC;AAG3D,KADsB,MAAM,uBAAuB,CAChC,QAAO;AAE1B,KAAI;AAEH,QAAM,MAAM,OAAO;GAAC;GAAM;GAAW;GAAM;GAAiB,EAAE;GAE7D,OAAO;GAEP,KAAK;IAAE,GAAG,QAAQ;IAAK,IAAI,KAAA;IAAW;GACtC,CAAC;AACF,SAAO;UACC,OAAO;EACf,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU;AACrD,MAAI,IAAI,SAAS,mBAAmB,IAAI,IAAI,SAAS,KAAK,CACzD,KAAI,MACH,8GACA;MAED,KAAI,MAAM,0BAA0B,MAAM;AAE3C,SAAO;;;;;;AAOT,eAAe,wBAAgD;AAC9D,KAAI;EACH,MAAM,UAAU,QAAQ,IAAI,QAAQ,QAAQ,IAAI;AAChD,MAAI,CAAC,QAAS,QAAO;EAErB,MAAM,kBAAkB,QACvB,SACA,WACA,WACA,mBACA;AACD,MAAI,CAAC,WAAW,gBAAgB,CAAE,QAAO;AAGzC,SADoB,KAAK,MAAM,aAAa,iBAAiB,QAAQ,CAAC,CACnD,gBAAgB;SAC5B;AACP,SAAO;;;;;;AAOT,eAAsB,0BAAkD;AACvE,KAAI;EACH,MAAM,cAAc,MAAM,uBAAuB;AACjD,MAAI,CAAC,aAAa;AACjB,OAAI,MAAM,0CAA0C;AACpD,UAAO;;EAQR,MAAM,UAAU,iCAJE,IAAI,MAAM,EAC1B,aAAa,CACb,QAAQ,SAAS,IAAI,CACrB,MAAM,GAAG,GAAG;EAId,MAAM,WAAW,MAAM,MACtB,6CACA;GACC,QAAQ;GACR,SAAS;IACR,eAAe,UAAU;IACzB,gBAAgB;IAChB;GACD,MAAM,KAAK,UAAU,EACpB,UAAU,SACV,CAAC;GACF,CACD;AAED,MAAI,CAAC,SAAS,IAAI;GACjB,MAAM,YAAY,MAAM,SAAS,MAAM;AACvC,OAAI,MACH,6BAA6B,SAAS,OAAO,GAAG,YAChD;AACD,UAAO;;AAIR,UADa,MAAM,SAAS,MAAM,EACtB,OAAO;UACX,OAAO;AACf,MAAI,MACH,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,kBACtE;AACD,SAAO"}
1
+ {"version":3,"file":"auth.js","names":[],"sources":["../../src/lib/auth.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { log } from \"@clack/prompts\";\nimport { execa } from \"execa\";\n\nexport interface AuthOptions {\n\tjson?: boolean;\n}\n\n/**\n * Ensures neonctl is authenticated by running a command that triggers auth if needed\n * This will automatically start the OAuth flow if the user isn't already authenticated\n */\nexport async function ensureNeonctlAuth(\n\toptions?: AuthOptions,\n): Promise<boolean> {\n\tconst quiet = options?.json === true;\n\n\t// If already authenticated (e.g. ran in a terminal before), we can proceed\n\tconst existingToken = await getNeonctlAccessToken();\n\tif (existingToken) return true;\n\n\ttry {\n\t\t// Use execa to authenticate with neonctl\n\t\tawait execa(\"npx\", [\"-y\", \"neonctl\", \"me\"], {\n\t\t\t// Shows OAuth URL and prompts to the user\n\t\t\tstdio: \"inherit\",\n\t\t\t// Unset CI so neonctl doesn't refuse to open the browser (e.g. when run from agent chat)\n\t\t\tenv: { ...process.env, CI: undefined },\n\t\t});\n\t\treturn true;\n\t} catch (error) {\n\t\tconst msg = error instanceof Error ? error.message : \"Unknown error\";\n\t\tif (!quiet) {\n\t\t\tif (msg.includes(\"interactive auth\") || msg.includes(\"CI\")) {\n\t\t\t\tlog.error(\n\t\t\t\t\t\"Auth requires an interactive terminal. Run neon-init in your system terminal (outside the chat) to sign in.\",\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog.error(`Authentication failed: ${msg}`);\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n}\n\n/**\n * Checks whether neonctl has stored OAuth credentials.\n */\nexport async function isAuthenticated(): Promise<boolean> {\n\tconst token = await getNeonctlAccessToken();\n\treturn token !== null;\n}\n\n/**\n * Gets the OAuth access token from neonctl's stored credentials\n */\nasync function getNeonctlAccessToken(): Promise<string | null> {\n\ttry {\n\t\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\t\tif (!homeDir) return null;\n\n\t\tconst credentialsPath = resolve(\n\t\t\thomeDir,\n\t\t\t\".config\",\n\t\t\t\"neonctl\",\n\t\t\t\"credentials.json\",\n\t\t);\n\t\tif (!existsSync(credentialsPath)) return null;\n\n\t\tconst credentials = JSON.parse(readFileSync(credentialsPath, \"utf-8\"));\n\t\treturn credentials.access_token || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Creates an API key using the Neon API with the OAuth token from neonctl\n */\nexport async function createApiKeyFromNeonctl(\n\toptions?: AuthOptions,\n): Promise<string | null> {\n\tconst quiet = options?.json === true;\n\n\ttry {\n\t\tconst accessToken = await getNeonctlAccessToken();\n\t\tif (!accessToken) {\n\t\t\tif (!quiet) log.error(\"Could not find OAuth token from neonctl\");\n\t\t\treturn null;\n\t\t}\n\n\t\t// Generate a unique key name with timestamp\n\t\tconst timestamp = new Date()\n\t\t\t.toISOString()\n\t\t\t.replace(/[:.]/g, \"-\")\n\t\t\t.slice(0, -5); // e.g., 2024-10-08T15-30-45\n\t\tconst keyName = `neonctl-init-${timestamp}`;\n\n\t\t// Call Neon API to create an API key\n\t\tconst response = await fetch(\n\t\t\t\"https://console.neon.tech/api/v2/api_keys\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tkey_name: keyName,\n\t\t\t\t}),\n\t\t\t\tsignal: AbortSignal.timeout(30000),\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\t\t\tif (!quiet)\n\t\t\t\tlog.error(\n\t\t\t\t\t`Failed to create API key: ${response.status} ${errorText}`,\n\t\t\t\t);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst data = await response.json();\n\t\treturn data.key || null;\n\t} catch (error) {\n\t\tif (!quiet)\n\t\t\tlog.error(\n\t\t\t\t`Failed to create API key: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t\t);\n\t\treturn null;\n\t}\n}\n"],"mappings":";;;;;;;;;AAaA,eAAsB,kBACrB,SACmB;CACnB,MAAM,QAAQ,SAAS,SAAS;CAIhC,IAAI,MADwB,sBAAsB,GAC/B,OAAO;CAE1B,IAAI;EAEH,MAAM,MAAM,OAAO;GAAC;GAAM;GAAW;EAAI,GAAG;GAE3C,OAAO;GAEP,KAAK;IAAE,GAAG,QAAQ;IAAK,IAAI,KAAA;GAAU;EACtC,CAAC;EACD,OAAO;CACR,SAAS,OAAO;EACf,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU;EACrD,IAAI,CAAC,OACJ,IAAI,IAAI,SAAS,kBAAkB,KAAK,IAAI,SAAS,IAAI,GACxD,IAAI,MACH,6GACD;OAEA,IAAI,MAAM,0BAA0B,KAAK;EAG3C,OAAO;CACR;AACD;;;;AAKA,eAAsB,kBAAoC;CAEzD,OAAO,MADa,sBAAsB,MACzB;AAClB;;;;AAKA,eAAe,wBAAgD;CAC9D,IAAI;EACH,MAAM,UAAU,QAAQ,IAAI,QAAQ,QAAQ,IAAI;EAChD,IAAI,CAAC,SAAS,OAAO;EAErB,MAAM,kBAAkB,QACvB,SACA,WACA,WACA,kBACD;EACA,IAAI,CAAC,WAAW,eAAe,GAAG,OAAO;EAGzC,OADoB,KAAK,MAAM,aAAa,iBAAiB,OAAO,CACnD,CAAC,CAAC,gBAAgB;CACpC,QAAQ;EACP,OAAO;CACR;AACD;;;;AAKA,eAAsB,wBACrB,SACyB;CACzB,MAAM,QAAQ,SAAS,SAAS;CAEhC,IAAI;EACH,MAAM,cAAc,MAAM,sBAAsB;EAChD,IAAI,CAAC,aAAa;GACjB,IAAI,CAAC,OAAO,IAAI,MAAM,yCAAyC;GAC/D,OAAO;EACR;EAOA,MAAM,UAAU,iCAJE,IAAI,KAAK,EAAA,CACzB,YAAY,CAAC,CACb,QAAQ,SAAS,GAAG,CAAC,CACrB,MAAM,GAAG,EAC6B;EAGxC,MAAM,WAAW,MAAM,MACtB,6CACA;GACC,QAAQ;GACR,SAAS;IACR,eAAe,UAAU;IACzB,gBAAgB;GACjB;GACA,MAAM,KAAK,UAAU,EACpB,UAAU,QACX,CAAC;GACD,QAAQ,YAAY,QAAQ,GAAK;EAClC,CACD;EAEA,IAAI,CAAC,SAAS,IAAI;GACjB,MAAM,YAAY,MAAM,SAAS,KAAK;GACtC,IAAI,CAAC,OACJ,IAAI,MACH,6BAA6B,SAAS,OAAO,GAAG,WACjD;GACD,OAAO;EACR;EAGA,QAAO,MADY,SAAS,KAAK,EAAA,CACrB,OAAO;CACpB,SAAS,OAAO;EACf,IAAI,CAAC,OACJ,IAAI,MACH,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,iBACvE;EACD,OAAO;CACR;AACD"}
@@ -0,0 +1,30 @@
1
+ //#region src/lib/bootstrap.d.ts
2
+ /**
3
+ * Neon features that a template or project may require.
4
+ * Each feature maps to a setup phase that the orchestrator can run.
5
+ */
6
+ type NeonFeature = "database" | "auth" | "functions" | "ai-gateway" | "object-storage";
7
+ interface BootstrapTemplate {
8
+ id: string;
9
+ title: string;
10
+ description: string;
11
+ /** Neon features this template needs (defaults to ["database"]) */
12
+ requires: NeonFeature[];
13
+ source: {
14
+ owner: string;
15
+ repo: string;
16
+ ref: string;
17
+ subdir: string;
18
+ };
19
+ }
20
+ /** Hardcoded fallback used when the remote manifest cannot be fetched. */
21
+ declare const FALLBACK_TEMPLATES: BootstrapTemplate[];
22
+ declare function parseManifest(text: string): BootstrapTemplate[];
23
+ /**
24
+ * Fetch the template manifest from the remote bootstrap.yaml in the
25
+ * neondatabase/examples repo. Falls back to the hardcoded list on any error.
26
+ */
27
+ declare function fetchTemplates(): Promise<BootstrapTemplate[]>;
28
+ //#endregion
29
+ export { BootstrapTemplate, FALLBACK_TEMPLATES, NeonFeature, fetchTemplates, parseManifest };
30
+ //# sourceMappingURL=bootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.d.ts","names":[],"sources":["../../src/lib/bootstrap.ts"],"mappings":";;AAMA;AAUA;AAeA;AAsBgB,KA/CJ,WAAA,GA+CiB,UAAgB,GAAA,MAAA,GAAiB,WAAA,GAAA,YAAA,GAAA,gBAAA;AAiDxC,UAtFL,iBAAA,CAsFmB;EAAA,EAAA,EAAA,MAAA;OAAY,EAAA,MAAA;aAAR,EAAA,MAAA;EAAO;YAjFpC;;;;;;;;;cAUE,oBAAoB;iBAsBjB,aAAA,gBAA6B;;;;;iBAiDvB,cAAA,CAAA,GAAkB,QAAQ"}
@@ -0,0 +1,61 @@
1
+ import YAML from "yaml";
2
+ //#region src/lib/bootstrap.ts
3
+ /** Default features when a template doesn't specify `requires`. */
4
+ const DEFAULT_REQUIRES = ["database"];
5
+ /** Hardcoded fallback used when the remote manifest cannot be fetched. */
6
+ const FALLBACK_TEMPLATES = [{
7
+ id: "hono",
8
+ title: "Hono API (Drizzle, Neon Postgres) on Neon Functions",
9
+ description: "A Hono API using Drizzle ORM and Neon Postgres, ready to deploy as a Neon Function.",
10
+ requires: ["database", "functions"],
11
+ source: {
12
+ owner: "neondatabase",
13
+ repo: "examples",
14
+ ref: "main",
15
+ subdir: "with-hono"
16
+ }
17
+ }];
18
+ const MANIFEST_URL = "https://raw.githubusercontent.com/neondatabase/examples/main/bootstrap.yaml";
19
+ const isRecord = (value) => typeof value === "object" && value !== null;
20
+ function parseManifest(text) {
21
+ const data = YAML.parse(text);
22
+ if (!isRecord(data) || !Array.isArray(data.templates)) throw new Error("Invalid bootstrap manifest: missing \"templates\" array.");
23
+ const templates = [];
24
+ for (const item of data.templates) {
25
+ if (!isRecord(item) || typeof item.id !== "string" || typeof item.title !== "string" || typeof item.description !== "string" || !isRecord(item.source) || typeof item.source.owner !== "string" || typeof item.source.repo !== "string" || typeof item.source.ref !== "string" || typeof item.source.subdir !== "string") continue;
26
+ const requires = Array.isArray(item.requires) && item.requires.every((r) => typeof r === "string") ? item.requires : DEFAULT_REQUIRES;
27
+ templates.push({
28
+ id: item.id,
29
+ title: item.title,
30
+ description: item.description,
31
+ requires,
32
+ source: {
33
+ owner: item.source.owner,
34
+ repo: item.source.repo,
35
+ ref: item.source.ref,
36
+ subdir: item.source.subdir
37
+ }
38
+ });
39
+ }
40
+ return templates;
41
+ }
42
+ /**
43
+ * Fetch the template manifest from the remote bootstrap.yaml in the
44
+ * neondatabase/examples repo. Falls back to the hardcoded list on any error.
45
+ */
46
+ async function fetchTemplates() {
47
+ const url = process.env.NEON_BOOTSTRAP_MANIFEST_URL ?? MANIFEST_URL;
48
+ try {
49
+ const res = await fetch(url, { signal: AbortSignal.timeout(1e4) });
50
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
51
+ const templates = parseManifest(await res.text());
52
+ if (templates.length === 0) return FALLBACK_TEMPLATES;
53
+ return templates;
54
+ } catch {
55
+ return FALLBACK_TEMPLATES;
56
+ }
57
+ }
58
+ //#endregion
59
+ export { FALLBACK_TEMPLATES, fetchTemplates, parseManifest };
60
+
61
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.js","names":[],"sources":["../../src/lib/bootstrap.ts"],"sourcesContent":["import YAML from \"yaml\";\n\n/**\n * Neon features that a template or project may require.\n * Each feature maps to a setup phase that the orchestrator can run.\n */\nexport type NeonFeature =\n\t| \"database\"\n\t| \"auth\"\n\t| \"functions\"\n\t| \"ai-gateway\"\n\t| \"object-storage\";\n\n/** Default features when a template doesn't specify `requires`. */\nconst DEFAULT_REQUIRES: NeonFeature[] = [\"database\"];\n\nexport interface BootstrapTemplate {\n\tid: string;\n\ttitle: string;\n\tdescription: string;\n\t/** Neon features this template needs (defaults to [\"database\"]) */\n\trequires: NeonFeature[];\n\tsource: {\n\t\towner: string;\n\t\trepo: string;\n\t\tref: string;\n\t\tsubdir: string;\n\t};\n}\n\n/** Hardcoded fallback used when the remote manifest cannot be fetched. */\nexport const FALLBACK_TEMPLATES: BootstrapTemplate[] = [\n\t{\n\t\tid: \"hono\",\n\t\ttitle: \"Hono API (Drizzle, Neon Postgres) on Neon Functions\",\n\t\tdescription:\n\t\t\t\"A Hono API using Drizzle ORM and Neon Postgres, ready to deploy as a Neon Function.\",\n\t\trequires: [\"database\", \"functions\"],\n\t\tsource: {\n\t\t\towner: \"neondatabase\",\n\t\t\trepo: \"examples\",\n\t\t\tref: \"main\",\n\t\t\tsubdir: \"with-hono\",\n\t\t},\n\t},\n];\n\nconst MANIFEST_URL =\n\t\"https://raw.githubusercontent.com/neondatabase/examples/main/bootstrap.yaml\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === \"object\" && value !== null;\n\nexport function parseManifest(text: string): BootstrapTemplate[] {\n\tconst data: unknown = YAML.parse(text);\n\tif (!isRecord(data) || !Array.isArray(data.templates)) {\n\t\tthrow new Error(\n\t\t\t'Invalid bootstrap manifest: missing \"templates\" array.',\n\t\t);\n\t}\n\tconst templates: BootstrapTemplate[] = [];\n\tfor (const item of data.templates) {\n\t\tif (\n\t\t\t!isRecord(item) ||\n\t\t\ttypeof item.id !== \"string\" ||\n\t\t\ttypeof item.title !== \"string\" ||\n\t\t\ttypeof item.description !== \"string\" ||\n\t\t\t!isRecord(item.source) ||\n\t\t\ttypeof item.source.owner !== \"string\" ||\n\t\t\ttypeof item.source.repo !== \"string\" ||\n\t\t\ttypeof item.source.ref !== \"string\" ||\n\t\t\ttypeof item.source.subdir !== \"string\"\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\t// Parse requires — accept string array, default to [\"database\"]\n\t\tconst requires: NeonFeature[] =\n\t\t\tArray.isArray(item.requires) &&\n\t\t\titem.requires.every((r: unknown) => typeof r === \"string\")\n\t\t\t\t? (item.requires as NeonFeature[])\n\t\t\t\t: DEFAULT_REQUIRES;\n\n\t\ttemplates.push({\n\t\t\tid: item.id,\n\t\t\ttitle: item.title,\n\t\t\tdescription: item.description,\n\t\t\trequires,\n\t\t\tsource: {\n\t\t\t\towner: item.source.owner,\n\t\t\t\trepo: item.source.repo,\n\t\t\t\tref: item.source.ref,\n\t\t\t\tsubdir: item.source.subdir,\n\t\t\t},\n\t\t});\n\t}\n\treturn templates;\n}\n\n/**\n * Fetch the template manifest from the remote bootstrap.yaml in the\n * neondatabase/examples repo. Falls back to the hardcoded list on any error.\n */\nexport async function fetchTemplates(): Promise<BootstrapTemplate[]> {\n\tconst url = process.env.NEON_BOOTSTRAP_MANIFEST_URL ?? MANIFEST_URL;\n\ttry {\n\t\tconst res = await fetch(url, {\n\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t});\n\t\tif (!res.ok) throw new Error(`HTTP ${res.status}`);\n\t\tconst text = await res.text();\n\t\tconst templates = parseManifest(text);\n\t\tif (templates.length === 0) return FALLBACK_TEMPLATES;\n\t\treturn templates;\n\t} catch {\n\t\treturn FALLBACK_TEMPLATES;\n\t}\n}\n"],"mappings":";;;AAcA,MAAM,mBAAkC,CAAC,UAAU;;AAiBnD,MAAa,qBAA0C,CACtD;CACC,IAAI;CACJ,OAAO;CACP,aACC;CACD,UAAU,CAAC,YAAY,WAAW;CAClC,QAAQ;EACP,OAAO;EACP,MAAM;EACN,KAAK;EACL,QAAQ;CACT;AACD,CACD;AAEA,MAAM,eACL;AAED,MAAM,YAAY,UACjB,OAAO,UAAU,YAAY,UAAU;AAExC,SAAgB,cAAc,MAAmC;CAChE,MAAM,OAAgB,KAAK,MAAM,IAAI;CACrC,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,MAAM,QAAQ,KAAK,SAAS,GACnD,MAAM,IAAI,MACT,0DACD;CAED,MAAM,YAAiC,CAAC;CACxC,KAAK,MAAM,QAAQ,KAAK,WAAW;EAClC,IACC,CAAC,SAAS,IAAI,KACd,OAAO,KAAK,OAAO,YACnB,OAAO,KAAK,UAAU,YACtB,OAAO,KAAK,gBAAgB,YAC5B,CAAC,SAAS,KAAK,MAAM,KACrB,OAAO,KAAK,OAAO,UAAU,YAC7B,OAAO,KAAK,OAAO,SAAS,YAC5B,OAAO,KAAK,OAAO,QAAQ,YAC3B,OAAO,KAAK,OAAO,WAAW,UAE9B;EAGD,MAAM,WACL,MAAM,QAAQ,KAAK,QAAQ,KAC3B,KAAK,SAAS,OAAO,MAAe,OAAO,MAAM,QAAQ,IACrD,KAAK,WACN;EAEJ,UAAU,KAAK;GACd,IAAI,KAAK;GACT,OAAO,KAAK;GACZ,aAAa,KAAK;GAClB;GACA,QAAQ;IACP,OAAO,KAAK,OAAO;IACnB,MAAM,KAAK,OAAO;IAClB,KAAK,KAAK,OAAO;IACjB,QAAQ,KAAK,OAAO;GACrB;EACD,CAAC;CACF;CACA,OAAO;AACR;;;;;AAMA,eAAsB,iBAA+C;CACpE,MAAM,MAAM,QAAQ,IAAI,+BAA+B;CACvD,IAAI;EACH,MAAM,MAAM,MAAM,MAAM,KAAK,EAC5B,QAAQ,YAAY,QAAQ,GAAM,EACnC,CAAC;EACD,IAAI,CAAC,IAAI,IAAI,MAAM,IAAI,MAAM,QAAQ,IAAI,QAAQ;EAEjD,MAAM,YAAY,cAAc,MADb,IAAI,KAAK,CACQ;EACpC,IAAI,UAAU,WAAW,GAAG,OAAO;EACnC,OAAO;CACR,QAAQ;EACP,OAAO;CACR;AACD"}
@@ -0,0 +1,5 @@
1
+ //#region src/lib/build-config.d.ts
2
+ declare const INTERNAL_VSX_GALLERY = "";
3
+ //#endregion
4
+ export { INTERNAL_VSX_GALLERY };
5
+ //# sourceMappingURL=build-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-config.d.ts","names":[],"sources":["../../src/lib/build-config.ts"],"mappings":";cACa,oBAAA"}
@@ -0,0 +1,6 @@
1
+ //#region src/lib/build-config.ts
2
+ const INTERNAL_VSX_GALLERY = "";
3
+ //#endregion
4
+ export { INTERNAL_VSX_GALLERY };
5
+
6
+ //# sourceMappingURL=build-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-config.js","names":[],"sources":["../../src/lib/build-config.ts"],"sourcesContent":["// Auto-generated by scripts/set-vsx-gallery.mjs — do not edit\nexport const INTERNAL_VSX_GALLERY = \"\";\n"],"mappings":";AACA,MAAa,uBAAuB"}
@@ -0,0 +1,22 @@
1
+ //#region src/lib/detect-agent.d.ts
2
+ /**
3
+ * Detects the IDE/editor the terminal is running in, regardless of which
4
+ * agent is active. Used for extension installation — the extension goes
5
+ * into the IDE, not the agent.
6
+ *
7
+ * Returns an Editor-compatible string: "Cursor", "VS Code", "Windsurf", or null.
8
+ */
9
+ declare function detectIde(): "Cursor" | "VS Code" | "Windsurf" | null;
10
+ declare function isCursorInstalled(): boolean;
11
+ declare function isVSCodeInstalled(): boolean;
12
+ /**
13
+ * Detects which agent/IDE is running the CLI from environment variables.
14
+ * Returns the add-mcp compatible agent ID (e.g. "cursor", "claude-code").
15
+ *
16
+ * Delegates IDE detection to detectIde() to avoid duplicating Cursor/VS Code
17
+ * heuristics, then checks for agent-specific env vars.
18
+ */
19
+ declare function detectAgent(): string | null;
20
+ //#endregion
21
+ export { detectAgent, detectIde, isCursorInstalled, isVSCodeInstalled };
22
+ //# sourceMappingURL=detect-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-agent.d.ts","names":[],"sources":["../../src/lib/detect-agent.ts"],"mappings":";;AASA;AAyDA;AAiBA;AA0BA;;;iBApGgB,SAAA,CAAA;iBAyDA,iBAAA,CAAA;iBAiBA,iBAAA,CAAA;;;;;;;;iBA0BA,WAAA,CAAA"}
@@ -0,0 +1,65 @@
1
+ import { existsSync } from "node:fs";
2
+ //#region src/lib/detect-agent.ts
3
+ /**
4
+ * Detects the IDE/editor the terminal is running in, regardless of which
5
+ * agent is active. Used for extension installation — the extension goes
6
+ * into the IDE, not the agent.
7
+ *
8
+ * Returns an Editor-compatible string: "Cursor", "VS Code", "Windsurf", or null.
9
+ */
10
+ function detectIde() {
11
+ const env = process.env;
12
+ if (env.TERM_PROGRAM === "cursor" || env.CURSOR_TRACE_ID !== void 0 || env.CURSOR_EXTENSION_HOST_ROLE !== void 0 || env.CURSOR_LAYOUT !== void 0 || env.CURSOR_SPAWNED_BY_EXTENSION_ID !== void 0) return "Cursor";
13
+ if (env.GIT_ASKPASS?.includes("Cursor.app") || env.VSCODE_GIT_ASKPASS_NODE?.includes("Cursor.app") || env.GIT_ASKPASS?.includes("cursor") || env.VSCODE_GIT_ASKPASS_NODE?.includes("cursor")) return "Cursor";
14
+ if (env.TERM_PROGRAM === "windsurf") return "Windsurf";
15
+ if (env.TERM_PROGRAM === "vscode" || env.VSCODE_PID !== void 0 || env.VSCODE_CWD !== void 0) {
16
+ const envValues = Object.values(env).join("\n").toLowerCase();
17
+ if (envValues.includes("cursor.app") || envValues.includes("/cursor/")) return "Cursor";
18
+ if (isCursorInstalled() && !isVSCodeInstalled()) return "Cursor";
19
+ return "VS Code";
20
+ }
21
+ return null;
22
+ }
23
+ function isCursorInstalled() {
24
+ if (process.platform === "darwin") return existsSync("/Applications/Cursor.app");
25
+ if (process.platform === "linux") return existsSync("/usr/share/cursor") || existsSync("/usr/bin/cursor");
26
+ if (process.platform === "win32") {
27
+ const localAppData = process.env.LOCALAPPDATA || "";
28
+ return existsSync(`${localAppData}\\Programs\\Cursor\\Cursor.exe`) || existsSync(`${localAppData}\\cursor\\Cursor.exe`);
29
+ }
30
+ return false;
31
+ }
32
+ function isVSCodeInstalled() {
33
+ if (process.platform === "darwin") return existsSync("/Applications/Visual Studio Code.app");
34
+ if (process.platform === "linux") return existsSync("/usr/share/code") || existsSync("/usr/bin/code");
35
+ if (process.platform === "win32") {
36
+ const localAppData = process.env.LOCALAPPDATA || "";
37
+ const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
38
+ return existsSync(`${localAppData}\\Programs\\Microsoft VS Code\\Code.exe`) || existsSync(`${programFiles}\\Microsoft VS Code\\Code.exe`);
39
+ }
40
+ return false;
41
+ }
42
+ /**
43
+ * Detects which agent/IDE is running the CLI from environment variables.
44
+ * Returns the add-mcp compatible agent ID (e.g. "cursor", "claude-code").
45
+ *
46
+ * Delegates IDE detection to detectIde() to avoid duplicating Cursor/VS Code
47
+ * heuristics, then checks for agent-specific env vars.
48
+ */
49
+ function detectAgent() {
50
+ const env = process.env;
51
+ if (env.CLAUDECODE === "1" || env.CLAUDE_CODE === "1" || env.CLAUDE_CLI === "1") return "claude-code";
52
+ if (env.CODEX === "1") return "codex";
53
+ if (env.CLINE === "1") return "cline";
54
+ const ide = detectIde();
55
+ if (ide) return {
56
+ Cursor: "cursor",
57
+ "VS Code": "vscode",
58
+ Windsurf: "windsurf"
59
+ }[ide] ?? null;
60
+ return null;
61
+ }
62
+ //#endregion
63
+ export { detectAgent, detectIde, isCursorInstalled, isVSCodeInstalled };
64
+
65
+ //# sourceMappingURL=detect-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-agent.js","names":[],"sources":["../../src/lib/detect-agent.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\n\n/**\n * Detects the IDE/editor the terminal is running in, regardless of which\n * agent is active. Used for extension installation — the extension goes\n * into the IDE, not the agent.\n *\n * Returns an Editor-compatible string: \"Cursor\", \"VS Code\", \"Windsurf\", or null.\n */\nexport function detectIde(): \"Cursor\" | \"VS Code\" | \"Windsurf\" | null {\n\tconst env = process.env;\n\n\t// Cursor: check explicit Cursor env vars (set in extension host context)\n\tif (\n\t\tenv.TERM_PROGRAM === \"cursor\" ||\n\t\tenv.CURSOR_TRACE_ID !== undefined ||\n\t\tenv.CURSOR_EXTENSION_HOST_ROLE !== undefined ||\n\t\tenv.CURSOR_LAYOUT !== undefined ||\n\t\tenv.CURSOR_SPAWNED_BY_EXTENSION_ID !== undefined\n\t) {\n\t\treturn \"Cursor\";\n\t}\n\n\t// Cursor: check app paths in env vars (reliable from terminal context)\n\tif (\n\t\tenv.GIT_ASKPASS?.includes(\"Cursor.app\") ||\n\t\tenv.VSCODE_GIT_ASKPASS_NODE?.includes(\"Cursor.app\") ||\n\t\tenv.GIT_ASKPASS?.includes(\"cursor\") ||\n\t\tenv.VSCODE_GIT_ASKPASS_NODE?.includes(\"cursor\")\n\t) {\n\t\treturn \"Cursor\";\n\t}\n\n\t// Windsurf\n\tif (env.TERM_PROGRAM === \"windsurf\") {\n\t\treturn \"Windsurf\";\n\t}\n\n\t// At this point we know we're in a VS Code-like environment if any\n\t// VSCODE_ env vars are set. But is it actually Cursor with generic env vars?\n\tif (\n\t\tenv.TERM_PROGRAM === \"vscode\" ||\n\t\tenv.VSCODE_PID !== undefined ||\n\t\tenv.VSCODE_CWD !== undefined\n\t) {\n\t\t// Check any remaining env vars that might contain \"cursor\" (case-insensitive)\n\t\t// This catches VSCODE_IPC_HOOK_CLI, PATH additions, etc.\n\t\tconst envValues = Object.values(env).join(\"\\n\").toLowerCase();\n\t\tif (\n\t\t\tenvValues.includes(\"cursor.app\") ||\n\t\t\tenvValues.includes(\"/cursor/\")\n\t\t) {\n\t\t\treturn \"Cursor\";\n\t\t}\n\n\t\t// If only Cursor is installed (not VS Code), it must be Cursor\n\t\tif (isCursorInstalled() && !isVSCodeInstalled()) {\n\t\t\treturn \"Cursor\";\n\t\t}\n\n\t\treturn \"VS Code\";\n\t}\n\n\treturn null;\n}\n\nexport function isCursorInstalled(): boolean {\n\tif (process.platform === \"darwin\") {\n\t\treturn existsSync(\"/Applications/Cursor.app\");\n\t}\n\tif (process.platform === \"linux\") {\n\t\treturn existsSync(\"/usr/share/cursor\") || existsSync(\"/usr/bin/cursor\");\n\t}\n\tif (process.platform === \"win32\") {\n\t\tconst localAppData = process.env.LOCALAPPDATA || \"\";\n\t\treturn (\n\t\t\texistsSync(`${localAppData}\\\\Programs\\\\Cursor\\\\Cursor.exe`) ||\n\t\t\texistsSync(`${localAppData}\\\\cursor\\\\Cursor.exe`)\n\t\t);\n\t}\n\treturn false;\n}\n\nexport function isVSCodeInstalled(): boolean {\n\tif (process.platform === \"darwin\") {\n\t\treturn existsSync(\"/Applications/Visual Studio Code.app\");\n\t}\n\tif (process.platform === \"linux\") {\n\t\treturn existsSync(\"/usr/share/code\") || existsSync(\"/usr/bin/code\");\n\t}\n\tif (process.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\texistsSync(\n\t\t\t\t`${localAppData}\\\\Programs\\\\Microsoft VS Code\\\\Code.exe`,\n\t\t\t) || existsSync(`${programFiles}\\\\Microsoft VS Code\\\\Code.exe`)\n\t\t);\n\t}\n\treturn false;\n}\n\n/**\n * Detects which agent/IDE is running the CLI from environment variables.\n * Returns the add-mcp compatible agent ID (e.g. \"cursor\", \"claude-code\").\n *\n * Delegates IDE detection to detectIde() to avoid duplicating Cursor/VS Code\n * heuristics, then checks for agent-specific env vars.\n */\nexport function detectAgent(): string | null {\n\tconst env = process.env;\n\n\t// Agent-specific env vars (checked first — these are unambiguous)\n\tif (\n\t\tenv.CLAUDECODE === \"1\" ||\n\t\tenv.CLAUDE_CODE === \"1\" ||\n\t\tenv.CLAUDE_CLI === \"1\"\n\t) {\n\t\treturn \"claude-code\";\n\t}\n\tif (env.CODEX === \"1\") return \"codex\";\n\tif (env.CLINE === \"1\") return \"cline\";\n\n\t// Fall back to IDE detection (Cursor, VS Code, Windsurf)\n\tconst ide = detectIde();\n\tif (ide) {\n\t\tconst IDE_TO_AGENT: Record<string, string> = {\n\t\t\tCursor: \"cursor\",\n\t\t\t\"VS Code\": \"vscode\",\n\t\t\tWindsurf: \"windsurf\",\n\t\t};\n\t\treturn IDE_TO_AGENT[ide] ?? null;\n\t}\n\n\treturn null;\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,YAAsD;CACrE,MAAM,MAAM,QAAQ;CAGpB,IACC,IAAI,iBAAiB,YACrB,IAAI,oBAAoB,KAAA,KACxB,IAAI,+BAA+B,KAAA,KACnC,IAAI,kBAAkB,KAAA,KACtB,IAAI,mCAAmC,KAAA,GAEvC,OAAO;CAIR,IACC,IAAI,aAAa,SAAS,YAAY,KACtC,IAAI,yBAAyB,SAAS,YAAY,KAClD,IAAI,aAAa,SAAS,QAAQ,KAClC,IAAI,yBAAyB,SAAS,QAAQ,GAE9C,OAAO;CAIR,IAAI,IAAI,iBAAiB,YACxB,OAAO;CAKR,IACC,IAAI,iBAAiB,YACrB,IAAI,eAAe,KAAA,KACnB,IAAI,eAAe,KAAA,GAClB;EAGD,MAAM,YAAY,OAAO,OAAO,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,YAAY;EAC5D,IACC,UAAU,SAAS,YAAY,KAC/B,UAAU,SAAS,UAAU,GAE7B,OAAO;EAIR,IAAI,kBAAkB,KAAK,CAAC,kBAAkB,GAC7C,OAAO;EAGR,OAAO;CACR;CAEA,OAAO;AACR;AAEA,SAAgB,oBAA6B;CAC5C,IAAI,QAAQ,aAAa,UACxB,OAAO,WAAW,0BAA0B;CAE7C,IAAI,QAAQ,aAAa,SACxB,OAAO,WAAW,mBAAmB,KAAK,WAAW,iBAAiB;CAEvE,IAAI,QAAQ,aAAa,SAAS;EACjC,MAAM,eAAe,QAAQ,IAAI,gBAAgB;EACjD,OACC,WAAW,GAAG,aAAa,+BAA+B,KAC1D,WAAW,GAAG,aAAa,qBAAqB;CAElD;CACA,OAAO;AACR;AAEA,SAAgB,oBAA6B;CAC5C,IAAI,QAAQ,aAAa,UACxB,OAAO,WAAW,sCAAsC;CAEzD,IAAI,QAAQ,aAAa,SACxB,OAAO,WAAW,iBAAiB,KAAK,WAAW,eAAe;CAEnE,IAAI,QAAQ,aAAa,SAAS;EACjC,MAAM,eAAe,QAAQ,IAAI,gBAAgB;EACjD,MAAM,eAAe,QAAQ,IAAI,gBAAgB;EACjD,OACC,WACC,GAAG,aAAa,wCACjB,KAAK,WAAW,GAAG,aAAa,8BAA8B;CAEhE;CACA,OAAO;AACR;;;;;;;;AASA,SAAgB,cAA6B;CAC5C,MAAM,MAAM,QAAQ;CAGpB,IACC,IAAI,eAAe,OACnB,IAAI,gBAAgB,OACpB,IAAI,eAAe,KAEnB,OAAO;CAER,IAAI,IAAI,UAAU,KAAK,OAAO;CAC9B,IAAI,IAAI,UAAU,KAAK,OAAO;CAG9B,MAAM,MAAM,UAAU;CACtB,IAAI,KAMH,OAAO;EAJN,QAAQ;EACR,WAAW;EACX,UAAU;CAEO,EAAE,QAAQ;CAG7B,OAAO;AACR"}
@@ -1 +1 @@
1
- {"version":3,"file":"editors.d.ts","names":[],"sources":["../../src/lib/editors.ts"],"sourcesContent":[],"mappings":";;;;;;AAQA;AA8CsB,iBA9CN,wBAAA,CA8C4B,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,IAAA;;;;AAElC,iBAFY,sBAAA,CAEZ,OAAA,EAAA,MAAA,CAAA,EAAP,OAAO,CAAC,MAAD,EAAA,CAAA"}
1
+ {"version":3,"file":"editors.d.ts","names":[],"sources":["../../src/lib/editors.ts"],"mappings":";;;;;;AAQA;AA8CsB,iBA9CN,wBAAA,CA8C4B,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,IAAA;;;;AAElC,iBAFY,sBAAA,CAEZ,OAAA,EAAA,MAAA,CAAA,EAAP,OAAO,CAAC,MAAD,EAAA,CAAA"}
@@ -1 +1 @@
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"}
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;CAEzB,IAAI,aAAa,UAEhB,OAAO,QACN,SACA,WACA,uBACA,QACA,MACD;CAED,IAAI,aAAa,SAEhB,OAAO,QAAQ,SAAS,WAAW,QAAQ,MAAM;CAElD,IAAI,aAAa,SAAS;EAEzB,MAAM,UAAU,QAAQ,IAAI;EAC5B,IAAI,SACH,OAAO,QAAQ,SAAS,QAAQ,MAAM;CAExC;CAEA,OAAO;AACR;;;;AAKA,eAAe,uBAAyC;CACvD,IAAI;EACH,MAAM,MAAM,UAAU,CAAC,WAAW,GAAG;GACpC,OAAO;GACP,SAAS;EACV,CAAC;EACD,OAAO;CACR,QAAQ;EACP,OAAO;CACR;AACD;;;;AAKA,eAAsB,uBACrB,SACoB;CACpB,MAAM,UAAoB,CAAC;CAI3B,IAAI,WADc,QAAQ,SAAS,SACZ,CAAC,GACvB,QAAQ,KAAK,QAAQ;CAItB,MAAM,kBAAkB,yBAAyB,OAAO;CACxD,IAAI,mBAAmB,WAAW,eAAe,GAChD,QAAQ,KAAK,SAAS;CAKvB,IAAI,MAD0B,qBAAqB,GAElD,QAAQ,KAAK,YAAY;CAG1B,OAAO;AACR"}
@@ -8,14 +8,22 @@ import { Editor } from "./types.js";
8
8
  * Falls back to the simple command name if no full path is found (in case it's in PATH)
9
9
  */
10
10
  declare function findEditorCommand(editor: Editor): Promise<string | null>;
11
+ /**
12
+ * Checks if the extension is installed by querying the editor's extension list
13
+ */
14
+ declare function isExtensionInstalled(editor: Editor): Promise<boolean>;
11
15
  /**
12
16
  * Waits for the extension to appear in the installed extensions list
13
17
  * This ensures the extension is fully installed and activated before we try to configure it
14
18
  */
15
19
  declare function waitForExtensionInstalled(editor: Editor, maxAttempts?: number, delayMs?: number): Promise<boolean>;
16
20
  /**
17
- * Installs the Neon Local Connect extension for VS Code or Cursor
18
- * Returns success only if installation succeeds, fails silently otherwise
21
+ * Installs the Neon Local Connect extension for VS Code or Cursor.
22
+ *
23
+ * Strategy:
24
+ * 1. Try `--install-extension <id>` directly (uses the editor's configured marketplace)
25
+ * 2. If that fails, download .vsix (from proxy or Open VSX) and install locally
26
+ * 3. Set NEON_VSX_GALLERY_URL to use a corporate proxy for the download
19
27
  */
20
28
  declare function installExtension(editor: Editor): Promise<boolean>;
21
29
  /**
@@ -28,5 +36,5 @@ declare function configureExtension(editor: Editor, apiKey: string): Promise<boo
28
36
  */
29
37
  declare function usesExtension(editor: Editor): boolean;
30
38
  //#endregion
31
- export { configureExtension, findEditorCommand, installExtension, usesExtension, waitForExtensionInstalled };
39
+ export { configureExtension, findEditorCommand, installExtension, isExtensionInstalled, usesExtension, waitForExtensionInstalled };
32
40
  //# sourceMappingURL=extension.d.ts.map
@@ -1 +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;;AAyEH;;;AAIG,iBAJmB,yBAAA,CAInB,MAAA,EAHM,MAGN,EAAA,WAAA,CAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAAA,OAAA,CAAA,OAAA,CAAA;;AAuBH;;;AAAwD,iBAAlC,gBAAA,CAAkC,MAAA,EAAT,MAAS,CAAA,EAAA,OAAA,CAAA,OAAA,CAAA;;AAkBxD;;;AAGG,iBAHmB,kBAAA,CAGnB,MAAA,EAFM,MAEN,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,OAAA,CAAA,OAAA,CAAA;;AAoCH;;iBAAgB,aAAA,SAAsB"}
1
+ {"version":3,"file":"extension.d.ts","names":[],"sources":["../../src/lib/extension.ts"],"mappings":";;;;;;AAuGA;;;AAEG,iBAFmB,iBAAA,CAEnB,MAAA,EADM,MACN,CAAA,EAAA,OAAA,CAAA,MAAA,GAAA,IAAA,CAAA;AAAO;AAqDV;;AAAmD,iBAA7B,oBAAA,CAA6B,MAAA,EAAA,MAAA,CAAA,EAAS,OAAT,CAAA,OAAA,CAAA;;AAAgB;AAoBnE;;AACS,iBADa,yBAAA,CACb,MAAA,EAAA,MAAA,EAAA,WAAA,CAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAGN,OAHM,CAAA,OAAA,CAAA;;AAGC;AA2BV;;;;AAA+D;AA0C/D;AAAwC,iBA1ClB,gBAAA,CA0CkB,MAAA,EA1CO,MA0CP,CAAA,EA1CgB,OA0ChB,CAAA,OAAA,CAAA;;;AAG9B;AAoCV;iBAvCsB,kBAAA,SACb,yBAEN;;;;iBAoCa,aAAA,SAAsB"}
@@ -1,7 +1,8 @@
1
+ import { NEON_EXTENSION_ID, downloadVsix } from "./vsix.js";
1
2
  import { existsSync } from "node:fs";
2
3
  import { execa } from "execa";
4
+ import { unlink } from "node:fs/promises";
3
5
  //#region src/lib/extension.ts
4
- const NEON_EXTENSION_ID = "databricks.neon-local-connect";
5
6
  /**
6
7
  * Uses macOS mdfind to locate an app by bundle identifier
7
8
  */
@@ -108,7 +109,7 @@ function getEditorUriScheme(editor) {
108
109
  /**
109
110
  * Checks if the extension is installed by querying the editor's extension list
110
111
  */
111
- async function isExtensionInList(editor) {
112
+ async function isExtensionInstalled(editor) {
112
113
  const command = await findEditorCommand(editor);
113
114
  if (!command) return false;
114
115
  try {
@@ -123,7 +124,7 @@ async function isExtensionInList(editor) {
123
124
  */
124
125
  async function waitForExtensionInstalled(editor, maxAttempts = 10, delayMs = 1e3) {
125
126
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
126
- if (await isExtensionInList(editor)) {
127
+ if (await isExtensionInstalled(editor)) {
127
128
  await new Promise((resolve) => setTimeout(resolve, 1e3));
128
129
  return true;
129
130
  }
@@ -132,17 +133,37 @@ async function waitForExtensionInstalled(editor, maxAttempts = 10, delayMs = 1e3
132
133
  return false;
133
134
  }
134
135
  /**
135
- * Installs the Neon Local Connect extension for VS Code or Cursor
136
- * Returns success only if installation succeeds, fails silently otherwise
136
+ * Installs the Neon Local Connect extension for VS Code or Cursor.
137
+ *
138
+ * Strategy:
139
+ * 1. Try `--install-extension <id>` directly (uses the editor's configured marketplace)
140
+ * 2. If that fails, download .vsix (from proxy or Open VSX) and install locally
141
+ * 3. Set NEON_VSX_GALLERY_URL to use a corporate proxy for the download
137
142
  */
138
143
  async function installExtension(editor) {
139
144
  const command = await findEditorCommand(editor);
140
145
  if (!command) return false;
141
146
  try {
142
- 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
+ });
143
160
  return true;
144
161
  } catch {
145
162
  return false;
163
+ } finally {
164
+ try {
165
+ await unlink(vsixPath);
166
+ } catch {}
146
167
  }
147
168
  }
148
169
  /**
@@ -176,6 +197,6 @@ function usesExtension(editor) {
176
197
  return editor === "VS Code" || editor === "Cursor";
177
198
  }
178
199
  //#endregion
179
- export { configureExtension, findEditorCommand, installExtension, usesExtension, waitForExtensionInstalled };
200
+ export { configureExtension, findEditorCommand, installExtension, isExtensionInstalled, usesExtension, waitForExtensionInstalled };
180
201
 
181
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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"}