neon 8.13.1 → 8.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -891,6 +891,8 @@ Unauthenticated `-y` skips linking and prints the next step: sign up at https://
891
891
 
892
892
  When init picks MCP auth (no `--mcp-auth`) and the CLI credential is an organization or project-scoped API key, which cannot mint the MCP server's key, init configures MCP with OAuth instead and continues. With `--mcp-auth api-key`, init skips MCP, finishes linking and `neon.ts`, then reports `Neon setup failed.` with the `neon mcp` commands to retry and exits 1.
893
893
 
894
+ When the Neon plugin, skills, or MCP setup fails for an agent, init runs the remaining agent steps, links (or writes `.neon` offline), and writes `neon.ts` as it would have, then reports `Neon setup failed.` with the error and the `neon plugins`, `neon skills`, or `neon mcp` command to retry, and exits 1.
895
+
894
896
  Custom asks how to add Neon to coding agents (plugin, skills and MCP separately, or skip), then how to get a project (sign in and link, or a claimable project when you are not signed in), then which services `neon.ts` should declare. Skills and MCP use separate agent pickers, and either can be skipped. Postgres is listed first as always included. The package manager is inferred from the directory; Custom asks only when none is detected. `--skill` selects skills (not the plugin) and skips that picker. MCP flags (`--mcp-config-location`, `--mcp-auth`, `--mcp-project-scoped`) select skills and MCP. `--mcp-config-location` is where the config is written: `global` maps to `neon mcp`, and `project` maps to `neon mcp --project`. With `-y`, the location defaults to `global`; interactive Custom asks. If some selected agents cannot write that location, init warns and skips MCP for those agents while continuing with the supported agents. It fails when none of the selected agents support the location. `--mcp-project-scoped` limits MCP tools to the linked Neon project by passing its ID to `neon mcp --project-id`. Omitted is false, including in Custom; there is no prompt. `--no-agent-setup` skips agent setup.
895
897
 
896
898
  Empty directories are set up in place. `neon init` does not scaffold starter files.
@@ -132,7 +132,7 @@ const pluginsInstallError = (outcome) => {
132
132
  global: scope === "global"
133
133
  });
134
134
  if (first.message.includes("needs npx (Node.js)")) return new Error(first.message);
135
- const detail = failed.map((row) => row.message).join("\n");
135
+ const detail = [...new Set(failed.map((row) => row.message))].join("\n");
136
136
  if (failed.length === rows.length) return /* @__PURE__ */ new Error(`${detail}\nRetry with: ${retry}`);
137
137
  return /* @__PURE__ */ new Error(`Failed to install the Neon plugin for: ${failed.flatMap((row) => row.agents).join(", ")}.\n${detail}\nRetry with: ${retry}`);
138
138
  };
package/dist/analytics.js CHANGED
@@ -356,4 +356,4 @@ const getAnalyticsEventProperties = (args) => ({
356
356
  githubEnvVars: getGithubEnvVars(process.env)
357
357
  });
358
358
  //#endregion
359
- export { analyticsIdentity, analyticsMiddleware, closeAnalytics, commandSuccessProperties, getAnalyticsEventProperties, getErrorAnalyticsEventProperties, initAnalyticsClientMiddleware, isAnalyticsActive, recordCommandSuccessExtras, recordScaffoldedTemplate, sendError, storedCredentialAttribution, takeCommandSuccessExtras, telemetryCredential, trackCommandSuccess, trackEvent, useAnalyticsClientForTests };
359
+ export { analyticsIdentity, analyticsMiddleware, closeAnalytics, commandSuccessProperties, getAnalyticsEventProperties, getErrorAnalyticsEventProperties, initAnalyticsClientMiddleware, isAnalyticsActive, recordCommandSuccessExtras, recordScaffoldedTemplate, redactCredentialTokenIds, sendError, storedCredentialAttribution, takeCommandSuccessExtras, telemetryCredential, trackCommandSuccess, trackEvent, useAnalyticsClientForTests };
@@ -7,7 +7,7 @@ import { c as config_exports } from "../_chunks/config-BH_qRHiG.js";
7
7
  import { u as projects_exports } from "../_chunks/projects-BwI2Uo0z.js";
8
8
  import { a as link_exports } from "../_chunks/link-BgtkOBws.js";
9
9
  import { o as mcp_exports } from "../_chunks/mcp-Bo-7TKc7.js";
10
- import { c as plugins_exports } from "../_chunks/plugins-DqD7BDO1.js";
10
+ import { c as plugins_exports } from "../_chunks/plugins-Dj0H6lDA.js";
11
11
  import { l as skills_exports } from "../_chunks/skills-BP6pIm7O.js";
12
12
  import { t as bootstrap_exports } from "../_chunks/bootstrap-wPZWa1sT.js";
13
13
  import { r as branches_exports } from "../_chunks/branches-BNa1xX1L.js";
@@ -1 +1 @@
1
- export { t as aliases, n as builder, r as command, i as describe, a as handler, o as installPlugins, s as pluginsInstallError, l as reportPluginsInstall } from "../_chunks/plugins-DqD7BDO1.js";
1
+ export { t as aliases, n as builder, r as command, i as describe, a as handler, o as installPlugins, s as pluginsInstallError, l as reportPluginsInstall } from "../_chunks/plugins-Dj0H6lDA.js";
package/dist/init/copy.js CHANGED
@@ -158,6 +158,17 @@ const mcpKeyFailedNext = (reason, mcp) => [
158
158
  "Or with OAuth:",
159
159
  `${mcp} --oauth`
160
160
  ];
161
+ const AGENTS_NOT_INSTALLED = "not installed";
162
+ const TOOLING_FAILED = {
163
+ plugins: "The Neon plugin was not installed. Retry with:",
164
+ skills: "Neon skills were not installed. Retry with:",
165
+ mcp: "The Neon MCP server was not configured. Retry with:"
166
+ };
167
+ const toolingFailedNext = (failures) => failures.flatMap((failure, index) => [
168
+ ...index > 0 ? [""] : [],
169
+ TOOLING_FAILED[failure.kind],
170
+ failure.command
171
+ ]);
161
172
  const mcpAfterLinkNext = (mcp) => ["Then configure the Neon MCP server for that project:", mcp];
162
173
  const envPullFailedNext = () => ({
163
174
  heading: HEADING_FAILED,
@@ -174,4 +185,4 @@ const headingForOutcome = (outcome) => {
174
185
  }
175
186
  };
176
187
  //#endregion
177
- export { AGENT_DETECTED_DESCRIPTION, AGENT_MCP_PICKER_MESSAGE, AGENT_MCP_SKIP_MESSAGE, AGENT_OTHER_DESCRIPTION, AGENT_PICKER_MESSAGE, AGENT_PLUGIN_SKIP_MESSAGE, AGENT_SETUP_MESSAGE, AGENT_SETUP_PLUGIN_DESCRIPTION, AGENT_SETUP_PLUGIN_TITLE, AGENT_SETUP_SKILLS_DESCRIPTION, AGENT_SETUP_SKILLS_TITLE, AGENT_SETUP_SKIP_DESCRIPTION, AGENT_SETUP_SKIP_TITLE, AGENT_SKILLS_PICKER_MESSAGE, AGENT_SKILLS_SKIP_MESSAGE, CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, CONFIG_CONFIRM_HINT, CONFIG_CONFIRM_MESSAGE, HEADING_CANCELLED, HEADING_COMPLETE, HEADING_FAILED, HEADING_PENDING, INIT_NEEDS_YES, INIT_SUBTITLE, MCP_API_KEY_NEEDS_AUTH, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, MCP_OAUTH_FALLBACK, MCP_SCOPED_NEEDS_PROJECT, MCP_SCOPE_GLOBAL_DESCRIPTION, MCP_SCOPE_GLOBAL_TITLE, MCP_SCOPE_MESSAGE, MCP_SCOPE_PROJECT_DESCRIPTION, MCP_SCOPE_PROJECT_TITLE, MODE_CUSTOM_DESCRIPTION, MODE_CUSTOM_TITLE, MODE_MESSAGE, MODE_RECOMMENDED_DESCRIPTION, MODE_RECOMMENDED_TITLE, NON_TTY_AGENT_SETUP, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, NO_AGENT_SETUP_CONFLICT, PACKAGE_MANAGER_MESSAGE, PROGRESS, PROJECT_PINNED_OFFLINE, PROJECT_SETUP_CLAIMABLE_DESCRIPTION, PROJECT_SETUP_CLAIMABLE_TITLE, PROJECT_SETUP_LINK_DESCRIPTION, PROJECT_SETUP_LINK_TITLE, PROJECT_SETUP_MESSAGE, SERVICES_HINT, SERVICES_MESSAGE, SKILLS_PICKER_MESSAGE, agentMissingCommandDescription, claimableNext, claimableThenServicesNext, configInitNext, envPullFailedNext, existingConfigNext, headingForOutcome, installFailedNext, mcpAfterLinkNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, mcpKeyFailedNext, namedAgentsUnavailable, newConfigNext, offlinePinNext, skippedLinkNext, unattendedUnauthedNext };
188
+ export { AGENTS_NOT_INSTALLED, AGENT_DETECTED_DESCRIPTION, AGENT_MCP_PICKER_MESSAGE, AGENT_MCP_SKIP_MESSAGE, AGENT_OTHER_DESCRIPTION, AGENT_PICKER_MESSAGE, AGENT_PLUGIN_SKIP_MESSAGE, AGENT_SETUP_MESSAGE, AGENT_SETUP_PLUGIN_DESCRIPTION, AGENT_SETUP_PLUGIN_TITLE, AGENT_SETUP_SKILLS_DESCRIPTION, AGENT_SETUP_SKILLS_TITLE, AGENT_SETUP_SKIP_DESCRIPTION, AGENT_SETUP_SKIP_TITLE, AGENT_SKILLS_PICKER_MESSAGE, AGENT_SKILLS_SKIP_MESSAGE, CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, CONFIG_CONFIRM_HINT, CONFIG_CONFIRM_MESSAGE, HEADING_CANCELLED, HEADING_COMPLETE, HEADING_FAILED, HEADING_PENDING, INIT_NEEDS_YES, INIT_SUBTITLE, MCP_API_KEY_NEEDS_AUTH, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, MCP_OAUTH_FALLBACK, MCP_SCOPED_NEEDS_PROJECT, MCP_SCOPE_GLOBAL_DESCRIPTION, MCP_SCOPE_GLOBAL_TITLE, MCP_SCOPE_MESSAGE, MCP_SCOPE_PROJECT_DESCRIPTION, MCP_SCOPE_PROJECT_TITLE, MODE_CUSTOM_DESCRIPTION, MODE_CUSTOM_TITLE, MODE_MESSAGE, MODE_RECOMMENDED_DESCRIPTION, MODE_RECOMMENDED_TITLE, NON_TTY_AGENT_SETUP, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, NO_AGENT_SETUP_CONFLICT, PACKAGE_MANAGER_MESSAGE, PROGRESS, PROJECT_PINNED_OFFLINE, PROJECT_SETUP_CLAIMABLE_DESCRIPTION, PROJECT_SETUP_CLAIMABLE_TITLE, PROJECT_SETUP_LINK_DESCRIPTION, PROJECT_SETUP_LINK_TITLE, PROJECT_SETUP_MESSAGE, SERVICES_HINT, SERVICES_MESSAGE, SKILLS_PICKER_MESSAGE, agentMissingCommandDescription, claimableNext, claimableThenServicesNext, configInitNext, envPullFailedNext, existingConfigNext, headingForOutcome, installFailedNext, mcpAfterLinkNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, mcpKeyFailedNext, namedAgentsUnavailable, newConfigNext, offlinePinNext, skippedLinkNext, toolingFailedNext, unattendedUnauthedNext };
@@ -1,6 +1,10 @@
1
1
  import pkg_default from "../pkg.js";
2
- import { trackEvent } from "../analytics.js";
2
+ import { codeFromBody, isNeonApiError } from "../api.js";
3
+ import { isNetworkError, matchErrorCode } from "../errors.js";
4
+ import { redactCredentialTokenIds, trackEvent } from "../analytics.js";
5
+ import { AuthRefreshError } from "../auth.js";
3
6
  import { randomUUID } from "node:crypto";
7
+ import { stripVTControlCharacters } from "node:util";
4
8
  //#region src/init/funnel.ts
5
9
  const CLI_INIT_START = "cli_init_start";
6
10
  const CLI_INIT_END = "cli_init_end";
@@ -70,17 +74,45 @@ const initStartProperties = (input) => ({
70
74
  detected_agents: input.detection.detectedAgents,
71
75
  ci: input.detection.ci
72
76
  });
73
- const initEndProperties = (input) => ({
74
- init_run_id: input.initRunId,
75
- cli_version: pkg_default.version,
76
- mode: input.mode,
77
- agent_setup: input.agentSetup,
78
- agents_installed: input.agentsInstalled,
79
- link: input.link,
80
- config: input.config,
81
- services: input.services,
82
- outcome: input.outcome
83
- });
77
+ /** Longer messages are cut so one plugin CLI dump cannot dominate the event. */
78
+ const ERROR_MESSAGE_LIMIT = 2e3;
79
+ /**
80
+ * The `errCode` the top-level failure handler (`index.ts`) sends on `CLI Error` for the same
81
+ * error, so a run's `error_code` and its `CLI Error` event agree.
82
+ */
83
+ const initErrorCode = (error) => {
84
+ if (error instanceof AuthRefreshError) return "AUTH_FAILED";
85
+ if (isNetworkError(error)) return "NETWORK_ERROR";
86
+ if (isNeonApiError(error)) {
87
+ if (error.code === "ECONNABORTED") return "REQUEST_TIMEOUT";
88
+ const bodyCode = codeFromBody(error.data);
89
+ if (error.status === 401 || bodyCode === "SSO_AUTHORIZATION_REQUIRED" || bodyCode === "SSO_ORG_CREDS_ONLY") return "AUTH_FAILED";
90
+ return "API_ERROR";
91
+ }
92
+ return matchErrorCode(error instanceof Error ? error.message : void 0);
93
+ };
94
+ const initErrorMessage = (error) => {
95
+ const raw = error instanceof Error ? error.message : String(error);
96
+ return redactCredentialTokenIds(stripVTControlCharacters(raw)).slice(0, ERROR_MESSAGE_LIMIT);
97
+ };
98
+ const initEndProperties = (input) => {
99
+ const reported = input.errors.find((entry) => entry.error === input.exitError) ?? input.errors[0];
100
+ return {
101
+ init_run_id: input.initRunId,
102
+ cli_version: pkg_default.version,
103
+ mode: input.mode,
104
+ agent_setup: input.agentSetup,
105
+ agents_installed: input.agentsInstalled,
106
+ link: input.link,
107
+ config: input.config,
108
+ services: input.services,
109
+ outcome: input.outcome,
110
+ error_step: reported?.step ?? null,
111
+ error_code: reported === void 0 ? null : initErrorCode(reported.error),
112
+ error_message: reported === void 0 ? null : initErrorMessage(reported.error),
113
+ error_steps: [...new Set(input.errors.map((entry) => entry.step))]
114
+ };
115
+ };
84
116
  const emitInitStart = (properties) => {
85
117
  trackEvent(CLI_INIT_START, properties);
86
118
  };
@@ -95,8 +127,22 @@ const createInitFunnel = (initRunId = createInitRunId()) => ({
95
127
  link: null,
96
128
  config: null,
97
129
  services: null,
130
+ errors: [],
131
+ exitError: void 0,
98
132
  ended: false
99
133
  });
134
+ /** Records a failure once: a deferred error rethrown at the end keeps the step it failed in. */
135
+ const recordInitError = (state, step, error) => {
136
+ if (state.errors.some((entry) => entry.error === error)) return;
137
+ state.errors.push({
138
+ step,
139
+ error
140
+ });
141
+ };
142
+ const recordInitExit = (state, step, error) => {
143
+ recordInitError(state, step, error);
144
+ state.exitError = error;
145
+ };
100
146
  const finishInitFunnel = (state, outcome, analytics) => {
101
147
  if (state.ended || !analytics) return;
102
148
  state.ended = true;
@@ -108,8 +154,10 @@ const finishInitFunnel = (state, outcome, analytics) => {
108
154
  link: state.link,
109
155
  config: state.config,
110
156
  services: state.services,
111
- outcome
157
+ outcome,
158
+ errors: state.errors,
159
+ exitError: state.exitError
112
160
  }));
113
161
  };
114
162
  //#endregion
115
- export { CLI_INIT_END, CLI_INIT_START, createInitFunnel, createInitRunId, emitInitEnd, emitInitStart, finishInitFunnel, initEndProperties, initFlagsFromArgv, initStartProperties };
163
+ export { CLI_INIT_END, CLI_INIT_START, createInitFunnel, createInitRunId, emitInitEnd, emitInitStart, finishInitFunnel, initEndProperties, initErrorCode, initErrorMessage, initFlagsFromArgv, initStartProperties, recordInitError, recordInitExit };
package/dist/init/run.js CHANGED
@@ -15,7 +15,7 @@ import { missingPluginsCommands, pluginsInstallableAgents } from "../plugins/tar
15
15
  import { skillsInstallableAgents } from "../skills/targets.js";
16
16
  import { FALLBACK_SKILLS_AGENTS, assertNamedAgentTooling, funnelAgentSetup, planInitToolingSteps, recommendedTooling, resolveNamedAgents, splitInitTooling } from "./plan.js";
17
17
  import { inferPackageManager, resolvePackageManager } from "../utils/package_manager.js";
18
- import { CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, MCP_API_KEY_NEEDS_AUTH, MCP_SCOPED_NEEDS_PROJECT, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, PROGRESS, PROJECT_PINNED_OFFLINE, claimableNext, claimableThenServicesNext, configInitNext, envPullFailedNext, existingConfigNext, installFailedNext, mcpAfterLinkNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, mcpKeyFailedNext, namedAgentsUnavailable, newConfigNext, offlinePinNext, skippedLinkNext, unattendedUnauthedNext } from "./copy.js";
18
+ import { AGENTS_NOT_INSTALLED, CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, MCP_API_KEY_NEEDS_AUTH, MCP_SCOPED_NEEDS_PROJECT, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, PROGRESS, PROJECT_PINNED_OFFLINE, claimableNext, claimableThenServicesNext, configInitNext, envPullFailedNext, existingConfigNext, installFailedNext, mcpAfterLinkNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, mcpKeyFailedNext, namedAgentsUnavailable, newConfigNext, offlinePinNext, skippedLinkNext, toolingFailedNext, unattendedUnauthedNext } from "./copy.js";
19
19
  import { agentsRowValue, configRowValue, formatInitDone, headingForKind, printInitBanner, printInitDone, printInitProgress, projectRowValue, shouldPrintInitBanner } from "./chrome.js";
20
20
  import { h as initCmd, t as ConfigInstallFailed } from "../_chunks/config-BH_qRHiG.js";
21
21
  import { o as quoteFlagValue, s as runLink } from "../_chunks/link-BgtkOBws.js";
@@ -24,11 +24,11 @@ import { CannotMintApiKeyError } from "../mcp/mint.js";
24
24
  import { pullInitEnv } from "./auth.js";
25
25
  import { InitCancelled, raceSigint, restoreCursor } from "./cancelled.js";
26
26
  import { pickAgentSetupInteractively, pickInitAgentsInteractively, pickInitConfigInteractively, pickInitMcpAuthInteractively, pickInitMcpConfigLocationInteractively, pickInitModeInteractively, pickInitPackageManagerInteractively, pickInitProjectSetupInteractively, pickInitServicesInteractively, pickInitSkillsInteractively } from "./wizard.js";
27
- import { runToolingSteps } from "./tooling.js";
27
+ import { runToolingSteps, toolingStepCommandText } from "./tooling.js";
28
28
  import { l as create } from "../_chunks/claim-DqAvAvDm.js";
29
29
  import { INIT_CONFIG_SERVICES_CONFLICT, configPlanFromResolution, initEnvPullAfterConfig, resolveInitConfigChoice } from "./choices.js";
30
30
  import { detectInitEnvironment } from "./detect.js";
31
- import { createInitFunnel, emitInitStart, finishInitFunnel, initStartProperties } from "./funnel.js";
31
+ import { createInitFunnel, emitInitStart, finishInitFunnel, initStartProperties, recordInitError, recordInitExit } from "./funnel.js";
32
32
  import { assertAgentSetupFlags, hasInitMcpFlags, inferInitAgentSetup, resolveInitMode } from "./mode.js";
33
33
  import { resolve } from "node:path";
34
34
  //#region src/init/run.ts
@@ -197,6 +197,8 @@ const runInit = async (props) => {
197
197
  const funnel = createInitFunnel();
198
198
  let outcome = "error";
199
199
  let printed = false;
200
+ let step = "setup";
201
+ let deferredFailureNext = () => [];
200
202
  try {
201
203
  const detectOpts = {
202
204
  cwd,
@@ -332,7 +334,23 @@ const runInit = async (props) => {
332
334
  const mcpOauth = mcpAuth === "oauth";
333
335
  const earlyTooling = delayMcp ? withoutMcp(tooling) : tooling;
334
336
  const mcpState = {};
335
- const reportedTooling = () => mcpState.error === void 0 && mcpState.deferredPin === void 0 ? tooling : withoutMcp(tooling);
337
+ const toolingFailures = [];
338
+ const reportedTooling = () => {
339
+ const dropped = new Set(toolingFailures.map((failure) => failure.kind));
340
+ if (mcpState.error !== void 0 || mcpState.deferredPin !== void 0) dropped.add("mcp");
341
+ return withoutKinds(tooling, dropped);
342
+ };
343
+ const agentsRow = () => {
344
+ const reported = reportedTooling();
345
+ return toolingFailures.length > 0 && reported.setup === "skip" ? AGENTS_NOT_INSTALLED : agentsRowValue({
346
+ tooling: reported,
347
+ installed: funnel.agentsInstalled
348
+ });
349
+ };
350
+ const toolingFailedNextLines = () => toolingFailedNext(toolingFailures.map((failure) => ({
351
+ kind: failure.kind,
352
+ command: failure.command
353
+ })));
336
354
  const mcpCommand = (projectIdArg) => [
337
355
  `${getCliName()} mcp -y`,
338
356
  ...mcpConfigLocation === "project" ? ["--project"] : [],
@@ -340,25 +358,35 @@ const runInit = async (props) => {
340
358
  ...("mcpAgents" in tooling ? tooling.mcpAgents : []).map((agent) => `--agent ${agent}`)
341
359
  ].join(" ");
342
360
  const mcpFailedNext = () => mcpState.error === void 0 ? [] : mcpKeyFailedNext(mcpState.reason ?? "cannot-mint", mcpCommand(mcpState.pinId !== void 0 ? quoteFlagValue(mcpState.pinId) : void 0));
361
+ deferredFailureNext = () => joinNext(toolingFailedNextLines(), mcpFailedNext());
343
362
  const mcpDeferredNext = () => mcpState.deferredPin === void 0 ? [] : mcpAfterLinkNext(`${mcpCommand(mcpState.deferredPin)}${mcpOauth ? " --oauth" : ""}`);
344
363
  const runTooling = async (steps, plan) => {
345
- let ran = plan;
346
- try {
347
- await runToolingSteps(steps, {
348
- cwd,
349
- output: props.output,
350
- auth,
351
- operations: props.operations,
352
- narrate: "human",
353
- mcpOauthFallback: !mcpAuthChosen
354
- });
355
- } catch (error) {
356
- if (!(error instanceof CannotMintApiKeyError)) throw error;
357
- mcpState.error = error;
358
- mcpState.reason = "cannot-mint";
359
- ran = withoutMcp(plan);
364
+ for (const toolingStep of steps) {
365
+ let completed;
366
+ try {
367
+ completed = await runToolingSteps([toolingStep], {
368
+ cwd,
369
+ output: props.output,
370
+ auth,
371
+ operations: props.operations,
372
+ narrate: "human",
373
+ mcpOauthFallback: !mcpAuthChosen
374
+ });
375
+ } catch (error) {
376
+ if (error instanceof InitCancelled) throw error;
377
+ if (error instanceof CannotMintApiKeyError) {
378
+ mcpState.error = error;
379
+ mcpState.reason = "cannot-mint";
380
+ } else toolingFailures.push({
381
+ kind: toolingStep.kind,
382
+ command: `${getCliName()} ${toolingStepCommandText(toolingStep)}`,
383
+ error
384
+ });
385
+ recordInitError(funnel, toolingStep.kind, error);
386
+ continue;
387
+ }
388
+ if (completed.includes(toolingStep.kind)) funnel.agentsInstalled = uniqueAgents([...funnel.agentsInstalled, ...agentsForStep(plan, toolingStep.kind)]);
360
389
  }
361
- funnel.agentsInstalled = uniqueAgents([...funnel.agentsInstalled, ...agentsFromTooling(ran)]);
362
390
  };
363
391
  const toolingSteps = planInitToolingSteps({
364
392
  tooling: earlyTooling,
@@ -370,6 +398,7 @@ const runInit = async (props) => {
370
398
  ...selectedSkills !== void 0 ? { skills: selectedSkills } : {}
371
399
  });
372
400
  if (toolingSteps.length > 0) await runTooling(toolingSteps, earlyTooling);
401
+ step = "link";
373
402
  let projectSetup = "skip";
374
403
  let claimExpiresAt;
375
404
  const noLink = props.link === false;
@@ -419,6 +448,7 @@ const runInit = async (props) => {
419
448
  };
420
449
  if (usesMcp && mcpAuth === "api-key" && (projectSetup === "claimable" || props.claimable === true)) throw new Error(CLAIMABLE_MCP_API_KEY);
421
450
  if (projectSetup === "claimable") {
451
+ step = "claimable";
422
452
  printInitProgress(PROGRESS.claim);
423
453
  await (props.createClaimable ?? defaultCreateClaimable)({
424
454
  cwd,
@@ -432,12 +462,14 @@ const runInit = async (props) => {
432
462
  funnel.link = "claimable";
433
463
  claimExpiresAt = readLinkedClaimableCredentials(configDir, readContextFile(contextFile))?.expiresAt;
434
464
  } else if (projectSetup === "link") {
465
+ step = "link";
435
466
  printInitProgress(detection.authenticated ? PROGRESS.link : PROGRESS.auth);
436
467
  await (props.linkProject ?? runAuthenticatedLink)(linkProps);
437
468
  funnel.link = "linked";
438
469
  }
439
470
  const pinnedOffline = funnel.link === "skipped" && !noLink && linkInputs.orgId !== void 0 && linkInputs.projectId !== void 0 && linkInputs.branch !== void 0 && linkInputs.projectName === void 0 && linkInputs.regionId === void 0;
440
471
  if (pinnedOffline) {
472
+ step = "link";
441
473
  await runLink({
442
474
  ...linkProps,
443
475
  checks: false,
@@ -449,9 +481,11 @@ const runInit = async (props) => {
449
481
  else if (delayMcp && tooling.setup !== "skip" && mcpAuth === "api-key" && yes && !detection.authenticated) {
450
482
  mcpState.error = new Error(MCP_API_KEY_NEEDS_AUTH);
451
483
  mcpState.reason = "signed-out";
484
+ recordInitError(funnel, "mcp", mcpState.error);
452
485
  const linkedId = readContextFile(contextFile).projectId;
453
486
  if (props.mcpProjectScoped === true && typeof linkedId === "string") mcpState.pinId = linkedId;
454
487
  } else if (delayMcp && tooling.setup !== "skip") {
488
+ step = "mcp";
455
489
  const linkedId = readContextFile(contextFile).projectId;
456
490
  let pinId;
457
491
  if (props.mcpProjectScoped === true) {
@@ -479,6 +513,7 @@ const runInit = async (props) => {
479
513
  mcpState.pinId = pinId;
480
514
  if (mcpSteps.length > 0) await runTooling(mcpSteps, mcpOnly);
481
515
  }
516
+ step = "config";
482
517
  const canAskConfig = props.pickConfig !== void 0 || detection.interactive;
483
518
  const configResolution = resolveInitConfigChoice({
484
519
  flag: recommended ? props.config ?? true : props.config,
@@ -536,7 +571,7 @@ const runInit = async (props) => {
536
571
  heading: failed.heading,
537
572
  body: failed.body,
538
573
  rows: [],
539
- next: joinNext(failed.next, mcpFailedNext())
574
+ next: joinNext(failed.next, joinNext(toolingFailedNextLines(), mcpFailedNext()))
540
575
  }));
541
576
  printed = true;
542
577
  outcome = "error";
@@ -557,48 +592,48 @@ const runInit = async (props) => {
557
592
  ...typeof context.projectId === "string" ? { projectId: context.projectId } : {},
558
593
  ...branch !== void 0 ? { branch } : {}
559
594
  });
560
- if (envPullPlan.kind !== "skip") try {
561
- if (typeof context.projectId !== "string" || typeof branch !== "string") throw new Error("Env pull requires a linked project and branch.");
562
- printInitProgress(PROGRESS.env);
563
- await raceSigint((props.envPull ?? pullInitEnv)({
564
- ...auth,
565
- output: props.output,
566
- cwd,
567
- projectId: context.projectId,
568
- branch,
569
- ...envPullPlan.kind === "services" ? {
570
- services: envPullPlan.services,
571
- envKeys: envPullPlan.envKeys
572
- } : {}
573
- }));
574
- } catch (error) {
575
- if (error instanceof InitCancelled) throw error;
576
- const failed = envPullFailedNext();
577
- printInitDone(formatInitDone({
578
- heading: failed.heading,
579
- body: failed.body,
580
- rows: [
581
- {
582
- label: "Agents",
583
- value: agentsRowValue({
584
- tooling: reportedTooling(),
585
- installed: funnel.agentsInstalled
586
- })
587
- },
588
- {
589
- label: "Project",
590
- value: projectRowValue(funnel.link)
591
- },
592
- {
593
- label: "Config",
594
- value: configRowValue(funnel.config, existingFilename)
595
- }
596
- ],
597
- next: joinNext(failed.next, mcpFailedNext())
598
- }));
599
- printed = true;
600
- outcome = "error";
601
- throw error;
595
+ if (envPullPlan.kind !== "skip") {
596
+ step = "env_pull";
597
+ try {
598
+ if (typeof context.projectId !== "string" || typeof branch !== "string") throw new Error("Env pull requires a linked project and branch.");
599
+ printInitProgress(PROGRESS.env);
600
+ await raceSigint((props.envPull ?? pullInitEnv)({
601
+ ...auth,
602
+ output: props.output,
603
+ cwd,
604
+ projectId: context.projectId,
605
+ branch,
606
+ ...envPullPlan.kind === "services" ? {
607
+ services: envPullPlan.services,
608
+ envKeys: envPullPlan.envKeys
609
+ } : {}
610
+ }));
611
+ } catch (error) {
612
+ if (error instanceof InitCancelled) throw error;
613
+ const failed = envPullFailedNext();
614
+ printInitDone(formatInitDone({
615
+ heading: failed.heading,
616
+ body: failed.body,
617
+ rows: [
618
+ {
619
+ label: "Agents",
620
+ value: agentsRow()
621
+ },
622
+ {
623
+ label: "Project",
624
+ value: projectRowValue(funnel.link)
625
+ },
626
+ {
627
+ label: "Config",
628
+ value: configRowValue(funnel.config, existingFilename)
629
+ }
630
+ ],
631
+ next: joinNext(failed.next, joinNext(toolingFailedNextLines(), mcpFailedNext()))
632
+ }));
633
+ printed = true;
634
+ outcome = "error";
635
+ throw error;
636
+ }
602
637
  }
603
638
  const pendingUnauthed = funnel.link === "skipped" && !noLink;
604
639
  const pendingServices = extraServices && linked;
@@ -640,16 +675,14 @@ const runInit = async (props) => {
640
675
  outcome = pending && !pendingUnauthed && noLink ? "success" : pending ? "pending" : "success";
641
676
  if (pendingUnauthed) outcome = "pending";
642
677
  if (noLink && !pendingUnauthed && !pendingServices && !pendingExisting) outcome = "success";
643
- if (mcpState.error !== void 0) outcome = "error";
678
+ const deferredError = toolingFailures[0]?.error ?? mcpState.error;
679
+ if (deferredError !== void 0) outcome = "error";
644
680
  printInitDone(formatInitDone({
645
681
  heading: headingForKind(outcome),
646
682
  rows: [
647
683
  {
648
684
  label: "Agents",
649
- value: agentsRowValue({
650
- tooling: reportedTooling(),
651
- installed: funnel.agentsInstalled
652
- })
685
+ value: agentsRow()
653
686
  },
654
687
  {
655
688
  label: "Project",
@@ -660,10 +693,10 @@ const runInit = async (props) => {
660
693
  value: configRowValue(funnel.config, existingFilename)
661
694
  }
662
695
  ],
663
- next: joinNext(mcpFailedNext(), next)
696
+ next: joinNext(joinNext(toolingFailedNextLines(), mcpFailedNext()), next)
664
697
  }));
665
698
  printed = true;
666
- if (mcpState.error !== void 0) throw mcpState.error;
699
+ if (deferredError !== void 0) throw deferredError;
667
700
  takeCommandSuccessExtras();
668
701
  recordCommandSuccessExtras({
669
702
  init_kind: detection.emptyDirectory ? "empty-skip" : "existing",
@@ -682,6 +715,13 @@ const runInit = async (props) => {
682
715
  throw error;
683
716
  }
684
717
  outcome = "error";
718
+ recordInitExit(funnel, step, error);
719
+ const deferred = deferredFailureNext();
720
+ if (!printed && deferred.length > 0) printInitDone(formatInitDone({
721
+ heading: headingForKind("error"),
722
+ rows: [],
723
+ next: deferred
724
+ }));
685
725
  throw error;
686
726
  } finally {
687
727
  finishInitFunnel(funnel, outcome, analytics);
@@ -702,31 +742,46 @@ const joinNext = (first, second) => first.length === 0 || second.length === 0 ?
702
742
  "",
703
743
  ...second
704
744
  ];
705
- const withoutMcp = (tooling) => {
706
- if (tooling.setup === "mixed") return {
707
- ...tooling,
708
- mcpAgents: []
709
- };
710
- if (tooling.setup === "skills-mcp") return tooling.skillsAgents.length === 0 ? { setup: "skip" } : {
711
- ...tooling,
712
- mcpAgents: []
713
- };
714
- return tooling;
745
+ const withoutKinds = (tooling, kinds) => {
746
+ switch (tooling.setup) {
747
+ case "skip": return tooling;
748
+ case "plugin": return kinds.has("plugins") ? { setup: "skip" } : tooling;
749
+ case "skills": return kinds.has("skills") ? { setup: "skip" } : tooling;
750
+ case "skills-mcp": {
751
+ const skillsAgents = kinds.has("skills") ? [] : tooling.skillsAgents;
752
+ const mcpAgents = kinds.has("mcp") ? [] : tooling.mcpAgents;
753
+ return skillsAgents.length === 0 && mcpAgents.length === 0 ? { setup: "skip" } : {
754
+ ...tooling,
755
+ skillsAgents,
756
+ mcpAgents
757
+ };
758
+ }
759
+ case "mixed": {
760
+ const skillsAgents = kinds.has("skills") ? [] : tooling.skillsAgents;
761
+ const mcpAgents = kinds.has("mcp") ? [] : tooling.mcpAgents;
762
+ if (!kinds.has("plugins")) return {
763
+ ...tooling,
764
+ skillsAgents,
765
+ mcpAgents
766
+ };
767
+ return skillsAgents.length === 0 && mcpAgents.length === 0 ? { setup: "skip" } : {
768
+ setup: "skills-mcp",
769
+ skillsAgents,
770
+ mcpAgents
771
+ };
772
+ }
773
+ default: return tooling;
774
+ }
715
775
  };
776
+ const withoutMcp = (tooling) => withoutKinds(tooling, /* @__PURE__ */ new Set(["mcp"]));
716
777
  /** The Neon plugin bundles the skills. */
717
778
  const installsSkills = (tooling) => tooling.setup === "skills-mcp" ? tooling.skillsAgents.length > 0 : tooling.setup !== "skip";
718
- const agentsFromTooling = (tooling) => {
719
- switch (tooling.setup) {
720
- case "skip": return [];
721
- case "plugin": return [...tooling.agents];
722
- case "skills": return [...tooling.agents];
723
- case "skills-mcp": return uniqueAgents([...tooling.skillsAgents, ...tooling.mcpAgents]);
724
- case "mixed": return uniqueAgents([
725
- ...tooling.pluginAgents,
726
- ...tooling.skillsAgents,
727
- ...tooling.mcpAgents
728
- ]);
729
- default: return tooling;
779
+ const agentsForStep = (tooling, kind) => {
780
+ switch (kind) {
781
+ case "plugins": return tooling.setup === "plugin" ? [...tooling.agents] : tooling.setup === "mixed" ? [...tooling.pluginAgents] : [];
782
+ case "skills": return tooling.setup === "skills" ? [...tooling.agents] : tooling.setup === "skills-mcp" || tooling.setup === "mixed" ? [...tooling.skillsAgents] : [];
783
+ case "mcp": return tooling.setup === "skills-mcp" || tooling.setup === "mixed" ? [...tooling.mcpAgents] : [];
784
+ default: return kind;
730
785
  }
731
786
  };
732
787
  //#endregion
@@ -6,7 +6,7 @@ import { CannotMintApiKeyError } from "../mcp/mint.js";
6
6
  import { detectAgent } from "./detect_host.js";
7
7
  import { AgentSelectionSkipped, canPickAgentsInteractively } from "../utils/agent_picker.js";
8
8
  import { a as mcpInstallError, s as reportMcpInstall } from "../_chunks/mcp-Bo-7TKc7.js";
9
- import { l as reportPluginsInstall, o as installPlugins, s as pluginsInstallError } from "../_chunks/plugins-DqD7BDO1.js";
9
+ import { l as reportPluginsInstall, o as installPlugins, s as pluginsInstallError } from "../_chunks/plugins-Dj0H6lDA.js";
10
10
  import { c as skillsInstallError, o as installSkills, s as reportSkillsInstall } from "../_chunks/skills-BP6pIm7O.js";
11
11
  import { runAuthenticatedMcp } from "./auth.js";
12
12
  import { raceSigint } from "./cancelled.js";
@@ -19,7 +19,7 @@ const TOOLING_STEP_LABELS = {
19
19
  skills: PROGRESS.skills,
20
20
  mcp: PROGRESS.mcp
21
21
  };
22
- /** Reconstructs a display-only, `neon <command> <flags>` style string for a step. Never executed — only ever logged. */
22
+ /** The `<command> <flags>` a user can run to repeat this step on its own. Never executed here. */
23
23
  const toolingStepCommandText = (step) => {
24
24
  const flags = [];
25
25
  switch (step.kind) {
@@ -185,4 +185,4 @@ const runAgentTooling = async (options) => {
185
185
  }));
186
186
  };
187
187
  //#endregion
188
- export { runAgentTooling, runToolingSteps };
188
+ export { runAgentTooling, runToolingSteps, toolingStepCommandText };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "8.13.1",
3
+ "version": "8.13.2",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -113,8 +113,8 @@
113
113
  "vitest": "^3.0.9",
114
114
  "@neon-internals/cli-core": "0.0.0",
115
115
  "@neon-internals/env-core": "0.0.36",
116
- "@neon/env": "1.6.6",
117
116
  "@neon/e2e-harness": "0.0.0",
117
+ "@neon/env": "1.6.6",
118
118
  "@neon/functions": "0.11.0"
119
119
  },
120
120
  "publishConfig": {