neon 4.7.0 → 4.9.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 (58) hide show
  1. package/README.md +49 -59
  2. package/dist/_chunks/{auth_selection-ktL6uoEe.js → auth_selection-pitMFzDe.js} +1 -1
  3. package/dist/_chunks/{credential_io-B1JxWELN.js → credential_io-YeAxg9Xn.js} +2 -2
  4. package/dist/_chunks/{paths-g3DRqJiD.js → paths-DMq0Lt7a.js} +1 -1
  5. package/dist/_chunks/{profiles-D95AeZ5v.js → profiles-CvnFEQyd.js} +1 -1
  6. package/dist/analytics.js +2 -2
  7. package/dist/auth_context.js +1 -1
  8. package/dist/commands/auth.js +12 -9
  9. package/dist/commands/bootstrap.js +72 -43
  10. package/dist/commands/branches.js +29 -14
  11. package/dist/commands/claim.js +1 -1
  12. package/dist/commands/init.js +80 -79
  13. package/dist/commands/link.js +78 -63
  14. package/dist/commands/mcp.js +1 -1
  15. package/dist/commands/plugins.js +4 -4
  16. package/dist/commands/profile.js +4 -4
  17. package/dist/commands/projects.js +17 -6
  18. package/dist/commands/skills.js +1 -1
  19. package/dist/config.js +1 -1
  20. package/dist/credential_io.js +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/init/child.js +36 -0
  23. package/dist/init/plan.js +47 -0
  24. package/dist/init/tooling.js +43 -0
  25. package/dist/init/wizard.js +42 -0
  26. package/dist/mcp/targets.js +1 -1
  27. package/dist/plugins/plan.js +1 -1
  28. package/dist/plugins/targets.js +5 -1
  29. package/dist/retire_credential.js +1 -1
  30. package/dist/utils/middlewares.js +1 -1
  31. package/dist/utils/package_manager.js +10 -5
  32. package/package.json +15 -16
  33. package/dist/init/auth.js +0 -53
  34. package/dist/init/build_config.js +0 -4
  35. package/dist/init/detect_agent.js +0 -63
  36. package/dist/init/editors.js +0 -19
  37. package/dist/init/enrich_output.js +0 -61
  38. package/dist/init/extension.js +0 -154
  39. package/dist/init/inspect.js +0 -249
  40. package/dist/init/interactive.js +0 -529
  41. package/dist/init/neonctl.js +0 -139
  42. package/dist/init/orchestrate.js +0 -189
  43. package/dist/init/phases/auth.js +0 -195
  44. package/dist/init/phases/cleanup.js +0 -27
  45. package/dist/init/phases/db.js +0 -257
  46. package/dist/init/phases/getting_started.js +0 -218
  47. package/dist/init/phases/mcp.js +0 -219
  48. package/dist/init/phases/migrations.js +0 -250
  49. package/dist/init/phases/neon_auth.js +0 -116
  50. package/dist/init/phases/setup.js +0 -582
  51. package/dist/init/phases/skills.js +0 -86
  52. package/dist/init/phases/status.js +0 -82
  53. package/dist/init/profile_cli.js +0 -56
  54. package/dist/init/resolve_context.js +0 -105
  55. package/dist/init/route_command.js +0 -93
  56. package/dist/init/skills.js +0 -206
  57. package/dist/init/types.js +0 -1
  58. package/dist/init/vsix.js +0 -89
@@ -0,0 +1,43 @@
1
+ import { log } from "../log.js";
2
+ import { getCliName } from "../utils/cli_name.js";
3
+ import { AUTH_CHILD } from "./child.js";
4
+ import { childArgv, planAgentSteps, resolveInitAgentSetup } from "./plan.js";
5
+ import { detectInstallablePluginsAgents } from "../plugins/targets.js";
6
+ import { canPickAgentsInteractively } from "../utils/agent_picker.js";
7
+ import { pickAgentSetupInteractively } from "./wizard.js";
8
+ //#region src/init/tooling.ts
9
+ const defaultHasProjectPlugins = async (cwd) => {
10
+ return (await detectInstallablePluginsAgents({
11
+ scope: "project",
12
+ cwd
13
+ })).length > 0;
14
+ };
15
+ const runInitSteps = async (steps, options) => {
16
+ for (const step of steps) {
17
+ log.info("Running `%s %s`", getCliName(), step.join(" "));
18
+ const argv = childArgv(step, options.forward);
19
+ const command = step[0];
20
+ if (!await options.run(argv, options.cwd, command !== void 0 && AUTH_CHILD.has(command) ? options.authEnv : void 0)) throw new Error(`\`${getCliName()} ${step.join(" ")}\` failed.`);
21
+ }
22
+ };
23
+ const runAgentTooling = async (options) => {
24
+ const yes = options.yes;
25
+ const hasProjectPlugins = yes ? await (options.hasProjectPlugins ?? defaultHasProjectPlugins)(options.cwd) : false;
26
+ const interactive = !yes && (options.pickAgentSetup !== void 0 || canPickAgentsInteractively());
27
+ const agentSetup = await resolveInitAgentSetup({
28
+ yes,
29
+ interactive,
30
+ hasProjectPlugins,
31
+ pick: options.pickAgentSetup ?? pickAgentSetupInteractively
32
+ });
33
+ await runInitSteps(planAgentSteps({
34
+ yes,
35
+ agentSetup
36
+ }), options);
37
+ };
38
+ const runScaffoldFollowUp = async (options) => {
39
+ if (!options.skipAgentSetup) await runAgentTooling(options);
40
+ if (options.shouldLink) await runInitSteps([options.linkYes ? ["link", "--yes"] : ["link"]], options);
41
+ };
42
+ //#endregion
43
+ export { runAgentTooling, runInitSteps, runScaffoldFollowUp };
@@ -0,0 +1,42 @@
1
+ import { canPickAgentsInteractively } from "../utils/agent_picker.js";
2
+ import { NEON_PLUGIN_NAME } from "../plugins/run.js";
3
+ import prompts from "prompts";
4
+ //#region src/init/wizard.ts
5
+ const restoreCursorOnAbort = (state) => {
6
+ if (state.aborted) {
7
+ process.stdout.write("\x1B[?25h");
8
+ process.stdout.write("\n");
9
+ process.exit(1);
10
+ }
11
+ };
12
+ const pickAgentSetupInteractively = async () => {
13
+ if (!canPickAgentsInteractively()) throw new Error("No interactive terminal. Pass -y to use defaults, or run this command in a terminal.");
14
+ const { setup } = await prompts({
15
+ onState: restoreCursorOnAbort,
16
+ type: "select",
17
+ name: "setup",
18
+ message: "How should coding agents get Neon in this project?",
19
+ initial: 0,
20
+ choices: [
21
+ {
22
+ title: "Plugin (recommended)",
23
+ value: "plugin",
24
+ description: `Install ${NEON_PLUGIN_NAME} (skills and MCP in one)`
25
+ },
26
+ {
27
+ title: "Skills and MCP separately",
28
+ value: "skills-mcp",
29
+ description: "Install skills, then the MCP server"
30
+ },
31
+ {
32
+ title: "Skip agent setup",
33
+ value: "skip",
34
+ description: "Continue without a plugin, skills, or MCP"
35
+ }
36
+ ]
37
+ });
38
+ if (setup !== "plugin" && setup !== "skills-mcp" && setup !== "skip") throw new Error("Aborted.");
39
+ return setup;
40
+ };
41
+ //#endregion
42
+ export { pickAgentSetupInteractively };
@@ -1,5 +1,5 @@
1
- import { mcpUnsupportedReason } from "./install.js";
2
1
  import { detectInstalledAgents, listMcpAgentIds, resolveAddMcpAgentId, uniqueAgentIds } from "./agents.js";
2
+ import { mcpUnsupportedReason } from "./install.js";
3
3
  import { detectProjectAgents } from "add-mcp";
4
4
  //#region src/mcp/targets.ts
5
5
  function mcpInstallableAgents(scope) {
@@ -1,7 +1,7 @@
1
1
  import { getAgentDisplayName, tryResolveAddMcpAgentId } from "../mcp/agents.js";
2
2
  import "../init/agents.js";
3
- import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
4
3
  import { detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, pluginsInstallableAgents } from "./targets.js";
4
+ import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
5
5
  //#region src/plugins/plan.ts
6
6
  const assertPluginsCanRun = (options) => {
7
7
  if (options.yes || options.interactive || options.hasAgents) return;
@@ -38,6 +38,10 @@ const detectPluginsAgents = async (options) => {
38
38
  const detected = options.scope === "project" ? detectProjectAgents(options.cwd) : await detectInstalledAgents();
39
39
  return uniqueAgentIds(detected).filter((id) => supportsPlugins(id));
40
40
  };
41
+ const detectInstallablePluginsAgents = async (options) => {
42
+ const installable = new Set(pluginsInstallableAgents(options.scope));
43
+ return (await detectPluginsAgents(options)).filter((id) => installable.has(id));
44
+ };
41
45
  const mappedPluginsTargets = (agents, scope) => {
42
46
  const mapped = [];
43
47
  const byTarget = /* @__PURE__ */ new Map();
@@ -61,4 +65,4 @@ const mappedPluginsTargets = (agents, scope) => {
61
65
  return mapped;
62
66
  };
63
67
  //#endregion
64
- export { detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, pluginsInstallableAgents, pluginsMappedAgents, supportsPlugins };
68
+ export { detectInstallablePluginsAgents, detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, pluginsInstallableAgents, pluginsMappedAgents, supportsPlugins };
@@ -1,4 +1,4 @@
1
- import { a as OAUTH, d as scopeOf, i as API_KEY, l as interpretCredentials, t as storeFor, u as isSameCredential } from "./_chunks/credential_io-B1JxWELN.js";
1
+ import { a as OAUTH, d as scopeOf, i as API_KEY, l as interpretCredentials, t as storeFor, u as isSameCredential } from "./_chunks/credential_io-YeAxg9Xn.js";
2
2
  import { log } from "./log.js";
3
3
  import { getApiClient } from "./api.js";
4
4
  import { revokeToken } from "./auth.js";
@@ -1,4 +1,4 @@
1
- import { r as recordCredentialInputs } from "../_chunks/auth_selection-ktL6uoEe.js";
1
+ import { r as recordCredentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
2
2
  //#region src/utils/middlewares.ts
3
3
  /**
4
4
  * Forwarding Yargs's default would turn ambient configuration into an explicit child override.
@@ -295,6 +295,14 @@ const DO_NOT_SUBSTITUTE_HINT = "Run this step's `command` exactly as written —
295
295
  * All four managers spell the whole-manifest install `install`.
296
296
  */
297
297
  const formatInstallCommand = (pm, packages, options) => `${pm} ${installArgs(pm, packages, options).join(" ")}`;
298
+ const commandEnv = (overlay, base = process.env) => {
299
+ const child = { ...base };
300
+ for (const key of Object.keys(child)) if (key.toUpperCase() === "NEON_API_KEY") delete child[key];
301
+ return overlay ? {
302
+ ...child,
303
+ ...overlay
304
+ } : child;
305
+ };
298
306
  /**
299
307
  * Run a command inheriting our stdio so the user sees install / link output
300
308
  * live and can answer any prompts the child raises. Resolves to whether it
@@ -306,10 +314,7 @@ const runCommand = (cmd, args, cwd, env) => new Promise((resolvePromise) => {
306
314
  cwd,
307
315
  stdio: "inherit",
308
316
  shell: process.platform === "win32",
309
- ...env ? { env: {
310
- ...process.env,
311
- ...env
312
- } } : {}
317
+ env: commandEnv(env)
313
318
  });
314
319
  child.on("error", (err) => {
315
320
  log.warning("Could not run `%s %s`: %s", cmd, args.join(" "), err instanceof Error ? err.message : String(err));
@@ -321,4 +326,4 @@ const runCommand = (cmd, args, cwd, env) => new Promise((resolvePromise) => {
321
326
  });
322
327
  });
323
328
  //#endregion
324
- export { DO_NOT_SUBSTITUTE_HINT, MISSING_BINARY_HINT, detectInvokingPackageManager, detectProjectPackageManager, formatExecCommand, formatInstallCommand, globalInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolveInvokingPackageManager, resolvePackageManager, runCommand };
329
+ export { DO_NOT_SUBSTITUTE_HINT, MISSING_BINARY_HINT, commandEnv, detectInvokingPackageManager, detectProjectPackageManager, formatExecCommand, formatInstallCommand, globalInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolveInvokingPackageManager, resolvePackageManager, runCommand };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "4.7.0",
3
+ "version": "4.9.0",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -63,9 +63,9 @@
63
63
  "yaml": "^2.9.0",
64
64
  "yargs": "17.7.2",
65
65
  "yoctocolors": "^2.1.2",
66
- "@neon/config": "1.0.4",
67
- "@neon/sdk": "2.3.0",
68
- "@neon/config-runtime": "1.0.4"
66
+ "@neon/config-runtime": "1.0.5",
67
+ "@neon/sdk": "3.0.0",
68
+ "@neon/config": "1.0.5"
69
69
  },
70
70
  "optionalDependencies": {
71
71
  "@napi-rs/keyring": "1.3.0",
@@ -96,9 +96,9 @@
96
96
  "tsx": "4.22.3",
97
97
  "typescript": "^5.9.0",
98
98
  "vitest": "^3.0.9",
99
- "@neon-internals/cli-core": "0.0.0",
100
- "@neon-internals/env-core": "0.0.4",
101
- "@neon/e2e-harness": "0.0.0"
99
+ "@neon-internals/env-core": "0.0.5",
100
+ "@neon/e2e-harness": "0.0.0",
101
+ "@neon-internals/cli-core": "0.0.0"
102
102
  },
103
103
  "publishConfig": {
104
104
  "access": "public",
@@ -120,17 +120,16 @@
120
120
  },
121
121
  "scripts": {
122
122
  "generateParams": "tsx generateOptionsFromSpec.ts",
123
- "codegen": "node scripts/set-vsx-gallery.mjs",
124
123
  "clean": "rm -rf dist",
125
- "build": "pnpm codegen && pnpm generateParams && tsc --noEmit && tsdown && cp src/*.html ./dist",
126
- "build:internal": "node scripts/set-vsx-gallery.mjs https://cursor-vsx-proxy.cloud.databricks.com/gallery && pnpm generateParams && tsc --noEmit && tsdown && cp src/*.html ./dist",
124
+ "build": "pnpm generateParams && tsc --noEmit && tsdown && cp src/*.html ./dist",
125
+ "build:internal": "pnpm build",
127
126
  "bundle": "node pkg.js",
128
- "typecheck": "pnpm codegen && tsc --noEmit",
129
- "lint": "pnpm codegen && pnpm typecheck && biome check src",
127
+ "typecheck": "tsc --noEmit",
128
+ "lint": "pnpm typecheck && biome check src",
130
129
  "lint:fix": "pnpm typecheck && biome check src --write",
131
- "test": "pnpm codegen && pnpm --filter neonctl... build && vitest run",
132
- "test:ci": "pnpm codegen && pnpm build && vitest run",
133
- "test:e2e": "pnpm codegen && pnpm build && vitest run --config vitest.e2e.config.ts",
134
- "test:conformance": "pnpm codegen && vitest run --config tests/psql-conformance/vitest.config.ts"
130
+ "test": "pnpm --filter neonctl... build && vitest run",
131
+ "test:ci": "pnpm build && vitest run",
132
+ "test:e2e": "pnpm build && vitest run --config vitest.e2e.config.ts",
133
+ "test:conformance": "vitest run --config tests/psql-conformance/vitest.config.ts"
135
134
  }
136
135
  }
package/dist/init/auth.js DELETED
@@ -1,53 +0,0 @@
1
- import { l as interpretCredentials, t as storeFor } from "../_chunks/credential_io-B1JxWELN.js";
2
- import { l as locationForName } from "../_chunks/profiles-D95AeZ5v.js";
3
- import { npxNeonArgs, selectedConfigDir, selectedProfileName } from "./profile_cli.js";
4
- import { log } from "@clack/prompts";
5
- import { execa } from "execa";
6
- //#region src/init/auth.ts
7
- /**
8
- * Ensures the Neon CLI is authenticated by running a command that triggers auth if needed
9
- * This will automatically start the OAuth flow if the user isn't already authenticated
10
- */
11
- async function ensureNeonctlAuth(options) {
12
- const quiet = options?.json === true;
13
- if (await getNeonctlAccessToken()) return true;
14
- try {
15
- await execa("npx", npxNeonArgs(["me"]), {
16
- stdio: "inherit",
17
- env: {
18
- ...process.env,
19
- CI: void 0
20
- }
21
- });
22
- return true;
23
- } catch (error) {
24
- const msg = error instanceof Error ? error.message : "Unknown error";
25
- if (!quiet) {
26
- 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.");
27
- else log.error(`Authentication failed: ${msg}`);
28
- }
29
- return false;
30
- }
31
- }
32
- /**
33
- * Checks whether the Neon CLI has stored OAuth credentials.
34
- */
35
- async function isAuthenticated() {
36
- return await getNeonctlAccessToken() !== null;
37
- }
38
- /**
39
- * Sharing the reader keeps init aligned with CLI path and API-key handling.
40
- * Unreadable credentials throw so browser sign-in cannot overwrite them.
41
- */
42
- async function getNeonctlAccessToken() {
43
- const dir = selectedConfigDir();
44
- const at = locationForName(dir, selectedProfileName());
45
- const loaded = storeFor(dir).read(at);
46
- if (loaded === null) return null;
47
- const credential = interpretCredentials(loaded.credentials, at, loaded.backend);
48
- if (credential.kind === "api_key") return credential.apiKey;
49
- const token = loaded.credentials.access_token;
50
- return typeof token === "string" && token.trim() !== "" ? token : null;
51
- }
52
- //#endregion
53
- export { ensureNeonctlAuth, isAuthenticated };
@@ -1,4 +0,0 @@
1
- //#region src/init/build_config.ts
2
- const INTERNAL_VSX_GALLERY = "";
3
- //#endregion
4
- export { INTERNAL_VSX_GALLERY };
@@ -1,63 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- //#region src/init/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 };
@@ -1,19 +0,0 @@
1
- import { detectInstalledAgents } from "../mcp/agents.js";
2
- import "./agents.js";
3
- import { resolve } from "node:path";
4
- //#region src/init/editors.ts
5
- function getVSCodeGlobalConfigDir(homeDir) {
6
- const platform = process.platform;
7
- if (platform === "darwin") return resolve(homeDir, "Library", "Application Support", "Code", "User");
8
- if (platform === "linux") return resolve(homeDir, ".config", "Code", "User");
9
- if (platform === "win32") {
10
- const appData = process.env.APPDATA;
11
- if (appData) return resolve(appData, "Code", "User");
12
- }
13
- return null;
14
- }
15
- async function detectAvailableEditors(_homeDir) {
16
- return detectInstalledAgents();
17
- }
18
- //#endregion
19
- export { detectAvailableEditors, getVSCodeGlobalConfigDir };
@@ -1,61 +0,0 @@
1
- import { neonInitAgentCmd } from "./profile_cli.js";
2
- //#region src/init/enrich_output.ts
3
- /**
4
- * Converts a phase's `args` (e.g. ["neon-auth", "--json", "--setup"]) into the
5
- * `neon init --agent --data` invocation that reaches the same handler.
6
- */
7
- function argsToCommand(args) {
8
- const data = {};
9
- let i = 0;
10
- if (args.length > 0 && !args[0].startsWith("-")) {
11
- data.step = args[0];
12
- i = 1;
13
- }
14
- while (i < args.length) {
15
- const arg = args[i];
16
- if (arg.startsWith("--")) {
17
- const key = arg.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
18
- const next = args[i + 1];
19
- if (key === "json") {
20
- i += 1;
21
- continue;
22
- }
23
- if (key === "profile" || key === "configDir") {
24
- i += next !== void 0 && !next.startsWith("-") ? 2 : 1;
25
- continue;
26
- }
27
- if (next !== void 0 && !next.startsWith("-")) {
28
- data[key] = next;
29
- i += 2;
30
- } else {
31
- data[key] = true;
32
- i += 1;
33
- }
34
- } else i += 1;
35
- }
36
- return neonInitAgentCmd(data);
37
- }
38
- /**
39
- * Walks a phase response object and:
40
- * 1. Replaces `args` arrays with `command` strings (neon init --data format)
41
- * 2. Renames `run_neon_init` → `run_shell_command`
42
- * 3. Adds a description to finalize steps
43
- */
44
- function enrichResponse(obj) {
45
- if (obj === null || typeof obj !== "object") return obj;
46
- if (Array.isArray(obj)) return obj.map(enrichResponse);
47
- const record = obj;
48
- const result = {};
49
- for (const [key, value] of Object.entries(record)) result[key] = enrichResponse(value);
50
- if (Array.isArray(result.args)) {
51
- result.command = argsToCommand(result.args);
52
- delete result.args;
53
- }
54
- if (result.type === "run_neon_init") {
55
- result.type = "run_shell_command";
56
- if (typeof result.command === "string" && result.command.includes("\"step\":\"finalize\"")) result.description = "Run this command to complete the setup. This is the final step — do not run any other neon init commands after this.";
57
- }
58
- return result;
59
- }
60
- //#endregion
61
- export { enrichResponse };
@@ -1,154 +0,0 @@
1
- import { NEON_EXTENSION_ID, downloadVsix } from "./vsix.js";
2
- import { existsSync } from "node:fs";
3
- import { unlink } from "node:fs/promises";
4
- import { execa } from "execa";
5
- //#region src/init/extension.ts
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
- * Checks if the extension is installed by querying the editor's extension list
103
- */
104
- async function isExtensionInstalled(editor) {
105
- const command = await findEditorCommand(editor);
106
- if (!command) return false;
107
- try {
108
- return (await execa(command, ["--list-extensions"], { timeout: 5e3 })).stdout.includes(NEON_EXTENSION_ID);
109
- } catch {
110
- return false;
111
- }
112
- }
113
- /**
114
- * Installs the Neon Local Connect extension for VS Code or Cursor.
115
- *
116
- * Strategy:
117
- * 1. Try `--install-extension <id>` directly (uses the editor's configured marketplace)
118
- * 2. If that fails, download .vsix (from proxy or Open VSX) and install locally
119
- * 3. Set NEON_VSX_GALLERY_URL to use a corporate proxy for the download
120
- */
121
- async function installExtension(editor) {
122
- const command = await findEditorCommand(editor);
123
- if (!command) return false;
124
- try {
125
- await execa(command, ["--install-extension", NEON_EXTENSION_ID], {
126
- stdio: "pipe",
127
- timeout: 6e4
128
- });
129
- return true;
130
- } catch {}
131
- const vsixPath = await downloadVsix();
132
- if (!vsixPath) return false;
133
- try {
134
- await execa(command, ["--install-extension", vsixPath], {
135
- stdio: "pipe",
136
- timeout: 6e4
137
- });
138
- return true;
139
- } catch {
140
- return false;
141
- } finally {
142
- try {
143
- await unlink(vsixPath);
144
- } catch {}
145
- }
146
- }
147
- /**
148
- * Returns the editor types that should use extension installation (vs MCP)
149
- */
150
- function usesExtension(editor) {
151
- return editor === "VS Code" || editor === "Cursor";
152
- }
153
- //#endregion
154
- export { findEditorCommand, installExtension, isExtensionInstalled, usesExtension };