neon 4.14.6 → 4.16.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.
@@ -0,0 +1,46 @@
1
+ import { getCliName } from "../utils/cli_name.js";
2
+ //#region src/init/choices.ts
3
+ const INIT_TEMPLATE_CONFLICT = "--skip-template cannot be combined with --template.";
4
+ const INIT_CONFIG_SERVICES_CONFLICT = "--no-config cannot be combined with --services.";
5
+ const INIT_TEMPLATE_KEEPS_CONFIG = "A scaffolded template keeps the neon.ts it ships. --config, --no-config, and --services apply when you skip the template or set up an existing app.";
6
+ const initTemplateInNonEmptyMessage = () => `--template is only for an empty directory. This directory already has files, so there is nothing to scaffold. Omit --template, or run \`${getCliName()} bootstrap\` in an empty folder.`;
7
+ const resolveInitTemplateChoice = (input) => {
8
+ const id = input.template?.trim();
9
+ const hasTemplate = id !== void 0 && id.length > 0;
10
+ if (input.skipTemplate && hasTemplate) throw new Error(INIT_TEMPLATE_CONFLICT);
11
+ if (!input.empty) {
12
+ if (hasTemplate) throw new Error(initTemplateInNonEmptyMessage());
13
+ return { kind: "existing" };
14
+ }
15
+ if (input.skipTemplate) return { kind: "skip" };
16
+ if (hasTemplate) return {
17
+ kind: "template",
18
+ id
19
+ };
20
+ if (input.yes) return { kind: "default" };
21
+ return { kind: "ask" };
22
+ };
23
+ const resolveInitConfigChoice = (input) => {
24
+ if (input.flag === false && input.services !== void 0) throw new Error(INIT_CONFIG_SERVICES_CONFLICT);
25
+ if (input.flag === false) return { kind: "skip" };
26
+ if (input.existingConfig) return { kind: "write" };
27
+ if (input.services !== void 0) return {
28
+ kind: "write",
29
+ services: input.services
30
+ };
31
+ if (input.yes) return {
32
+ kind: "write",
33
+ services: ["none"]
34
+ };
35
+ if (input.flag === true) return { kind: "write" };
36
+ if (input.canAsk) return { kind: "ask" };
37
+ return { kind: "write" };
38
+ };
39
+ const configPlanFromResolution = (resolution, accepted) => {
40
+ if (resolution.kind === "ask") return accepted === true ? { kind: "write" } : { kind: "skip" };
41
+ if (resolution.kind === "skip") return { kind: "skip" };
42
+ return resolution;
43
+ };
44
+ const shouldRefreshEnvAfterNewConfig = (input) => input.wroteNewFile && typeof input.projectId === "string" && input.projectId.length > 0 && typeof input.branch === "string" && input.branch.length > 0;
45
+ //#endregion
46
+ export { INIT_CONFIG_SERVICES_CONFLICT, INIT_TEMPLATE_CONFLICT, INIT_TEMPLATE_KEEPS_CONFIG, configPlanFromResolution, initTemplateInNonEmptyMessage, resolveInitConfigChoice, resolveInitTemplateChoice, shouldRefreshEnvAfterNewConfig };
@@ -13,7 +13,7 @@ const INIT_BANNER_LINES = [
13
13
  const formatInitBanner = () => INIT_BANNER_LINES.join("\n");
14
14
  const shouldPrintInitBanner = (yes) => !yes && !isCi() && Boolean(process.stdout.isTTY);
15
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`);
16
+ process.stdout.write(`\n${chalk.hex(NEON_GREEN)(formatInitBanner())}\n\n${chalk.dim("Set up this directory for Neon.")}\n\n`);
17
17
  };
18
18
  const formatInitDone = (input) => {
19
19
  const labelWidth = Math.max(0, ...input.rows.map((row) => row.label.length));
@@ -40,5 +40,28 @@ const agentSetupDoneLabel = (input) => {
40
40
  if (!input.ran && input.setup !== "skip") return "not run";
41
41
  return agentSetupLabel(input.setup);
42
42
  };
43
+ const configSummaryLabel = (summary) => {
44
+ switch (summary) {
45
+ case "created": return "neon.ts created";
46
+ case "skipped": return "skipped";
47
+ case "existing": return "existing Neon config";
48
+ case "template": return "provided by template";
49
+ default: return summary;
50
+ }
51
+ };
52
+ const INIT_STEP_LABELS = {
53
+ bootstrap: "Creating the app from the selected template…",
54
+ plugins: "Installing the Neon plugin…",
55
+ skills: "Installing Neon agent skills…",
56
+ mcp: "Setting up the Neon MCP server…",
57
+ link: "Linking a Neon project…",
58
+ config: "Setting up neon.ts…",
59
+ env: "Refreshing Neon env vars…"
60
+ };
61
+ const initStepLabel = (step) => {
62
+ const command = step[0];
63
+ if (command === void 0) return;
64
+ return INIT_STEP_LABELS[command];
65
+ };
43
66
  //#endregion
44
- export { INIT_BANNER_LINES, NEON_GREEN, agentSetupDoneLabel, agentSetupLabel, formatInitBanner, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner };
67
+ export { INIT_BANNER_LINES, INIT_STEP_LABELS, NEON_GREEN, agentSetupDoneLabel, agentSetupLabel, configSummaryLabel, formatInitBanner, formatInitDone, initStepLabel, printInitBanner, printInitDone, shouldPrintInitBanner };
package/dist/init/plan.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { getCliName } from "../utils/cli_name.js";
2
+ import { NO_SERVICES } from "../neon_services.js";
2
3
  import { tryResolveAddMcpAgentId, uniqueAgentIds } from "../mcp/agents.js";
3
4
  import { mcpInstallableAgents } from "../mcp/targets.js";
4
5
  import { supportsSkills } from "./agents.js";
@@ -93,6 +94,9 @@ const BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
93
94
  "--no-env-pull",
94
95
  "--no-agent-setup",
95
96
  "--no-link",
97
+ "--no-config",
98
+ "--skip-template",
99
+ "--config",
96
100
  "--list",
97
101
  "--list-templates",
98
102
  "--force-auth",
@@ -218,15 +222,46 @@ const resolveNamedAgents = (raw) => {
218
222
  }
219
223
  return uniqueAgentIds(resolved);
220
224
  };
221
- const planExistingInit = (input) => {
222
- const steps = [...planAgentSteps(input)];
223
- if (!input.linked) steps.push(input.yes ? ["link", "--yes"] : ["link"]);
224
- steps.push(input.yes ? [
225
+ const planLinkStep = (input) => [
226
+ "link",
227
+ ...input.yes ? ["--yes"] : [],
228
+ "--no-config",
229
+ ...input.extra ?? []
230
+ ];
231
+ const planConfigInitStep = (input) => {
232
+ if (input.services !== void 0) return [
233
+ "config",
234
+ "init",
235
+ "--services",
236
+ input.services.length === 0 ? NO_SERVICES : input.services.join(",")
237
+ ];
238
+ if (input.yes) return [
225
239
  "config",
226
240
  "init",
227
241
  "--services",
228
- "none"
229
- ] : ["config", "init"]);
242
+ NO_SERVICES
243
+ ];
244
+ return ["config", "init"];
245
+ };
246
+ const linkInputArgv = (input) => {
247
+ const args = [];
248
+ if (input.orgId) args.push("--org-id", input.orgId);
249
+ if (input.projectId) args.push("--project-id", input.projectId);
250
+ if (input.projectName) args.push("--project-name", input.projectName);
251
+ if (input.regionId) args.push("--region-id", input.regionId);
252
+ if (input.branch) args.push("--branch", input.branch);
253
+ return args;
254
+ };
255
+ const planExistingInit = (input) => {
256
+ const steps = [...planAgentSteps(input)];
257
+ if (!input.linked) steps.push(planLinkStep({
258
+ yes: input.yes,
259
+ extra: input.linkExtra
260
+ }));
261
+ if (input.config.kind === "write") steps.push(planConfigInitStep({
262
+ yes: input.yes,
263
+ services: input.config.services
264
+ }));
230
265
  return steps;
231
266
  };
232
267
  const resolveInitAgentSetup = async (input) => {
@@ -243,4 +278,4 @@ const childArgv = (step, forward) => {
243
278
  return args;
244
279
  };
245
280
  //#endregion
246
- export { INIT_NEEDS_YES_OR_TERMINAL, NAMED_AGENTS_MIXED, NAMED_AGENTS_UNSUPPORTED, assertNamedAgentTooling, bootstrapInitStep, childArgv, chooseYesAgentTooling, collectYesAgents, directoryIsEmpty, initPluginAgents, initSkillsMcpAgents, initYesSupportedAgents, namedAgentsMixedMessage, namedAgentsNeedSplit, noDetectedAgentsMessage, planAgentSteps, planExistingInit, planToolingSteps, planYesAgentSteps, postScaffoldActions, projectContextFile, resolveInitAgentSetup, resolveNamedAgents, resolveYesAgentList, rewriteUnknownAgentArg };
281
+ export { INIT_NEEDS_YES_OR_TERMINAL, NAMED_AGENTS_MIXED, NAMED_AGENTS_UNSUPPORTED, assertNamedAgentTooling, bootstrapInitStep, childArgv, chooseYesAgentTooling, collectYesAgents, directoryIsEmpty, initPluginAgents, initSkillsMcpAgents, initYesSupportedAgents, linkInputArgv, namedAgentsMixedMessage, namedAgentsNeedSplit, noDetectedAgentsMessage, planAgentSteps, planConfigInitStep, planExistingInit, planLinkStep, planToolingSteps, planYesAgentSteps, postScaffoldActions, projectContextFile, resolveInitAgentSetup, resolveNamedAgents, resolveYesAgentList, rewriteUnknownAgentArg };
@@ -0,0 +1,15 @@
1
+ import chalk from "chalk";
2
+ //#region src/init/template_title.ts
3
+ const SKIP_TEMPLATE_VALUE = "skip";
4
+ /**
5
+ * The picker label for a template: the title first, then the Neon services it
6
+ * uses as a dim, italic suffix. The suffix is styled with chalk.dim (and italic)
7
+ * only — never a foreground color — so it survives the cyan/underline `prompts`
8
+ * paints over the focused row.
9
+ */
10
+ const formatTemplateTitle = (template) => {
11
+ if (!template.services || template.services.length === 0) return template.title;
12
+ return `${template.title} ${chalk.dim.italic(template.services.join(" · "))}`;
13
+ };
14
+ //#endregion
15
+ export { SKIP_TEMPLATE_VALUE, formatTemplateTitle };
@@ -1,6 +1,7 @@
1
1
  import { log } from "../log.js";
2
2
  import { getCliName } from "../utils/cli_name.js";
3
3
  import { AUTH_CHILD } from "./child.js";
4
+ import { initStepLabel } from "./chrome.js";
4
5
  import { assertNamedAgentTooling, childArgv, chooseYesAgentTooling, collectYesAgents, initYesSupportedAgents, noDetectedAgentsMessage, planAgentSteps, planToolingSteps, planYesAgentSteps, resolveInitAgentSetup } from "./plan.js";
5
6
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
6
7
  import { detectAgent } from "./detect_host.js";
@@ -10,7 +11,10 @@ import { detectProjectAgents } from "add-mcp";
10
11
  const defaultProjectAgents = (cwd) => detectProjectAgents(cwd);
11
12
  const runInitSteps = async (steps, options) => {
12
13
  for (const step of steps) {
13
- log.info("Running `%s %s`", getCliName(), step.join(" "));
14
+ const label = options.narrate === "human" ? initStepLabel(step) : void 0;
15
+ if (label !== void 0) log.info(label);
16
+ else log.info("Running `%s %s`", getCliName(), step.join(" "));
17
+ log.debug("Running `%s %s`", getCliName(), step.join(" "));
14
18
  const argv = childArgv(step, options.forward);
15
19
  const command = step[0];
16
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.`);
@@ -1,6 +1,8 @@
1
1
  import { INIT_NEEDS_YES_OR_TERMINAL } from "./plan.js";
2
+ import { SKIP_TEMPLATE_VALUE, formatTemplateTitle } from "./template_title.js";
2
3
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
3
4
  import { NEON_PLUGIN_NAME } from "../plugins/run.js";
5
+ import chalk from "chalk";
4
6
  import prompts from "prompts";
5
7
  //#region src/init/wizard.ts
6
8
  const restoreCursorOnAbort = (state) => {
@@ -10,19 +12,22 @@ const restoreCursorOnAbort = (state) => {
10
12
  process.exit(1);
11
13
  }
12
14
  };
13
- const pickAgentSetupInteractively = async () => {
15
+ const requireInteractive = () => {
14
16
  if (!canPickAgentsInteractively()) throw new Error(INIT_NEEDS_YES_OR_TERMINAL);
17
+ };
18
+ const pickAgentSetupInteractively = async () => {
19
+ requireInteractive();
15
20
  const { setup } = await prompts({
16
21
  onState: restoreCursorOnAbort,
17
22
  type: "select",
18
23
  name: "setup",
19
- message: "How should coding agents get Neon in this project?",
24
+ message: "How would you like to set up your coding agents?",
20
25
  initial: 0,
21
26
  choices: [
22
27
  {
23
- title: "Plugin (recommended)",
28
+ title: "Neon plugin (recommended)",
24
29
  value: "plugin",
25
- description: `Install ${NEON_PLUGIN_NAME} (skills and MCP in one)`
30
+ description: `Install ${NEON_PLUGIN_NAME} (skills and MCP together)`
26
31
  },
27
32
  {
28
33
  title: "Skills and MCP separately",
@@ -32,12 +37,59 @@ const pickAgentSetupInteractively = async () => {
32
37
  {
33
38
  title: "Skip agent setup",
34
39
  value: "skip",
35
- description: "Continue without a plugin, skills, or MCP"
40
+ description: "Continue to project setup"
36
41
  }
37
42
  ]
38
43
  });
39
44
  if (setup !== "plugin" && setup !== "skills-mcp" && setup !== "skip") throw new Error("Aborted.");
40
45
  return setup;
41
46
  };
47
+ /** First catalog template; skip is index 0 so it stays on the first screen. */
48
+ const INIT_TEMPLATE_PICKER_INITIAL = 1;
49
+ const initTemplatePickerChoices = (templates) => {
50
+ if (templates.length === 0) throw new Error("No templates available to scaffold from.");
51
+ return [{
52
+ title: "Skip the template",
53
+ value: SKIP_TEMPLATE_VALUE,
54
+ description: "Set up agents and link a Neon project without copying template files."
55
+ }, ...templates.map((template) => ({
56
+ title: formatTemplateTitle(template),
57
+ value: template.id,
58
+ description: template.description ?? ""
59
+ }))];
60
+ };
61
+ const pickInitTemplateInteractively = async (templates) => {
62
+ requireInteractive();
63
+ const choices = initTemplatePickerChoices(templates);
64
+ const { id } = await prompts({
65
+ onState: restoreCursorOnAbort,
66
+ type: "select",
67
+ name: "id",
68
+ message: "How would you like to set up this directory?",
69
+ initial: 1,
70
+ choices
71
+ });
72
+ if (id === "skip") return { kind: "skip" };
73
+ if (typeof id !== "string" || id.length === 0) throw new Error("Aborted.");
74
+ const selected = templates.find((template) => template.id === id);
75
+ if (selected === void 0) throw new Error("Aborted.");
76
+ return {
77
+ kind: "template",
78
+ template: selected
79
+ };
80
+ };
81
+ const pickInitConfigInteractively = async () => {
82
+ requireInteractive();
83
+ process.stdout.write(`${chalk.dim("Choose services, then edit neon.ts and apply changes with neon config apply.")}\n`);
84
+ const { value } = await prompts({
85
+ onState: restoreCursorOnAbort,
86
+ type: "confirm",
87
+ name: "value",
88
+ message: "Create neon.ts to manage this project's Neon setup as code?",
89
+ initial: true
90
+ });
91
+ if (value === void 0) throw new Error("Aborted.");
92
+ return value === true;
93
+ };
42
94
  //#endregion
43
- export { pickAgentSetupInteractively };
95
+ export { INIT_TEMPLATE_PICKER_INITIAL, initTemplatePickerChoices, pickAgentSetupInteractively, pickInitConfigInteractively, pickInitTemplateInteractively };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "4.14.6",
3
+ "version": "4.16.0",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -63,9 +63,9 @@
63
63
  "yaml": "^2.9.0",
64
64
  "yargs": "17.7.2",
65
65
  "yoctocolors": "^2.1.2",
66
- "@neon/config-runtime": "1.2.3",
67
- "@neon/config": "1.3.2",
68
- "@neon/sdk": "4.0.0"
66
+ "@neon/config": "1.3.3",
67
+ "@neon/config-runtime": "1.2.4",
68
+ "@neon/sdk": "4.1.0"
69
69
  },
70
70
  "optionalDependencies": {
71
71
  "@napi-rs/keyring": "1.3.0",
@@ -98,8 +98,8 @@
98
98
  "typescript": "^5.9.0",
99
99
  "vitest": "^3.0.9",
100
100
  "@neon-internals/cli-core": "0.0.0",
101
+ "@neon-internals/env-core": "0.0.11",
101
102
  "@neon/e2e-harness": "0.0.0",
102
- "@neon-internals/env-core": "0.0.10",
103
103
  "@neon/functions": "0.9.0"
104
104
  },
105
105
  "publishConfig": {