neon 4.7.0 → 4.8.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 (56) hide show
  1. package/README.md +37 -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/claim.js +1 -1
  11. package/dist/commands/init.js +80 -79
  12. package/dist/commands/link.js +78 -63
  13. package/dist/commands/mcp.js +1 -1
  14. package/dist/commands/plugins.js +4 -4
  15. package/dist/commands/profile.js +4 -4
  16. package/dist/commands/skills.js +1 -1
  17. package/dist/config.js +1 -1
  18. package/dist/credential_io.js +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/init/child.js +36 -0
  21. package/dist/init/plan.js +47 -0
  22. package/dist/init/tooling.js +43 -0
  23. package/dist/init/wizard.js +42 -0
  24. package/dist/mcp/targets.js +1 -1
  25. package/dist/plugins/plan.js +1 -1
  26. package/dist/plugins/targets.js +5 -1
  27. package/dist/retire_credential.js +1 -1
  28. package/dist/utils/middlewares.js +1 -1
  29. package/dist/utils/package_manager.js +10 -5
  30. package/package.json +13 -14
  31. package/dist/init/auth.js +0 -53
  32. package/dist/init/build_config.js +0 -4
  33. package/dist/init/detect_agent.js +0 -63
  34. package/dist/init/editors.js +0 -19
  35. package/dist/init/enrich_output.js +0 -61
  36. package/dist/init/extension.js +0 -154
  37. package/dist/init/inspect.js +0 -249
  38. package/dist/init/interactive.js +0 -529
  39. package/dist/init/neonctl.js +0 -139
  40. package/dist/init/orchestrate.js +0 -189
  41. package/dist/init/phases/auth.js +0 -195
  42. package/dist/init/phases/cleanup.js +0 -27
  43. package/dist/init/phases/db.js +0 -257
  44. package/dist/init/phases/getting_started.js +0 -218
  45. package/dist/init/phases/mcp.js +0 -219
  46. package/dist/init/phases/migrations.js +0 -250
  47. package/dist/init/phases/neon_auth.js +0 -116
  48. package/dist/init/phases/setup.js +0 -582
  49. package/dist/init/phases/skills.js +0 -86
  50. package/dist/init/phases/status.js +0 -82
  51. package/dist/init/profile_cli.js +0 -56
  52. package/dist/init/resolve_context.js +0 -105
  53. package/dist/init/route_command.js +0 -93
  54. package/dist/init/skills.js +0 -206
  55. package/dist/init/types.js +0 -1
  56. package/dist/init/vsix.js +0 -89
@@ -1,94 +1,95 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
- import { closeAnalytics, sendError } from "../analytics.js";
3
- import { detectAgent } from "../init/detect_agent.js";
4
- import { enrichResponse } from "../init/enrich_output.js";
5
- import { interactiveInit } from "../init/interactive.js";
6
- import { orchestrate } from "../init/orchestrate.js";
7
- import { routeDataStep } from "../init/route_command.js";
8
- import { writeAllSync } from "../utils/write_sync.js";
2
+ import { log } from "../log.js";
3
+ import { readContextFile } from "../context.js";
4
+ import { getCliName } from "../utils/cli_name.js";
5
+ import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
6
+ import { initChildEnv, spawnCliChild } from "../init/child.js";
7
+ import { bootstrapInitStep, directoryIsEmpty, planExistingInit } from "../init/plan.js";
8
+ import { runAgentTooling, runInitSteps } from "../init/tooling.js";
9
+ import { helpEpilogue } from "../utils/help_text.js";
10
+ import { existsSync, readdirSync } from "node:fs";
11
+ import { resolve } from "node:path";
9
12
  //#region src/commands/init.ts
10
13
  var init_exports = /* @__PURE__ */ __exportAll({
11
14
  builder: () => builder,
12
15
  command: () => command,
13
16
  describe: () => describe,
14
- handler: () => handler
17
+ handler: () => handler,
18
+ initChildEnv: () => initChildEnv
15
19
  });
16
20
  const command = "init";
17
- const describe = "Initialize a project with Neon using your AI coding assistant";
18
- const builder = (yargs) => yargs.option("context-file", { hidden: true }).option("agent", {
19
- alias: "a",
21
+ const describe = "Set up this directory for Neon: agent tooling, a linked project, and neon.ts. In an empty directory, scaffolds a template first. Skills needs Node.js 22.20 or newer.";
22
+ const removedProtocol = () => `\`${getCliName()} init --agent\` and \`--data\` were removed. Run \`${getCliName()} init\` or \`${getCliName()} init -y\`.`;
23
+ const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file", { hidden: true }).option("yes", {
24
+ alias: "y",
20
25
  type: "boolean",
21
26
  default: false,
22
- describe: "Enable agent/JSON mode (agent type is auto-detected)."
27
+ describe: "Empty dir: bootstrap --default. Otherwise plugin when a project plugin agent is detected, else skills and MCP, then link --yes and config init --services none. link --yes still asks for a project unless one is already linked"
28
+ }).option("agent", {
29
+ hidden: true,
30
+ type: "boolean"
23
31
  }).option("data", {
24
- type: "string",
25
- describe: "JSON object with a \"step\" field to route to a specific phase and phase-specific options."
26
- }).option("skip-migrations", {
27
- type: "boolean",
28
- default: false,
29
- describe: "Skip the migrations phase."
30
- }).option("preview", {
31
- type: "boolean",
32
- default: false,
33
- describe: "Enable preview features (e.g. project bootstrapping from templates)."
34
- }).strict(false);
35
- /**
36
- * The agent-facing half of `neon init` speaks JSON, and it speaks it on **stdout**:
37
- * one object, no prefix, nothing else. `log.info` would prefix every line with
38
- * `INFO: ` and send it to stderr, which is right for a diagnostic and wrong for the
39
- * payload an agent is expected to parse.
40
- */
41
- const writeAgentResponse = (result) => {
42
- writeAllSync(1, `${JSON.stringify(enrichResponse(result), null, 2)}\n`);
43
- };
44
- /**
45
- * A failure has to arrive in the shape the caller asked for. An agent parses stdout and
46
- * has no branch for "empty stdout, exit 1" — it cannot tell a broken credentials file
47
- * from a phase that legitimately produced nothing — so the error goes out as JSON too.
48
- */
49
- const writeAgentFailure = (error) => {
50
- writeAllSync(1, `${JSON.stringify({
51
- success: false,
52
- error: error.message
53
- }, null, 2)}\n`);
54
- };
55
- /** ` at position 12`, or nothing when the parser did not report one. */
56
- const parsePosition = (parseError) => {
57
- const at = (parseError instanceof Error ? parseError.message : "").match(/at position (\d+)/);
58
- return at === null ? "" : ` at position ${at[1]}`;
32
+ hidden: true,
33
+ type: "string"
34
+ }).option("output", {
35
+ alias: "o",
36
+ hidden: true,
37
+ describe: "Not supported; the commands init runs print their own output"
38
+ }).example("$0 init", "Empty dir: bootstrap (scaffold, agent tooling, link). Existing app: agent tooling, link, config init").example("$0 init -y", "Same steps, using each child's defaults").epilogue(helpEpilogue("Interactive agent setup: plugin (recommended), skills and MCP separately, or skip agent setup. Never both plugin and skills+MCP.", "-y installs the plugin when Cursor, Claude Code, or Codex is detected in the project. Otherwise skills and MCP. Then link unless already linked. link --yes may still ask for a project.")).check((argv) => {
39
+ if (argv.agent === true || argv.data !== void 0) throw new Error(removedProtocol());
40
+ if (argv.help !== true && (argv.output === "json" || argv.output === "yaml")) throw new Error(`\`${getCliName()} init\` does not support --output. The commands it runs print their own output.`);
41
+ return true;
42
+ }).strict();
43
+ const isLinked = (contextFile) => {
44
+ const projectId = readContextFile(contextFile).projectId;
45
+ return typeof projectId === "string" && projectId.length > 0;
59
46
  };
60
- const handler = async (argv) => {
61
- const agent = (argv.agent || !process.stdin.isTTY ? detectAgent() : null) || void 0;
62
- const isAgentMode = argv.agent || agent !== void 0;
63
- try {
64
- if (argv.data && isAgentMode) {
65
- let data;
66
- try {
67
- data = JSON.parse(argv.data);
68
- } catch (parseError) {
69
- throw new Error(`Invalid JSON in --data flag${parsePosition(parseError)}. Expected a JSON object.`);
70
- }
71
- if (typeof data.step === "string") {
72
- writeAgentResponse(await routeDataStep(data, agent));
73
- return;
74
- }
75
- }
76
- if (isAgentMode) writeAgentResponse(await orchestrate({
77
- agent,
78
- skipMigrations: argv.skipMigrations,
79
- preview: argv.preview
80
- }));
81
- else await interactiveInit({ preview: argv.preview });
82
- } catch (error) {
83
- const cause = error instanceof Error ? error : new Error(String(error));
84
- if (isAgentMode) {
85
- sendError(cause, "NEON_INIT_FAILED");
86
- writeAgentFailure(cause);
87
- await closeAnalytics();
88
- process.exit(1);
89
- }
90
- throw cause;
47
+ const handler = async (props) => {
48
+ if (props.output === "json" || props.output === "yaml") throw new Error(`\`${getCliName()} init\` does not support --output. The commands it runs print their own output.`);
49
+ const cwd = props.cwd ?? process.cwd();
50
+ const names = existsSync(cwd) ? readdirSync(cwd) : [];
51
+ const contextFile = resolve(cwd, props.contextFile);
52
+ const yes = props.yes === true;
53
+ const run = props.run ?? spawnCliChild;
54
+ const explicitKey = props.profile ? "" : credentialInputs().apiKeyFlag;
55
+ const authEnv = explicitKey ? { NEON_API_KEY: explicitKey } : void 0;
56
+ const forward = {
57
+ ...props.configDir ? { configDir: props.configDir } : {},
58
+ ...props.profile ? { profile: props.profile } : {},
59
+ apiHost: props.apiHost,
60
+ contextFile,
61
+ ...props.analytics === false ? { analytics: false } : {}
62
+ };
63
+ if (directoryIsEmpty(names)) {
64
+ await runInitSteps([bootstrapInitStep(yes)], {
65
+ cwd,
66
+ run,
67
+ forward,
68
+ authEnv
69
+ });
70
+ log.info("Done.");
71
+ return;
91
72
  }
73
+ await runAgentTooling({
74
+ cwd,
75
+ yes,
76
+ run,
77
+ forward,
78
+ authEnv,
79
+ ...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
80
+ ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {}
81
+ });
82
+ await runInitSteps(planExistingInit({
83
+ linked: isLinked(contextFile),
84
+ yes,
85
+ agentSetup: "skip"
86
+ }), {
87
+ cwd,
88
+ run,
89
+ forward,
90
+ authEnv
91
+ });
92
+ log.info("Done.");
92
93
  };
93
94
  //#endregion
94
- export { builder, command, describe, handler, init_exports as t };
95
+ export { builder, command, describe, handler, initChildEnv, init_exports as t };
@@ -25,7 +25,7 @@ const nonInteractiveLinkCommands = () => {
25
25
  return [
26
26
  `${cli} orgs list --output json`,
27
27
  `${cli} projects list --org-id <org-id> --output json`,
28
- `${cli} link --project-id <project-id>`,
28
+ `${cli} link --project-id <project-id> [--branch <name> | -y]`,
29
29
  `${cli} link --org-id <org-id> --project-name <name> --region-id aws-us-east-2`
30
30
  ];
31
31
  };
@@ -53,7 +53,7 @@ const builder = (argv) => argv.usage("$0 link [options]").options({
53
53
  },
54
54
  branch: {
55
55
  alias: "branch-id",
56
- describe: `Branch name or ID to pin in the context (resolved to its ID before writing). Without it, link only resolves the org and project — pin a branch with \`${getCliName()} checkout <branch>\` (link never guesses a default).`,
56
+ describe: "Branch name or ID to pin in the context (resolved to its name before writing).",
57
57
  type: "string"
58
58
  },
59
59
  params: {
@@ -66,7 +66,7 @@ const builder = (argv) => argv.usage("$0 link [options]").options({
66
66
  },
67
67
  yes: {
68
68
  alias: "y",
69
- describe: "Skip the \"already linked\" confirmation in interactive mode and re-link anyway.",
69
+ describe: "Skip the \"already linked\" confirmation, and pin the project's default branch when linking a project that has more than one.",
70
70
  type: "boolean",
71
71
  default: false
72
72
  },
@@ -86,7 +86,8 @@ const builder = (argv) => argv.usage("$0 link [options]").options({
86
86
  default: true
87
87
  }
88
88
  }).example([
89
- ["$0 link --project-id polished-snowflake-12345678", `Link an existing project (org is inferred); pin a branch later with '${getCliName()} checkout'`],
89
+ ["$0 link --project-id polished-snowflake-12345678", "Link an existing project (org is inferred). Pins the only branch; several prompt in a TTY"],
90
+ ["$0 link --project-id polished-snowflake-12345678 -y", "Same, pinning the project's default branch when several exist"],
90
91
  ["$0 link --org-id org-… --project-name my-app --region-id aws-us-east-2", "Create a new project and link it"],
91
92
  ["$0 link --branch-id br-…", "Pin a branch in the already-linked project"],
92
93
  ["$0 link --no-checks --org-id org-… --project-id polished-snowflake-12345678", "Write the context offline (no API calls, no verification)"],
@@ -166,16 +167,8 @@ const validateInputs = (inputs) => {
166
167
  if (inputs.projectName && inputs.branch) throw new Error(`Conflicting inputs: --branch pins a branch of an existing project, but --project-name creates a new one. Create the project first, then \`${getCliName()} checkout <branch>\`.`);
167
168
  };
168
169
  /**
169
- * Whether the inputs (combined with the existing `.neon`) fully determine what
170
- * to write without prompting the user. Everything else falls back to the
171
- * interactive picker (TTY) or the CI guard.
172
- *
173
- * - `--project-id` is always enough: the org is inferred from the project and
174
- * the branch is left to an explicit `checkout` (never auto-defaulted).
175
- * - `--org-id --project-name --region-id` fully describes a project to create.
176
- * - `--branch-id` needs a project, which it takes from the existing `.neon`.
177
- * - `--org-id` on its own just records the default org (merged into any
178
- * existing context).
170
+ * Branch selection may still prompt after org and project resolve without the
171
+ * org/project wizard.
179
172
  */
180
173
  const canResolveNonInteractively = (inputs, existing) => {
181
174
  if (inputs.projectId) return true;
@@ -295,20 +288,65 @@ const resolveOrgForProject = async (props, inputs, existing, projectId) => {
295
288
  if (projectId === existing.projectId && existing.orgId) return existing.orgId;
296
289
  };
297
290
  /**
298
- * Resolve the branch to persist alongside a project in non-interactive mode.
299
- *
300
- * `link` never guesses the project's default branch — that's `checkout`'s job —
301
- * so the only sources are an explicit `--branch` (name or id, verified and
302
- * normalized to its name) or a branch already pinned for the *same* project (so
303
- * re-linking it doesn't drop your checked-out branch). Reading the existing
304
- * branch via {@link contextBranch} also recovers a legacy `branchId` field.
291
+ * Keep an existing pin when re-linking the same project so `-y` does not reset
292
+ * a checkout.
305
293
  */
306
294
  const resolvePinnedBranch = async (props, inputs, existing, projectId) => {
307
295
  if (inputs.branch) {
308
296
  const branch = await resolveBranchRef(props, projectId, inputs.branch);
309
- return branchPersistValue(branch);
297
+ return {
298
+ branch: branchPersistValue(branch),
299
+ empty: false
300
+ };
301
+ }
302
+ if (projectId === existing.projectId) {
303
+ const pinned = contextBranch(existing);
304
+ if (pinned) return {
305
+ branch: pinned,
306
+ empty: false
307
+ };
308
+ }
309
+ return resolveBranchFromList(props, projectId);
310
+ };
311
+ const resolveBranchFromList = async (props, projectId) => {
312
+ const { data } = await props.apiClient.listProjectBranches({ projectId });
313
+ const branches = data.branches;
314
+ if (branches.length === 0) return { empty: true };
315
+ if (branches.length === 1) {
316
+ const [only] = branches;
317
+ if (!only) return { empty: true };
318
+ return {
319
+ branch: branchPersistValue(only),
320
+ empty: false
321
+ };
322
+ }
323
+ if (props.yes) {
324
+ const def = branches.find((b) => b.default);
325
+ if (!def) throw new Error(`Project '${projectId}' has no default branch. Pass --branch <name> to pin one.`);
326
+ return {
327
+ branch: branchPersistValue(def),
328
+ empty: false
329
+ };
330
+ }
331
+ if (canPromptInteractively()) {
332
+ const picked = await pickBranchInteractively(branches, {
333
+ message: "Which branch would you like to link?",
334
+ nonInteractiveMessage: `No branch could be selected without an interactive terminal. Re-run \`${getCliName()} link\` interactively, or \`${getCliName()} checkout <branch>\` to pin one.`
335
+ });
336
+ if (picked.kind === "existing") {
337
+ const existing = branches.find((b) => b.id === picked.branchId);
338
+ return {
339
+ branch: existing ? branchPersistValue(existing) : picked.branchId,
340
+ empty: false
341
+ };
342
+ }
343
+ await createBranch(props.apiClient, projectId, picked.name, branches);
344
+ return {
345
+ branch: picked.name,
346
+ empty: false
347
+ };
310
348
  }
311
- if (projectId === existing.projectId) return contextBranch(existing);
349
+ return { empty: false };
312
350
  };
313
351
  const runNonInteractive = async (props, inputs, existing) => {
314
352
  if (inputs.projectName) {
@@ -337,36 +375,38 @@ const runNonInteractive = async (props, inputs, existing) => {
337
375
  }
338
376
  if (inputs.projectId) {
339
377
  const orgId = await resolveOrgForProject(props, inputs, existing, inputs.projectId);
340
- const branch = await resolvePinnedBranch(props, inputs, existing, inputs.projectId);
378
+ const resolved = await resolvePinnedBranch(props, inputs, existing, inputs.projectId);
341
379
  applyContext(props.contextFile, {
342
380
  orgId,
343
381
  projectId: inputs.projectId,
344
- branch
382
+ branch: resolved.branch
345
383
  });
346
384
  await finalizeLink(props, {
347
385
  contextFile: props.contextFile,
348
386
  orgId,
349
387
  projectId: inputs.projectId,
350
- branch,
351
- created: false
388
+ branch: resolved.branch,
389
+ created: false,
390
+ empty: resolved.empty
352
391
  });
353
392
  return;
354
393
  }
355
394
  if (inputs.branch && existing.projectId) {
356
395
  const projectId = existing.projectId;
357
396
  const orgId = await resolveOrgForProject(props, inputs, existing, projectId);
358
- const branch = await resolvePinnedBranch(props, inputs, existing, projectId);
397
+ const resolved = await resolvePinnedBranch(props, inputs, existing, projectId);
359
398
  applyContext(props.contextFile, {
360
399
  orgId,
361
400
  projectId,
362
- branch
401
+ branch: resolved.branch
363
402
  });
364
403
  await finalizeLink(props, {
365
404
  contextFile: props.contextFile,
366
405
  orgId,
367
406
  projectId,
368
- branch,
369
- created: false
407
+ branch: resolved.branch,
408
+ created: false,
409
+ empty: resolved.empty
370
410
  });
371
411
  return;
372
412
  }
@@ -425,20 +465,21 @@ const runInteractive = async (props, inputs) => {
425
465
  const projects = await listAllProjects(props, orgId);
426
466
  const action = await promptProjectChoice(projects, inputs.projectName);
427
467
  if (action.type === "existing") {
428
- const branch = await resolveInteractiveBranch(props, action.projectId);
468
+ const resolved = await resolveBranchFromList(props, action.projectId);
429
469
  applyContext(props.contextFile, {
430
470
  orgId,
431
471
  projectId: action.projectId,
432
- branch
472
+ branch: resolved.branch
433
473
  });
434
474
  await finalizeInteractiveLink(props, {
435
475
  contextFile: props.contextFile,
436
476
  orgId,
437
477
  projectId: action.projectId,
438
- branch,
478
+ branch: resolved.branch,
439
479
  created: false,
440
480
  projectName: action.name,
441
- regionId: action.regionId
481
+ regionId: action.regionId,
482
+ empty: resolved.empty
442
483
  });
443
484
  return;
444
485
  }
@@ -626,33 +667,6 @@ const listAllProjects = async (props, orgId) => {
626
667
  }
627
668
  return result;
628
669
  };
629
- /**
630
- * Resolve which branch to pin for an interactively-chosen project, returned as the value to
631
- * persist (its name when known, see {@link branchPersistValue}). When the project has a
632
- * single branch there is nothing to choose, so we pin it silently. Otherwise we offer the
633
- * shared branch picker (the same "+ Create a new branch…" + list as `neonctl checkout`),
634
- * creating the branch when the user opts to. This makes interactive `link` a full org →
635
- * project → branch flow; non-interactive `link` instead defers the branch to `checkout`.
636
- */
637
- const resolveInteractiveBranch = async (props, projectId) => {
638
- const { data } = await props.apiClient.listProjectBranches({ projectId });
639
- const branches = data.branches;
640
- if (branches.length <= 1) {
641
- const only = branches.find((b) => b.default) ?? branches[0];
642
- if (!only) throw new Error(`Could not find a default branch for project ${projectId}.`);
643
- return branchPersistValue(only);
644
- }
645
- const picked = await pickBranchInteractively(branches, {
646
- message: "Which branch would you like to link?",
647
- nonInteractiveMessage: `No branch could be selected without an interactive terminal. Re-run \`${getCliName()} link\` interactively, or \`${getCliName()} checkout <branch>\` to pin one.`
648
- });
649
- if (picked.kind === "existing") {
650
- const existing = branches.find((b) => b.id === picked.branchId);
651
- return existing ? branchPersistValue(existing) : picked.branchId;
652
- }
653
- await createBranch(props.apiClient, projectId, picked.name, branches);
654
- return picked.name;
655
- };
656
670
  const fetchRegions = async (props) => {
657
671
  try {
658
672
  const { data } = await props.apiClient.getActiveRegions();
@@ -704,7 +718,8 @@ const printSummary = (_props, summary) => {
704
718
  lines.push("Written offline (--no-checks): nothing was verified.");
705
719
  } else if (summary.projectId && !summary.branch && !summary.orgOnly) {
706
720
  lines.push("");
707
- lines.push(`No branch pinned. Run \`${getCliName()} checkout <branch>\` to pin a branch and pull its env vars.`);
721
+ if (summary.empty) lines.push(`This project has no branches, so none was pinned. Create a branch, then run \`${getCliName()} checkout <branch>\` to pin it and pull its env vars.`);
722
+ else lines.push(`No branch pinned. Run \`${getCliName()} checkout <branch>\` to pin a branch and pull its env vars.`);
708
723
  }
709
724
  lines.push("");
710
725
  process.stdout.write(`${lines.join("\n")}\n`);
@@ -4,10 +4,10 @@ import { readContextFile } from "../context.js";
4
4
  import { getCliName } from "../utils/cli_name.js";
5
5
  import { writer } from "../writer.js";
6
6
  import { noPassthrough, single } from "../utils/flags.js";
7
+ import { canPickAgentsInteractively } from "../utils/agent_picker.js";
7
8
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
8
9
  import { NEON_MCP_CATEGORIES, NEON_MCP_URL, existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
9
10
  import { mintMcpApiKey, mintedKeyRevokeCommand, withdrawMintedKey } from "../mcp/mint.js";
10
- import { canPickAgentsInteractively } from "../utils/agent_picker.js";
11
11
  import { mcpInstallableAgents, resolveInstallTargets } from "../mcp/targets.js";
12
12
  import { confirmMcpInstall } from "../mcp/wizard.js";
13
13
  import { resolveMcpPlan } from "../mcp/plan.js";
@@ -2,14 +2,14 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { log } from "../log.js";
3
3
  import { writer } from "../writer.js";
4
4
  import { noPassthrough } from "../utils/flags.js";
5
- import { helpCsv, helpEpilogue } from "../utils/help_text.js";
6
- import { NEON_MCP_URL } from "../mcp/install.js";
7
5
  import { getAgentDisplayName } from "../mcp/agents.js";
8
6
  import "../init/agents.js";
9
- import { canPickAgentsInteractively } from "../utils/agent_picker.js";
10
7
  import { pluginsInstallableAgents } from "../plugins/targets.js";
11
- import { resolvePluginsPlan } from "../plugins/plan.js";
8
+ import { canPickAgentsInteractively } from "../utils/agent_picker.js";
12
9
  import { NEON_PLUGIN_NAME, PLUGIN_SKILLS, PLUGIN_SOURCE, neonPluginsRetryCommand, pluginsAddArgs, runPluginsCli } from "../plugins/run.js";
10
+ import { helpCsv, helpEpilogue } from "../utils/help_text.js";
11
+ import { NEON_MCP_URL } from "../mcp/install.js";
12
+ import { resolvePluginsPlan } from "../plugins/plan.js";
13
13
  import { confirmPluginsInstall } from "../plugins/wizard.js";
14
14
  //#region src/commands/plugins.ts
15
15
  var plugins_exports = /* @__PURE__ */ __exportAll({
@@ -1,14 +1,14 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { t as writeSecretFile } from "../_chunks/secure_file-BucZj4yQ.js";
3
- import { c as describeScope, d as scopeOf, l as interpretCredentials, n as KEYRING_SERVICE, o as apiKeyCredentials, r as keyringAccount, s as credentialLabel, t as storeFor } from "../_chunks/credential_io-B1JxWELN.js";
3
+ import { c as describeScope, d as scopeOf, l as interpretCredentials, n as KEYRING_SERVICE, o as apiKeyCredentials, r as keyringAccount, s as credentialLabel, t as storeFor } from "../_chunks/credential_io-YeAxg9Xn.js";
4
4
  import { isCi } from "../env.js";
5
5
  import { log } from "../log.js";
6
6
  import { getApiClient, isNeonApiError } from "../api.js";
7
- import { o as isOwnedCredentialPath, r as credentialsPath } from "../_chunks/paths-g3DRqJiD.js";
7
+ import { a as isOwnedCredentialPath, n as credentialsPath } from "../_chunks/paths-DMq0Lt7a.js";
8
8
  import { setAuthContext } from "../auth_context.js";
9
- import { _ as upsertProfile, a as canDropProfilesFile, c as listProfiles, d as newProfileLocation, f as profilesFilePath, g as selectProfileName, h as resolveProfile, i as assertValidProfileName, l as locationForName, m as readProfiles, n as KEYRING_CREDENTIALS, o as credentialsDisplay, p as profilesUsingPath, r as assertProfilesUsable, s as isKeyringPointer, u as locationOf, v as CRED_STORAGE_FILE, y as CRED_STORAGE_KEYRING } from "../_chunks/profiles-D95AeZ5v.js";
9
+ import { _ as upsertProfile, a as canDropProfilesFile, c as listProfiles, d as newProfileLocation, f as profilesFilePath, g as selectProfileName, h as resolveProfile, i as assertValidProfileName, l as locationForName, m as readProfiles, n as KEYRING_CREDENTIALS, o as credentialsDisplay, p as profilesUsingPath, r as assertProfilesUsable, s as isKeyringPointer, u as locationOf, v as CRED_STORAGE_FILE, y as CRED_STORAGE_KEYRING } from "../_chunks/profiles-CvnFEQyd.js";
10
10
  import { auth, revokeToken } from "../auth.js";
11
- import { t as credentialInputs } from "../_chunks/auth_selection-ktL6uoEe.js";
11
+ import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
12
12
  import { readOutgoingCredential, retirePreviousCredential, revokeTokenSet, withdrawKey } from "../retire_credential.js";
13
13
  import { authFlow, usableCredential } from "./auth.js";
14
14
  import { writer } from "../writer.js";
@@ -2,10 +2,10 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { log } from "../log.js";
3
3
  import { writer } from "../writer.js";
4
4
  import { noPassthrough } from "../utils/flags.js";
5
- import { helpCsv, helpEpilogue } from "../utils/help_text.js";
6
5
  import { getAgentDisplayName } from "../mcp/agents.js";
7
6
  import "../init/agents.js";
8
7
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
8
+ import { helpCsv, helpEpilogue } from "../utils/help_text.js";
9
9
  import { skillsHelpValues, skillsYesHelp } from "../skills/catalog.js";
10
10
  import { mappedSkillsAgentNames, skillsInstallableAgents } from "../skills/targets.js";
11
11
  import { confirmSkillsInstall, confirmSkillsUpdate } from "../skills/wizard.js";
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { isCi } from "./env.js";
2
- import { a as isInsideConfigDir, i as defaultDir, o as isOwnedCredentialPath, r as credentialsPath, t as CREDENTIALS_FILE } from "./_chunks/paths-g3DRqJiD.js";
2
+ import { a as isOwnedCredentialPath, i as isInsideConfigDir, n as credentialsPath, r as defaultDir, t as CREDENTIALS_FILE } from "./_chunks/paths-DMq0Lt7a.js";
3
3
  import { existsSync, mkdirSync } from "node:fs";
4
4
  //#region src/config.ts
5
5
  const ensureConfigDir = ({ "config-dir": configDirArg, "force-auth": forceAuth }) => {
@@ -1,2 +1,2 @@
1
- import { t as storeFor } from "./_chunks/credential_io-B1JxWELN.js";
1
+ import { t as storeFor } from "./_chunks/credential_io-YeAxg9Xn.js";
2
2
  export { storeFor };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { log } from "./log.js";
2
2
  import pkg_default from "./pkg.js";
3
3
  import { isNeonApiError, messageFromBody } from "./api.js";
4
- import { i as defaultDir } from "./_chunks/paths-g3DRqJiD.js";
4
+ import { r as defaultDir } from "./_chunks/paths-DMq0Lt7a.js";
5
5
  import { ensureConfigDir } from "./config.js";
6
6
  import { authFailureMessage, credentialsToClearOn401, getAuthContext } from "./auth_context.js";
7
7
  import { currentContextFile, enrichFromContext } from "./context.js";
@@ -0,0 +1,36 @@
1
+ import { spawn } from "node:child_process";
2
+ //#region src/init/child.ts
3
+ const AUTH_CHILD = /* @__PURE__ */ new Set([
4
+ "bootstrap",
5
+ "link",
6
+ "mcp"
7
+ ]);
8
+ const initChildEnv = (command, overlay, base = process.env) => {
9
+ const child = { ...base };
10
+ if (command === void 0 || !AUTH_CHILD.has(command)) {
11
+ for (const key of Object.keys(child)) if (key.toUpperCase() === "NEON_API_KEY") delete child[key];
12
+ }
13
+ return overlay ? {
14
+ ...child,
15
+ ...overlay
16
+ } : child;
17
+ };
18
+ const spawnCliChild = async (argv, cwd, overlay) => {
19
+ const cli = process.argv[1];
20
+ if (!cli) throw new Error("Cannot re-exec the Neon CLI: process.argv[1] is missing.");
21
+ return new Promise((resolve) => {
22
+ const child = spawn(process.execPath, [cli, ...argv], {
23
+ cwd,
24
+ stdio: "inherit",
25
+ env: initChildEnv(argv[0], overlay)
26
+ });
27
+ child.on("error", () => {
28
+ resolve(false);
29
+ });
30
+ child.on("close", (code) => {
31
+ resolve(code === 0);
32
+ });
33
+ });
34
+ };
35
+ //#endregion
36
+ export { AUTH_CHILD, initChildEnv, spawnCliChild };
@@ -0,0 +1,47 @@
1
+ import { isAbsolute, join, relative, resolve } from "node:path";
2
+ //#region src/init/plan.ts
3
+ const directoryIsEmpty = (names) => names.filter((name) => name !== ".git").length === 0;
4
+ const bootstrapInitStep = (yes) => yes ? [
5
+ "bootstrap",
6
+ ".",
7
+ "--default"
8
+ ] : ["bootstrap", "."];
9
+ const projectContextFile = (projectDir, contextFile) => {
10
+ const resolved = isAbsolute(contextFile) ? contextFile : resolve(projectDir, contextFile);
11
+ const rel = relative(projectDir, resolved);
12
+ if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return join(projectDir, ".neon");
13
+ return resolved;
14
+ };
15
+ const planAgentSteps = (input) => {
16
+ const y = input.yes ? ["-y"] : [];
17
+ if (input.agentSetup === "plugin") return [["plugins", ...y]];
18
+ if (input.agentSetup === "skills-mcp") return [["skills", ...y], ["mcp", ...y]];
19
+ return [];
20
+ };
21
+ const planExistingInit = (input) => {
22
+ const steps = [...planAgentSteps(input)];
23
+ if (!input.linked) steps.push(input.yes ? ["link", "--yes"] : ["link"]);
24
+ steps.push(input.yes ? [
25
+ "config",
26
+ "init",
27
+ "--services",
28
+ "none"
29
+ ] : ["config", "init"]);
30
+ return steps;
31
+ };
32
+ const resolveInitAgentSetup = async (input) => {
33
+ if (input.yes) return input.hasProjectPlugins ? "plugin" : "skills-mcp";
34
+ if (input.interactive) return input.pick();
35
+ return "skills-mcp";
36
+ };
37
+ const childArgv = (step, forward) => {
38
+ const args = [...step];
39
+ if (forward.configDir) args.push("--config-dir", forward.configDir);
40
+ if (forward.profile) args.push("--profile", forward.profile);
41
+ args.push("--api-host", forward.apiHost);
42
+ args.push("--context-file", forward.contextFile);
43
+ if (forward.analytics === false) args.push("--no-analytics");
44
+ return args;
45
+ };
46
+ //#endregion
47
+ export { bootstrapInitStep, childArgv, directoryIsEmpty, planAgentSteps, planExistingInit, projectContextFile, resolveInitAgentSetup };
@@ -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 };