neon 4.10.0 → 4.10.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
@@ -683,7 +683,7 @@ When a package cannot be bundled — a native addon with no esbuild loader, or a
683
683
 
684
684
  `neon bootstrap` copies a Neon starter template into a new (or current) directory — conceptually like `degit`, but it only pulls from a small set of templates we maintain in the public [`neondatabase/examples`](https://github.com/neondatabase/examples) repo. The template copy needs no Neon login: it downloads files from GitHub.
685
685
 
686
- After scaffolding, an interactive terminal offers agent tooling (the Neon plugin, or skills and MCP separately — never both) and then `neon link`. `--default` / `-y` skips the template, install, git, and agent pickers, then runs `link --yes`. `link --yes` still asks for a project unless one is already linked. `--no-agent-setup` and `--no-link` skip those. Non-interactive without `--default` prints next steps and does not install, set up agents, or link.
686
+ After scaffolding, an interactive terminal asks about dependency install, git, agent tooling (the Neon plugin, or skills and MCP separately — never both), and `neon link` before running those steps. Dependency install is last, except when the template has a `neon.ts` and you chose to link — then install runs first so link can pull env. `--default` / `-y` skips the template, install, git, and agent pickers, then runs `link --yes`. `link --yes` still asks for a project unless one is already linked. `--no-agent-setup` and `--no-link` skip those. Non-interactive without `--default` prints next steps and does not install, set up agents, or link.
687
687
 
688
688
  Pass a target directory (or `.` for the current one). In an interactive terminal you pick the template from a list; in CI / non-interactive contexts pass `--template <id>`.
689
689
 
@@ -707,7 +707,7 @@ The target directory must be empty unless you pass `--force` (a lone `.git` is i
707
707
 
708
708
  `neon init` sets up this directory for Neon.
709
709
 
710
- An empty directory (nothing except `.git`) runs `neon bootstrap .` and stops. With `-y` that is `neon bootstrap . --default`. Bootstrap handles scaffolding, agent tooling, and linking.
710
+ An empty directory (nothing except `.git`) runs `neon bootstrap .` and stops. With `-y` that is `neon bootstrap . --default`. Bootstrap handles scaffolding, agent tooling, and linking. Interactive bootstrap prints a NEON banner, asks every setup question, then runs the work — dependency install last, except when a `neon.ts` needs deps before `link`.
711
711
 
712
712
  An existing app installs agent tooling, then `neon link` unless `.neon` already has a projectId, then `neon config init`. Interactive `config init` opens the services picker; `-y` uses `--services none` (starter policy).
713
713
 
@@ -791,7 +791,7 @@ On a TTY the command asks which agents and which skills, then shows a summary to
791
791
 
792
792
  `-y` skips those questions and installs the default skills. `--skill` / `-s` names specific skills and skips the skill picker. `--agent` and `--global` still apply. Without a TTY, pass `-y` or `--skill`. `--agent` alone is not enough.
793
793
 
794
- `--skill` names by source repo: `neondatabase/agent-skills` (`claimable-postgres`, `neon`, `neon-ai-gateway`, `neon-functions`, `neon-object-storage`, `neon-postgres`, `neon-postgres-branches`, `neon-postgres-egress-optimizer`); `neondatabase/neon-for-agent-platforms` (`neon-postgres-agent-platforms`).
794
+ `--skill` names by source repo: `neondatabase/agent-skills` (`neon`, `neon-ai-gateway`, `neon-functions`, `neon-object-storage`, `neon-postgres`, `neon-postgres-branches`, `neon-postgres-egress-optimizer`); `neondatabase/neon-for-agent-platforms` (`neon-postgres-agent-platforms`).
795
795
 
796
796
  `--agent` names match `neon mcp`, minus agents that cannot install skills: `antigravity`, `cline`, `cline-cli`, `claude-code`, `claude-desktop`, `codex`, `cursor`, `gemini-cli`, `goose`, `github-copilot-cli`, `grok-build`, `opencode`, `vscode`, `windsurf`, `zed`. `mcporter` is a known MCP name that is then skipped. `neon skills --help` lists the same skill and agent values, and that `-y` leaves out `neon-postgres-agent-platforms`.
797
797
 
@@ -821,7 +821,7 @@ Default scope is `project`. `--global` is `user`. On macOS and Linux, Cursor and
821
821
 
822
822
  `--agent` names with a plugins mapping: `claude-code`, `claude-desktop`, `codex`, `cursor`, `github-copilot-cli`, `grok-build`, `vscode`. `claude-desktop` installs as Claude Code; naming both produces one install and lists both names in the table. `mcporter` is a known MCP name that is then skipped.
823
823
 
824
- The plugins CLI installs every plugin it finds in the Neon plugin package. Today that is `neon-postgres` from `neondatabase/agent-skills`. It includes the Neon MCP server (`https://mcp.neon.tech/mcp`) and these skills: `neon`, `neon-ai-gateway`, `neon-functions`, `neon-object-storage`, `neon-postgres`, `neon-postgres-branches`, `neon-postgres-egress-optimizer`. It does not include `claimable-postgres` or `neon-postgres-agent-platforms`.
824
+ The plugins CLI installs every plugin it finds in the Neon plugin package. Today that is `neon-postgres` from `neondatabase/agent-skills`. It includes the Neon MCP server (`https://mcp.neon.tech/mcp`) and these skills: `neon`, `neon-ai-gateway`, `neon-functions`, `neon-object-storage`, `neon-postgres`, `neon-postgres-branches`, `neon-postgres-egress-optimizer`. It does not include `neon-postgres-agent-platforms`.
825
825
 
826
826
  `neon plugins --help` lists the plugin, those contents, and the supported `--agent` names.
827
827
 
@@ -871,7 +871,7 @@ All sub-commands honor the [global options](#global-options), including `--outpu
871
871
 
872
872
  ## Database diagnostics (`inspect`)
873
873
 
874
- `neon inspect db stalled-queries` takes a read-only snapshot of active queries that have run for more than 30 seconds and groups parallel workers with their leader. Table output shows duration, wait event, blocking pids, role, query group, and query. `--output json` adds timestamps, query IDs, pids, database, and the rest of the row. A blocking pid can belong to an idle-in-transaction backend this command does not list; `neon inspect db locks` shows lock holders.
874
+ `neon inspect db stalled-queries` takes a read-only snapshot of active queries that have run for more than 30 seconds and groups parallel workers with their leader. Oldest group first. Table output shows duration, wait event, blocking pids, role, query group, and query. `--output json` adds timestamps, query IDs, pids, database, and the rest of the row. A blocking pid can belong to an idle-in-transaction backend this command does not list; `neon inspect db locks` shows lock holders.
875
875
 
876
876
  ```bash
877
877
  neon inspect db stalled-queries
@@ -6,8 +6,10 @@ import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
6
6
  import { writer } from "../writer.js";
7
7
  import { FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds } from "../init/bootstrap.js";
8
8
  import { spawnCliChild } from "../init/child.js";
9
- import { projectContextFile } from "../init/plan.js";
10
- import { runScaffoldFollowUp } from "../init/tooling.js";
9
+ import { agentSetupDoneLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
10
+ import { postScaffoldActions, projectContextFile } from "../init/plan.js";
11
+ import { pickAgentSetupInteractively } from "../init/wizard.js";
12
+ import { runAgentTooling, runInitSteps } from "../init/tooling.js";
11
13
  import { formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
12
14
  import { existsSync } from "node:fs";
13
15
  import { join, relative, resolve } from "node:path";
@@ -63,7 +65,7 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
63
65
  default: true
64
66
  },
65
67
  link: {
66
- describe: `Run \`${getCliName()} link\` in the scaffolded directory after installing. In interactive mode this is offered as a prompt; use --no-link to skip without being asked.`,
68
+ describe: `Run \`${getCliName()} link\` after scaffolding. Templates with neon.ts link after install so env pull works; otherwise link runs before install. In interactive mode this is offered as a prompt; use --no-link to skip without being asked.`,
67
69
  type: "boolean",
68
70
  default: true
69
71
  },
@@ -99,6 +101,7 @@ const handler = async (props) => {
99
101
  }
100
102
  return;
101
103
  }
104
+ if (shouldPrintInitBanner(props.default)) printInitBanner();
102
105
  const templates = await resolveTemplateList(props);
103
106
  const interactive = !props.default && Boolean(process.stdout.isTTY) && !isCi();
104
107
  const template = await resolveSelectedTemplate(props, interactive, templates);
@@ -106,7 +109,7 @@ const handler = async (props) => {
106
109
  ensureTargetUsable(targetDir, props.force);
107
110
  await scaffold(template, targetDir);
108
111
  printScaffolded(template, targetDir);
109
- await runPostScaffoldSteps(props, targetDir, interactive);
112
+ await runPostScaffoldSteps(props, targetDir, interactive, template);
110
113
  };
111
114
  /**
112
115
  * The template list to choose from. When --template is given we try the
@@ -179,84 +182,150 @@ const scaffold = async (template, targetDir) => {
179
182
  log.info("Scaffolded %d files into %s.", filesWritten, targetDir);
180
183
  return filesWritten;
181
184
  };
182
- const runPostScaffoldSteps = async (props, targetDir, interactive) => {
185
+ const runPostScaffoldSteps = async (props, targetDir, interactive, template) => {
183
186
  const inferred = inferPackageManager(targetDir);
184
187
  const defaultPm = resolvePackageManager(targetDir);
188
+ const neonConfig = hasNeonConfig(targetDir);
185
189
  if (props.default) {
186
- await runDefaultSteps(props, targetDir, defaultPm);
190
+ await runDefaultSteps(props, targetDir, defaultPm, neonConfig, template);
187
191
  return;
188
192
  }
189
193
  if (!interactive) {
190
- printNextSteps(targetDir, defaultPm, {
194
+ printDoneSummary({
195
+ heading: "Project scaffolded.",
196
+ template,
197
+ targetDir,
198
+ pm: defaultPm,
191
199
  installed: false,
192
- suggestLink: true
200
+ installFailed: false,
201
+ gitFailed: false,
202
+ git: false,
203
+ agentSetup: "skip",
204
+ linked: false,
205
+ skippedLinkForDeps: false,
206
+ suggestLink: true,
207
+ agentsRan: false
193
208
  });
194
209
  return;
195
210
  }
196
211
  let pm = defaultPm;
197
- let installed = false;
198
- if (props.install && await confirm(installPrompt(inferred))) {
199
- pm = inferred ?? await selectPackageManager();
200
- installed = await runCommand(pm, installArgs(pm), targetDir);
201
- }
202
- if (props.git && !isGitRepo(targetDir) && await confirm("Initialize a git repository?")) await initGitRepo(targetDir);
203
- const skipLink = shouldSkipLinkForDeps(targetDir, installed);
204
- if (props.link && skipLink) logSkippedLink(pm);
205
- const kids = bootstrapChildren(props, targetDir);
206
- await runScaffoldFollowUp({
207
- cwd: targetDir,
212
+ const wantInstall = props.install && await confirm(installPrompt(inferred));
213
+ if (wantInstall) pm = inferred ?? await selectPackageManager();
214
+ const wantGit = props.git && !isGitRepo(targetDir) && await confirm("Initialize a git repository?");
215
+ const agentSetup = props.agentSetup === false ? "skip" : await (props.pickAgentSetup ?? pickAgentSetupInteractively)();
216
+ const canLink = props.link && !(neonConfig && !wantInstall);
217
+ if (props.link && !canLink) logSkippedLink(pm);
218
+ const wantLink = canLink && await confirm(`Link this project to a Neon project now? (runs ${getCliName()} link)`);
219
+ const outcome = await executePostScaffold(props, targetDir, {
208
220
  yes: false,
209
- skipAgentSetup: props.agentSetup === false,
210
- shouldLink: false,
211
- linkYes: false,
212
- ...kids,
213
- ...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
214
- ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {}
221
+ lockAgentSetup: true,
222
+ pm,
223
+ git: wantGit,
224
+ agentSetup,
225
+ install: wantInstall,
226
+ link: wantLink,
227
+ hasNeonConfig: neonConfig
215
228
  });
216
- if (props.link && !skipLink && await confirm(`Link this project to a Neon project now? (runs ${getCliName()} link)`)) {
217
- await runScaffoldFollowUp({
218
- cwd: targetDir,
219
- yes: false,
220
- skipAgentSetup: true,
221
- shouldLink: true,
222
- linkYes: false,
223
- ...kids
224
- });
225
- printNextSteps(targetDir, pm, {
226
- installed,
227
- suggestLink: false
228
- });
229
- return;
230
- }
231
- printNextSteps(targetDir, pm, {
232
- installed,
233
- suggestLink: true
229
+ finishPostScaffold({
230
+ heading: "Project scaffolded.",
231
+ template,
232
+ targetDir,
233
+ pm,
234
+ ...outcome,
235
+ suggestLink: !outcome.linked
234
236
  });
235
237
  };
236
238
  const installPrompt = (inferred) => inferred ? `Install dependencies with ${inferred}?` : "Install dependencies?";
237
239
  /** `link --yes` still asks for a project unless one is already linked. */
238
- const runDefaultSteps = async (props, targetDir, pm) => {
240
+ const runDefaultSteps = async (props, targetDir, pm, neonConfig, template) => {
239
241
  log.info("Quick start (--default): skipping the template, install, git, and agent pickers. link --yes still asks for a project unless one is already linked.");
240
- let installed = false;
241
- if (props.install) installed = await runCommand(pm, installArgs(pm), targetDir);
242
- if (props.git && !isGitRepo(targetDir)) await initGitRepo(targetDir);
243
- const skipLink = shouldSkipLinkForDeps(targetDir, installed);
244
- if (props.link && skipLink) logSkippedLink(pm);
245
- await runScaffoldFollowUp({
246
- cwd: targetDir,
242
+ const wantGit = props.git && !isGitRepo(targetDir);
243
+ const agentSetup = props.agentSetup === false ? "skip" : "skills-mcp";
244
+ const outcome = await executePostScaffold(props, targetDir, {
247
245
  yes: true,
248
- skipAgentSetup: props.agentSetup === false,
249
- shouldLink: props.link && !skipLink,
250
- linkYes: true,
251
- ...bootstrapChildren(props, targetDir),
252
- ...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
253
- ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {}
246
+ lockAgentSetup: false,
247
+ pm,
248
+ git: wantGit,
249
+ agentSetup,
250
+ install: props.install,
251
+ link: props.link,
252
+ hasNeonConfig: neonConfig
254
253
  });
255
- printNextSteps(targetDir, pm, {
256
- installed,
257
- suggestLink: !(props.link && !skipLink)
254
+ finishPostScaffold({
255
+ heading: "Project scaffolded.",
256
+ template,
257
+ targetDir,
258
+ pm,
259
+ ...outcome,
260
+ suggestLink: !outcome.linked
258
261
  });
259
262
  };
263
+ const executePostScaffold = async (props, targetDir, choices) => {
264
+ const kids = bootstrapChildren(props, targetDir);
265
+ let installed = false;
266
+ let installFailed = false;
267
+ let gitFailed = false;
268
+ let git = false;
269
+ let linked = false;
270
+ let skippedLinkForDeps = false;
271
+ let agentSetup = choices.agentSetup;
272
+ let agentsRan = false;
273
+ const actions = postScaffoldActions({
274
+ git: choices.git,
275
+ agentSetup: choices.agentSetup,
276
+ install: choices.install,
277
+ link: choices.link,
278
+ hasNeonConfig: choices.hasNeonConfig
279
+ });
280
+ for (const action of actions) {
281
+ if (action === "git") {
282
+ git = await initGitRepo(targetDir);
283
+ if (!git) {
284
+ gitFailed = true;
285
+ break;
286
+ }
287
+ continue;
288
+ }
289
+ if (action === "agent") {
290
+ agentSetup = await runAgentTooling({
291
+ cwd: targetDir,
292
+ yes: choices.yes,
293
+ ...choices.lockAgentSetup ? { agentSetup: choices.agentSetup } : {},
294
+ ...kids,
295
+ ...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
296
+ ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {}
297
+ });
298
+ agentsRan = true;
299
+ continue;
300
+ }
301
+ if (action === "install") {
302
+ installed = await runCommand(choices.pm, installArgs(choices.pm), targetDir);
303
+ if (!installed) installFailed = true;
304
+ continue;
305
+ }
306
+ if (action !== "link") throw new Error(`Unhandled post-scaffold action: ${action}`);
307
+ if (choices.hasNeonConfig && !installed) {
308
+ skippedLinkForDeps = true;
309
+ logSkippedLink(choices.pm);
310
+ continue;
311
+ }
312
+ await runInitSteps([choices.yes ? ["link", "--yes"] : ["link"]], {
313
+ cwd: targetDir,
314
+ ...kids
315
+ });
316
+ linked = true;
317
+ }
318
+ return {
319
+ installed,
320
+ installFailed,
321
+ gitFailed,
322
+ git,
323
+ linked,
324
+ skippedLinkForDeps,
325
+ agentSetup,
326
+ agentsRan
327
+ };
328
+ };
260
329
  const isGitRepo = (dir) => existsSync(join(dir, ".git"));
261
330
  const NEON_CONFIG_FILENAMES = [
262
331
  "neon.ts",
@@ -265,7 +334,6 @@ const NEON_CONFIG_FILENAMES = [
265
334
  "neon.mjs"
266
335
  ];
267
336
  const hasNeonConfig = (dir) => NEON_CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)));
268
- const shouldSkipLinkForDeps = (dir, installed) => !installed && hasNeonConfig(dir);
269
337
  const logSkippedLink = (pm) => {
270
338
  log.info(`Skipping the Neon link step: \`${getCliName()} link\` reads this project's neon.ts to pull env vars, which needs its dependencies. Run \`${formatInstallCommand(pm)}\`, then \`${getCliName()} link\`.`);
271
339
  };
@@ -288,9 +356,7 @@ const bootstrapChildren = (props, targetDir) => {
288
356
  * deliberately don't auto-commit, both to avoid failing on a machine with no
289
357
  * git identity configured and to leave the first commit to the user.
290
358
  */
291
- const initGitRepo = async (dir) => {
292
- await runCommand("git", ["init"], dir);
293
- };
359
+ const initGitRepo = async (dir) => runCommand("git", ["init"], dir);
294
360
  const confirm = async (message) => {
295
361
  const { value } = await prompts({
296
362
  onState: onPromptState,
@@ -324,22 +390,56 @@ const selectPackageManager = async () => {
324
390
  return pm ?? "npm";
325
391
  };
326
392
  const printScaffolded = (template, targetDir) => {
327
- log.info("");
328
- log.info("Done. Scaffolded \"%s\" into %s.", template.title, isCurrentDir(targetDir) ? "the current directory" : displayDir(targetDir));
393
+ log.info("Scaffolded \"%s\" into %s.", template.title, isCurrentDir(targetDir) ? "the current directory" : displayDir(targetDir));
329
394
  };
330
- /**
331
- * The closing "Next steps" hint. Skips `cd` for the current directory, omits
332
- * the install line once deps are in, and only nudges `neon link` when linking
333
- * wasn't already offered/run — so the user never sees a step they just did.
334
- */
335
- const printNextSteps = (targetDir, pm, opts) => {
336
- log.info("");
337
- log.info("Next steps:");
338
- if (!isCurrentDir(targetDir)) log.info(" cd %s", displayDir(targetDir));
339
- if (!opts.installed) log.info(" %s", formatInstallCommand(pm));
340
- if (opts.suggestLink) log.info(` ${getCliName()} link`);
341
- log.info(" See the README to run it.");
342
- log.info("");
395
+ const printDoneSummary = (input) => {
396
+ const unfinished = input.installFailed || input.gitFailed;
397
+ const heading = unfinished ? "Setup did not finish." : input.heading;
398
+ const deps = input.installFailed ? "install failed" : input.installed ? `installed with ${input.pm}` : "skipped";
399
+ const git = input.gitFailed ? "init failed" : input.git ? "initialized" : "skipped";
400
+ const project = input.linked ? "linked" : input.skippedLinkForDeps ? "skipped (needs dependencies)" : "not linked";
401
+ const next = [];
402
+ if (!isCurrentDir(input.targetDir)) next.push(`cd ${displayDir(input.targetDir)}`);
403
+ if (!input.installed) next.push(formatInstallCommand(input.pm));
404
+ if (input.suggestLink) next.push(`${getCliName()} link`);
405
+ if (!unfinished) next.push("See the README to run it.");
406
+ printInitDone(formatInitDone({
407
+ heading,
408
+ rows: [
409
+ {
410
+ label: "Template",
411
+ value: input.template.title
412
+ },
413
+ {
414
+ label: "Directory",
415
+ value: displayDir(input.targetDir)
416
+ },
417
+ {
418
+ label: "Dependencies",
419
+ value: deps
420
+ },
421
+ {
422
+ label: "Git",
423
+ value: git
424
+ },
425
+ {
426
+ label: "Agents",
427
+ value: agentSetupDoneLabel({
428
+ setup: input.agentSetup,
429
+ ran: input.agentsRan
430
+ })
431
+ },
432
+ {
433
+ label: "Project",
434
+ value: project
435
+ }
436
+ ],
437
+ next
438
+ }));
439
+ };
440
+ const finishPostScaffold = (input) => {
441
+ printDoneSummary(input);
442
+ if (input.installFailed || input.gitFailed) throw new Error("Setup did not finish.");
343
443
  };
344
444
  const isCurrentDir = (targetDir) => relative(process.cwd(), targetDir) === "";
345
445
  /**
@@ -1,9 +1,9 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
- import { log } from "../log.js";
3
2
  import { readContextFile } from "../context.js";
4
3
  import { getCliName } from "../utils/cli_name.js";
5
4
  import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
6
5
  import { initChildEnv, spawnCliChild } from "../init/child.js";
6
+ import { agentSetupLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
7
7
  import { bootstrapInitStep, directoryIsEmpty, planExistingInit } from "../init/plan.js";
8
8
  import { runAgentTooling, runInitSteps } from "../init/tooling.js";
9
9
  import { helpEpilogue } from "../utils/help_text.js";
@@ -67,10 +67,11 @@ const handler = async (props) => {
67
67
  forward,
68
68
  authEnv
69
69
  });
70
- log.info("Done.");
71
70
  return;
72
71
  }
73
- await runAgentTooling({
72
+ if (shouldPrintInitBanner(yes)) printInitBanner();
73
+ const alreadyLinked = isLinked(contextFile);
74
+ const agentSetup = await runAgentTooling({
74
75
  cwd,
75
76
  yes,
76
77
  run,
@@ -80,7 +81,7 @@ const handler = async (props) => {
80
81
  ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {}
81
82
  });
82
83
  await runInitSteps(planExistingInit({
83
- linked: isLinked(contextFile),
84
+ linked: alreadyLinked,
84
85
  yes,
85
86
  agentSetup: "skip"
86
87
  }), {
@@ -89,7 +90,24 @@ const handler = async (props) => {
89
90
  forward,
90
91
  authEnv
91
92
  });
92
- log.info("Done.");
93
+ printInitDone(formatInitDone({
94
+ heading: "Configured this directory for Neon.",
95
+ rows: [
96
+ {
97
+ label: "Agents",
98
+ value: agentSetupLabel(agentSetup)
99
+ },
100
+ {
101
+ label: "Project",
102
+ value: alreadyLinked ? "already linked" : "linked"
103
+ },
104
+ {
105
+ label: "Config",
106
+ value: "neon.ts"
107
+ }
108
+ ],
109
+ next: []
110
+ }));
93
111
  };
94
112
  //#endregion
95
113
  export { builder, command, describe, handler, initChildEnv, init_exports as t };
package/dist/errors.js CHANGED
@@ -2,7 +2,8 @@
2
2
  const ERROR_MATCHERS = [
3
3
  [/^Unknown command: (.*)$/, "UNKNOWN_COMMAND"],
4
4
  [/^Missing required argument: (.*)$/, "MISSING_ARGUMENT"],
5
- [/^Failed to open web browser. (.*)$/, "AUTH_BROWSER_FAILED"]
5
+ [/^Failed to open web browser. (.*)$/, "AUTH_BROWSER_FAILED"],
6
+ [/^Setup did not finish\.$/, "NEON_INIT_FAILED"]
6
7
  ];
7
8
  const matchErrorCode = (message) => {
8
9
  if (!message) return "UNKNOWN_ERROR";
package/dist/index.js CHANGED
@@ -162,8 +162,9 @@ async function handleError(msg, err) {
162
162
  }
163
163
  } else {
164
164
  const error = err instanceof Error ? err : new Error(msg || "Unknown error");
165
- sendError(error, matchErrorCode(error.message));
166
- log.error(error.message);
165
+ const code = matchErrorCode(error.message);
166
+ sendError(error, code);
167
+ if (code !== "NEON_INIT_FAILED") log.error(error.message);
167
168
  return false;
168
169
  }
169
170
  }
@@ -0,0 +1,44 @@
1
+ import { isCi } from "../env.js";
2
+ import chalk from "chalk";
3
+ //#region src/init/chrome.ts
4
+ const NEON_GREEN = "#4BB578";
5
+ const INIT_BANNER_LINES = [
6
+ " ██╗ ██╗██████╗ ██████╗ ██╗ ██╗",
7
+ " ███╗ ██║██╔═══╝ ██╔═══██╗███╗ ██║",
8
+ " ████╗██║██████╗ ██║ ██║████╗██║",
9
+ " ██╔████║██╔═══╝ ██║ ██║██╔████║",
10
+ " ██║╚███║██████╗ ╚██████╔╝██║╚███║",
11
+ " ╚═╝ ╚══╝╚═════╝ ╚═════╝ ╚═╝ ╚══╝"
12
+ ];
13
+ const formatInitBanner = () => INIT_BANNER_LINES.join("\n");
14
+ const shouldPrintInitBanner = (yes) => !yes && !isCi() && Boolean(process.stdout.isTTY);
15
+ const printInitBanner = () => {
16
+ process.stdout.write(`\n${chalk.hex(NEON_GREEN)(formatInitBanner())}\n\n${chalk.dim("Let's get this directory set up with Neon.")}\n\n`);
17
+ };
18
+ const formatInitDone = (input) => {
19
+ const labelWidth = Math.max(0, ...input.rows.map((row) => row.label.length));
20
+ const body = input.rows.length === 0 ? "" : `\n\n${input.rows.map((row) => ` ${row.label.padEnd(labelWidth)} ${row.value}`).join("\n")}`;
21
+ const next = input.next.length === 0 ? "" : `\n\nNext:\n${input.next.map((line) => ` ${line}`).join("\n")}`;
22
+ const rule = "-".repeat(input.heading.length);
23
+ return `${input.heading}\n${rule}${body}${next}\n`;
24
+ };
25
+ const printInitDone = (text) => {
26
+ const painted = (text.endsWith("\n") ? text.slice(0, -1) : text).split("\n").map((line, index) => {
27
+ if (index === 0) return chalk.hex(NEON_GREEN).bold(line);
28
+ if (/^-{3,}$/.test(line)) return chalk.hex(NEON_GREEN)(line);
29
+ if (line === "Next:") return chalk.bold(line);
30
+ return line;
31
+ });
32
+ process.stdout.write(`\n${painted.join("\n")}\n\n`);
33
+ };
34
+ const agentSetupLabel = (setup) => {
35
+ if (setup === "plugin") return "plugin";
36
+ if (setup === "skills-mcp") return "skills and MCP";
37
+ return "skipped";
38
+ };
39
+ const agentSetupDoneLabel = (input) => {
40
+ if (!input.ran && input.setup !== "skip") return "not run";
41
+ return agentSetupLabel(input.setup);
42
+ };
43
+ //#endregion
44
+ export { INIT_BANNER_LINES, NEON_GREEN, agentSetupDoneLabel, agentSetupLabel, formatInitBanner, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner };
package/dist/init/plan.js CHANGED
@@ -12,6 +12,21 @@ const projectContextFile = (projectDir, contextFile) => {
12
12
  if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return join(projectDir, ".neon");
13
13
  return resolved;
14
14
  };
15
+ /** Linking a template with neon.ts requires installed dependencies because env pull evaluates it. */
16
+ const postScaffoldActions = (input) => {
17
+ const actions = [];
18
+ if (input.git) actions.push("git");
19
+ if (input.agentSetup !== "skip") actions.push("agent");
20
+ const canLink = input.link && !(input.hasNeonConfig && !input.install);
21
+ if (canLink && input.hasNeonConfig && input.install) {
22
+ actions.push("install");
23
+ actions.push("link");
24
+ return actions;
25
+ }
26
+ if (canLink) actions.push("link");
27
+ if (input.install) actions.push("install");
28
+ return actions;
29
+ };
15
30
  const planAgentSteps = (input) => {
16
31
  const y = input.yes ? ["-y"] : [];
17
32
  if (input.agentSetup === "plugin") return [["plugins", ...y]];
@@ -44,4 +59,4 @@ const childArgv = (step, forward) => {
44
59
  return args;
45
60
  };
46
61
  //#endregion
47
- export { bootstrapInitStep, childArgv, directoryIsEmpty, planAgentSteps, planExistingInit, projectContextFile, resolveInitAgentSetup };
62
+ export { bootstrapInitStep, childArgv, directoryIsEmpty, planAgentSteps, planExistingInit, postScaffoldActions, projectContextFile, resolveInitAgentSetup };
@@ -24,7 +24,7 @@ const runAgentTooling = async (options) => {
24
24
  const yes = options.yes;
25
25
  const hasProjectPlugins = yes ? await (options.hasProjectPlugins ?? defaultHasProjectPlugins)(options.cwd) : false;
26
26
  const interactive = !yes && (options.pickAgentSetup !== void 0 || canPickAgentsInteractively());
27
- const agentSetup = await resolveInitAgentSetup({
27
+ const agentSetup = options.agentSetup ?? await resolveInitAgentSetup({
28
28
  yes,
29
29
  interactive,
30
30
  hasProjectPlugins,
@@ -34,6 +34,7 @@ const runAgentTooling = async (options) => {
34
34
  yes,
35
35
  agentSetup
36
36
  }), options);
37
+ return agentSetup;
37
38
  };
38
39
  const runScaffoldFollowUp = async (options) => {
39
40
  if (!options.skipAgentSetup) await runAgentTooling(options);
@@ -3,11 +3,6 @@ import { helpCsv } from "../utils/help_text.js";
3
3
  const AGENT_SKILLS_SOURCE = "neondatabase/agent-skills";
4
4
  const PLATFORMS_SKILLS_SOURCE = "neondatabase/neon-for-agent-platforms";
5
5
  const NEON_SKILL_CATALOG = [
6
- {
7
- skill: "claimable-postgres",
8
- source: AGENT_SKILLS_SOURCE,
9
- defaultSelected: true
10
- },
11
6
  {
12
7
  skill: "neon",
13
8
  source: AGENT_SKILLS_SOURCE,
@@ -112,7 +112,7 @@ const INSPECT_QUERIES = {
112
112
  `
113
113
  },
114
114
  "stalled-queries": {
115
- describe: "Active queries running longer than 30 seconds with parallel workers, waits, and blockers (compute-wide)",
115
+ describe: "Active queries running longer than 30 seconds with parallel workers, waits, and blockers, oldest group first (compute-wide)",
116
116
  scope: "compute",
117
117
  fields: [
118
118
  "duration",
@@ -132,9 +132,12 @@ const INSPECT_QUERIES = {
132
132
  AND pid <> pg_backend_pid()
133
133
  ),
134
134
  stalled_groups AS (
135
- SELECT DISTINCT COALESCE(leader_pid, pid) AS query_group
135
+ SELECT
136
+ COALESCE(leader_pid, pid) AS query_group,
137
+ min(query_start) AS group_start
136
138
  FROM activity
137
139
  WHERE query_start <= statement_timestamp() - interval '30 seconds'
140
+ GROUP BY COALESCE(leader_pid, pid)
138
141
  )
139
142
  SELECT
140
143
  statement_timestamp() AS observed_at,
@@ -159,7 +162,7 @@ const INSPECT_QUERIES = {
159
162
  FROM activity a
160
163
  JOIN stalled_groups g
161
164
  ON g.query_group = COALESCE(a.leader_pid, a.pid)
162
- ORDER BY g.query_group, a.leader_pid NULLS FIRST, a.pid;
165
+ ORDER BY g.group_start, g.query_group, a.leader_pid NULLS FIRST, a.pid;
163
166
  `
164
167
  },
165
168
  locks: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "4.10.0",
3
+ "version": "4.10.2",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -64,8 +64,8 @@
64
64
  "yargs": "17.7.2",
65
65
  "yoctocolors": "^2.1.2",
66
66
  "@neon/config": "1.0.5",
67
- "@neon/sdk": "3.0.0",
68
- "@neon/config-runtime": "1.0.5"
67
+ "@neon/config-runtime": "1.0.5",
68
+ "@neon/sdk": "3.0.0"
69
69
  },
70
70
  "optionalDependencies": {
71
71
  "@napi-rs/keyring": "1.3.0",