neon 6.2.4 → 6.4.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.
package/README.md CHANGED
@@ -665,7 +665,37 @@ export default async function hello(): Promise<Response> {
665
665
  }
666
666
  ```
667
667
 
668
- An existing `neon.ts` (or `hello.ts`) is never overwritten.
668
+ An existing `neon.ts` (or `hello.ts`) is never overwritten. To change one, use `config add`.
669
+
670
+ ### Editing a `neon.ts` (`config add`)
671
+
672
+ `neon config add` declares one service in the `neon.ts` you already have and leaves the rest of the file (comments, the `branch` closure, formatting) as it was. It is purely local — no auth, no API calls — and creates the starter `neon.ts` first when there is none.
673
+
674
+ ```bash
675
+ neon config add auth # auth: true
676
+ neon config add data-api # dataApi: true, and auth: true (the default provider needs it)
677
+ neon config add ai-gateway # aiGateway: true
678
+ neon config add function sendemail # functions.sendemail + functions/sendemail.ts
679
+ neon config add function sendemail --name "Send email" --source ./src/send.ts
680
+ neon config add bucket uploads # buckets.uploads = { access: 'private' }
681
+ neon config add bucket assets --access public_read
682
+ ```
683
+
684
+ Each command prints what it changed and the next step:
685
+
686
+ ```
687
+ $ neon config add function sendemail
688
+ INFO: Created functions/sendemail.ts.
689
+ INFO: Updated neon.ts: added functions.sendemail.
690
+ INFO: Next: `neon dev` to run it locally, `neon config apply` to deploy.
691
+ ```
692
+
693
+ - **Functions** take a slug of 1-20 lowercase letters and digits, the same rule `plan` enforces. The handler file is written first and an existing file is never overwritten, so pointing `--source` at code you already have works. A `.js` `neon.ts` gets a `.js` handler.
694
+ - Enabling a service that `neon.ts` already enables changes nothing and exits 0. Adding a function slug or bucket name that already exists is an error.
695
+ - `--config <path>` edits a specific file; otherwise the closest `neon.ts` above the current directory is used, and the suggested `plan` / `apply` commands repeat the flag.
696
+ - `--no-install` applies when the command had to create `neon.ts`; editing an existing file never installs anything.
697
+
698
+ `config add` edits the file as text and checks its own output: it re-applies the edit to the result and fails if that is not a no-op. When the file is in a shape it cannot edit safely (a single-line `defineConfig({ auth: true })`, a service declared under `preview`, a value that is not a literal), it changes nothing and prints the exact lines to paste.
669
699
 
670
700
  Four sub-commands plus two top-level aliases drive it:
671
701
 
@@ -792,7 +822,7 @@ Recommended installs the Neon plugin for every detected agent (global config, pr
792
822
 
793
823
  Unauthenticated `-y` skips linking and prints the next step: sign up at https://neon.com/signup, then `neon auth`, `neon link`, or `neon claim create` (no account, expires in 72 hours unless claimed). `-y` never opens a browser. `-y` with `--project-id` or other account flags links when the CLI is already signed in; otherwise it errors and names `neon auth` or `--claimable`.
794
824
 
795
- 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. 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.
825
+ 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.
796
826
 
797
827
  Empty directories are set up in place. `neon init` does not scaffold starter files.
798
828
 
@@ -5,7 +5,7 @@ import { log } from "../log.js";
5
5
  import { getApiClient, isNeonApiError } from "../api.js";
6
6
  import { setAuthContext } from "../auth_context.js";
7
7
  import { a as isOwnedCredentialPath } from "../_chunks/paths-DMq0Lt7a.js";
8
- import { currentContextFile, isAskCommand, isClaimCommand, isConfigInit, isCurrentBranchProbe, isGitLocalCommand, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, isUnfollowedGitHookSync, readContextFile } from "../context.js";
8
+ import { currentContextFile, isAskCommand, isClaimCommand, isConfigAdd, isConfigInit, isCurrentBranchProbe, isGitLocalCommand, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, isUnfollowedGitHookSync, readContextFile } from "../context.js";
9
9
  import { _ as upsertProfile, d as newProfileLocation, g as selectProfileName, h as resolveProfile, i as assertValidProfileName, l as locationForName, m as readProfiles, n as KEYRING_CREDENTIALS, p as profilesUsingPath, r as assertProfilesUsable, s as isKeyringPointer, t as DEFAULT_PROFILE, u as locationOf } from "../_chunks/profiles-CvnFEQyd.js";
10
10
  import { extendTokenSet } from "../utils/auth.js";
11
11
  import { AuthRefreshError, auth, classifyRefreshFailure, refreshToken } from "../auth.js";
@@ -296,6 +296,7 @@ const ensureAuth = async (props) => {
296
296
  if (props._.length === 0 || props.help) return;
297
297
  if (isCurrentBranchProbe(props)) return;
298
298
  if (isConfigInit(props)) return;
299
+ if (isConfigAdd(props)) return;
299
300
  if (isProfileCommand(props)) return;
300
301
  if (isGitLocalCommand(props)) return;
301
302
  if (isUnfollowedGitHookSync(props)) return;
@@ -6,8 +6,8 @@ import { applyContext, contextBranch, readContextFile } from "../context.js";
6
6
  import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
7
7
  import { ClaimableClient, ClaimableServiceError } from "../claimable/api.js";
8
8
  import { assertionHasExpired, listClaimableCredentials, readClaimableCredentials, readLinkedClaimableCredentials, removeClaimableCredentials, writeClaimableCredentials } from "../claimable/state.js";
9
- import { claimableDataApiCreateBody, declaredNeonServices } from "../config_services.js";
10
9
  import { NEON_SERVICES, deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
10
+ import { claimableDataApiCreateBody, declaredNeonServices } from "../config_services.js";
11
11
  import { resolveEnvFilePath } from "../env_file.js";
12
12
  import { writer } from "../writer.js";
13
13
  import { noPassthrough } from "../utils/flags.js";
@@ -4,10 +4,11 @@ import { log } from "../log.js";
4
4
  import { getApiClient } from "../api.js";
5
5
  import { contextBranch, ensureDirectoryGitignored, readContextFile } from "../context.js";
6
6
  import { getCliName } from "../utils/cli_name.js";
7
+ import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
8
+ import { CONFIG_INIT_NONE_MEANS, CONFIG_INIT_SERVICES, CONFIG_INIT_UNAVAILABLE, FUNCTION_FILENAME, FUNCTION_SLUG, FUNCTION_TEMPLATE, REQUIRED_PACKAGES, renderFunctionSource, renderNeonConfig, renderNeonConfigFromView } from "../config_template.js";
9
+ import { editNeonConfig } from "../config_edit.js";
7
10
  import { toNeonConfigView } from "../config_format.js";
8
11
  import { declaredNeonServices } from "../config_services.js";
9
- import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
10
- import { CONFIG_INIT_NONE_MEANS, CONFIG_INIT_SERVICES, CONFIG_INIT_UNAVAILABLE, FUNCTION_FILENAME, FUNCTION_SLUG, FUNCTION_TEMPLATE, REQUIRED_PACKAGES, renderNeonConfig, renderNeonConfigFromView } from "../config_template.js";
11
12
  import { loadEnvFileIntoProcess } from "../env_file.js";
12
13
  import { writer } from "../writer.js";
13
14
  import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
@@ -21,14 +22,16 @@ import { resolveHookEnv, runDeployAfterHook, runDeployBeforeHook } from "../util
21
22
  import { pickServicesInteractively } from "../utils/service_picker.js";
22
23
  import { zipBundle } from "../utils/zip.js";
23
24
  import { autoPullEnvAfterPin } from "./env.js";
24
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
25
- import { dirname, join } from "node:path";
25
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
26
+ import { dirname, join, relative, resolve } from "node:path";
27
+ import { homedir } from "node:os";
26
28
  import chalk from "chalk";
27
29
  import { packagesToStage, previewGaWarningForConfig, resolveConfig } from "@neon/config";
28
30
  import { PushConflictError, apply, assertZipWithinLimits, bundleAsIs, createBranch, describeNativeFinding, enforceLimits, findUndeclaredNativePackages, inspect, isPartialBranchCreateError, loadConfigFromFile as loadConfigFromFile$1, plan, resolveEsbuildEntry, traceNativePackages, zipFunctionBundle } from "@neon/config-runtime";
29
31
  //#region src/commands/config.ts
30
32
  var config_exports = /* @__PURE__ */ __exportAll({
31
33
  ConfigInstallFailed: () => ConfigInstallFailed,
34
+ addCmd: () => addCmd,
32
35
  applyCmd: () => applyCmd,
33
36
  applyFlags: () => applyFlags,
34
37
  applyPolicyOnCreate: () => applyPolicyOnCreate,
@@ -213,6 +216,28 @@ const seedFromBranch = async (props) => {
213
216
  branchName: live.branch.name
214
217
  };
215
218
  };
219
+ /** Install the config packages the project is missing, or say how to. */
220
+ const ensureConfigPackages = async (cwd, props) => {
221
+ const run = props.run ?? runCommand;
222
+ const missing = missingDependencies(cwd);
223
+ if (missing.length === 0) {
224
+ if (!props.silent) log.info("%s are already installed.", REQUIRED_PACKAGES.join(" and "));
225
+ return;
226
+ }
227
+ const pm = props.packageManager ?? resolvePackageManager(cwd);
228
+ const args = installArgs(pm, missing);
229
+ if (props.install === false) {
230
+ if (!props.silent) log.info("Install the Neon config packages to use neon.ts: %s", formatInstallCommand(pm, missing));
231
+ return;
232
+ }
233
+ ensureDirectoryGitignored(join(cwd, "node_modules"));
234
+ if (!props.silent) log.info("Installing %s with %s…", missing.join(", "), pm);
235
+ if (!await run(pm, args, cwd)) {
236
+ const command = formatInstallCommand(pm, missing);
237
+ if (props.requireInstall) throw new ConfigInstallFailed(command);
238
+ log.warning("Could not install the config packages automatically. Run by hand: %s", command);
239
+ }
240
+ };
216
241
  /**
217
242
  * Scaffold a `neon.ts` policy and make sure the Neon config packages are
218
243
  * installed, so a project can go straight to `neon config plan` / `apply`.
@@ -220,10 +245,12 @@ const seedFromBranch = async (props) => {
220
245
  */
221
246
  const initCmd = async (props) => {
222
247
  const cwd = props.cwd ?? process.cwd();
223
- const run = props.run ?? runCommand;
224
248
  const existing = neonConfigFilename(cwd);
225
249
  if (existing) {
226
- if (!props.silent) log.info("Found an existing %s — leaving it untouched.", existing);
250
+ if (!props.silent) {
251
+ log.info("Found an existing %s — leaving it untouched.", existing);
252
+ if (props.services !== void 0) log.warning("--services was ignored. To declare services in the existing %s, use `%s config add`.", existing, getCliName());
253
+ }
227
254
  } else if (props.fromBranch) {
228
255
  const { source, seeded, branchName } = await seedFromBranch(props);
229
256
  writeFileSync(join(cwd, "neon.ts"), source);
@@ -240,26 +267,139 @@ const initCmd = async (props) => {
240
267
  }
241
268
  if (services.includes("functions")) scaffoldFunction(cwd);
242
269
  }
243
- const missing = missingDependencies(cwd);
244
- if (missing.length === 0) {
245
- if (!props.silent) log.info("%s are already installed.", REQUIRED_PACKAGES.join(" and "));
246
- } else {
247
- const pm = props.packageManager ?? resolvePackageManager(cwd);
248
- const args = installArgs(pm, missing);
249
- if (props.install === false) {
250
- if (!props.silent) log.info("Install the Neon config packages to use neon.ts: %s", formatInstallCommand(pm, missing));
251
- } else {
252
- ensureDirectoryGitignored(join(cwd, "node_modules"));
253
- if (!props.silent) log.info("Installing %s with %s…", missing.join(", "), pm);
254
- if (!await run(pm, args, cwd)) {
255
- const command = formatInstallCommand(pm, missing);
256
- if (props.requireInstall) throw new ConfigInstallFailed(command);
257
- log.warning("Could not install the config packages automatically. Run by hand: %s", command);
270
+ await ensureConfigPackages(cwd, props);
271
+ if (!props.silent) log.info("Next: edit neon.ts, then run `neon config plan` to preview and `neon config apply`.");
272
+ };
273
+ /** The rule the platform enforces on function slugs, so a bad one fails before any write. */
274
+ const FUNCTION_SLUG_PATTERN = /^[a-z0-9]{1,20}$/;
275
+ const SCRIPT_EXTENSION = /\.(?:ts|mts|cts|js|mjs|cjs)$/;
276
+ const TYPESCRIPT_EXTENSION = /\.(?:ts|mts|cts)$/;
277
+ /**
278
+ * The nearest neon.ts, found the way the config loader finds it: walk up from `start`, stop
279
+ * at the first directory holding `.git` (or at the home directory).
280
+ */
281
+ const findNeonConfigUpward = (start) => {
282
+ let dir = resolve(start);
283
+ for (;;) {
284
+ const name = neonConfigFilename(dir);
285
+ if (name) return join(dir, name);
286
+ if (existsSync(join(dir, ".git")) || dir === homedir()) return void 0;
287
+ const parent = dirname(dir);
288
+ if (parent === dir) return void 0;
289
+ dir = parent;
290
+ }
291
+ };
292
+ const asRelativeModulePath = (path) => path.startsWith("./") || path.startsWith("../") || path.startsWith("/") ? path : `./${path}`;
293
+ const shellQuote = (value) => /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
294
+ /**
295
+ * `explicitConfig` is the `--config` the user passed. The commands suggested afterwards must
296
+ * carry it, or they would act on whichever `neon.ts` a walk up from cwd finds instead.
297
+ */
298
+ const planAdd = (target, configPath, explicitConfig) => {
299
+ const language = TYPESCRIPT_EXTENSION.test(configPath) ? "ts" : "js";
300
+ const cli = getCliName();
301
+ const flag = explicitConfig ? ` --config ${shellQuote(explicitConfig)}` : "";
302
+ const provision = `Next: \`${cli} config plan${flag}\` to preview, \`${cli} config apply${flag}\` to provision.`;
303
+ switch (target.kind) {
304
+ case "service": return {
305
+ edit: {
306
+ kind: "service",
307
+ service: target.service
308
+ },
309
+ next: provision
310
+ };
311
+ case "bucket":
312
+ if (target.name.length < 1 || target.name.length > 255) throw new Error("A bucket name must be 1-255 characters.");
313
+ if (target.name === "__proto__") throw new Error("\"__proto__\" cannot be used as a bucket name.");
314
+ return {
315
+ edit: {
316
+ kind: "bucket",
317
+ name: target.name,
318
+ access: target.access ?? "private"
319
+ },
320
+ next: provision
321
+ };
322
+ case "function": {
323
+ if (!FUNCTION_SLUG_PATTERN.test(target.slug)) {
324
+ const suggestion = target.slug.toLowerCase().replace(/[^a-z0-9]/g, "").slice(0, 20);
325
+ throw new Error(`"${target.slug}" is not a valid function slug: use 1-20 lowercase letters and digits, no hyphens.` + (suggestion ? ` Try "${suggestion}".` : ""));
258
326
  }
327
+ if (target.source !== void 0 && !SCRIPT_EXTENSION.test(target.source)) throw new Error(`--source must be a .ts or .js file, got "${target.source}". Declare a directory source in neon.ts by hand.`);
328
+ const source = asRelativeModulePath(target.source ?? `functions/${target.slug}.${language}`);
329
+ return {
330
+ edit: {
331
+ kind: "function",
332
+ slug: target.slug,
333
+ name: target.name ?? target.slug,
334
+ source
335
+ },
336
+ sourceFile: resolve(dirname(configPath), source),
337
+ next: explicitConfig ? `Next: \`${cli} config apply${flag}\` to deploy.` : `Next: \`${cli} dev\` to run it locally, \`${cli} config apply\` to deploy.`
338
+ };
259
339
  }
260
340
  }
261
- if (!props.silent) log.info("Next: edit neon.ts, then run `neon config plan` to preview and `neon config apply`.");
262
341
  };
342
+ /**
343
+ * Declare a service, function, or bucket in the project's `neon.ts` with a text edit that
344
+ * leaves the rest of the file alone, creating the file (and installing the config packages)
345
+ * when the project has none. Local only, like `config init`.
346
+ */
347
+ const addCmd = async (props) => {
348
+ const cwd = props.cwd ?? process.cwd();
349
+ let existing;
350
+ if (props.config) {
351
+ existing = resolve(cwd, props.config);
352
+ if (!existsSync(existing)) throw new Error(`--config ${props.config} does not exist.`);
353
+ } else existing = findNeonConfigUpward(cwd);
354
+ const configPath = existing ?? join(cwd, "neon.ts");
355
+ const shown = (path) => relative(cwd, path) || path;
356
+ const plan = planAdd(props.target, configPath, props.config);
357
+ const { source, changes } = editNeonConfig(existing ? readFileSync(existing, "utf8") : renderNeonConfig([]), plan.edit);
358
+ if (changes.length === 0) {
359
+ if (props.target.kind === "service") {
360
+ log.info("%s already enables that; nothing to change.", shown(configPath));
361
+ return;
362
+ }
363
+ throw new Error(`${shown(configPath)} already declares ${props.target.kind} "${props.target.kind === "function" ? props.target.slug : props.target.name}". Edit it there, or pick another name.`);
364
+ }
365
+ if (plan.sourceFile) {
366
+ if (existsSync(plan.sourceFile)) log.info("Found an existing %s — leaving it untouched.", shown(plan.sourceFile));
367
+ else {
368
+ mkdirSync(dirname(plan.sourceFile), { recursive: true });
369
+ const slug = props.target.kind === "function" ? props.target.slug : "";
370
+ writeFileSync(plan.sourceFile, renderFunctionSource(slug, TYPESCRIPT_EXTENSION.test(plan.sourceFile) ? "ts" : "js"));
371
+ log.info("Created %s.", shown(plan.sourceFile));
372
+ }
373
+ }
374
+ writeFileSync(configPath, source);
375
+ log.info("%s %s: %s.", existing ? "Updated" : "Created", shown(configPath), changes.join(", "));
376
+ if (!existing) await ensureConfigPackages(dirname(configPath), props);
377
+ log.info("%s", plan.next);
378
+ };
379
+ /**
380
+ * Options every `config add` sub-command takes. `--project-id` and `--branch` are inherited
381
+ * from `config` but mean nothing to a command that never leaves the working tree, so they are
382
+ * hidden from its help.
383
+ */
384
+ const addSharedOptions = (yargs) => yargs.strict().options({
385
+ "project-id": { hidden: true },
386
+ branch: { hidden: true },
387
+ config: {
388
+ describe: "Path to the neon.ts to edit (defaults to walking up from cwd; created in cwd when there is none)",
389
+ type: "string"
390
+ },
391
+ install: {
392
+ describe: "Install @neon/config and @neon/env when creating neon.ts. On by default; use --no-install to just print the command.",
393
+ type: "boolean",
394
+ default: true
395
+ }
396
+ });
397
+ /** The `config add` props for a sub-command's parsed flags. */
398
+ const addProps = (args, target) => ({
399
+ target,
400
+ install: args.install,
401
+ ...args.config !== void 0 ? { config: args.config } : {}
402
+ });
263
403
  const command = "config";
264
404
  const describe = "Manage a branch with a neon.ts policy";
265
405
  const builder = (argv) => argv.usage("$0 config <sub-command> [options]").options({
@@ -296,7 +436,47 @@ const builder = (argv) => argv.usage("$0 config <sub-command> [options]").option
296
436
  ...envFlag,
297
437
  ...applyFlags,
298
438
  ...envPullFlag
299
- }), (args) => applyCmd(args)).command("init", "Scaffold a neon.ts policy and install the Neon config packages", (yargs) => yargs.options({
439
+ }), (args) => applyCmd(args)).command("add", "Add a service, function, or bucket to neon.ts (creates it if missing)", (yargs) => yargs.command("auth", "Enable Neon Auth (auth: true)", addSharedOptions, (args) => addCmd(addProps(args, {
440
+ kind: "service",
441
+ service: "auth"
442
+ }))).command("data-api", "Enable the Data API (dataApi: true) and Neon Auth, which the default provider requires", addSharedOptions, (args) => addCmd(addProps(args, {
443
+ kind: "service",
444
+ service: "data-api"
445
+ }))).command("ai-gateway", "Enable the AI Gateway (aiGateway: true)", addSharedOptions, (args) => addCmd(addProps(args, {
446
+ kind: "service",
447
+ service: "ai-gateway"
448
+ }))).command("function <slug>", "Declare a Neon Function and create its handler file", (yargs) => addSharedOptions(yargs).example("$0 config add function sendemail", "Creates functions/sendemail.ts and declares it").example("$0 config add function sendemail --source src/send.ts", "Declares an existing handler").positional("slug", {
449
+ describe: "Function slug: 1-20 lowercase letters and digits",
450
+ type: "string",
451
+ demandOption: true
452
+ }).options({
453
+ name: {
454
+ describe: "Display name. Defaults to the slug",
455
+ type: "string"
456
+ },
457
+ source: {
458
+ describe: "Handler file, relative to neon.ts. Defaults to functions/<slug>.ts. Created when missing, left alone when it exists",
459
+ type: "string"
460
+ }
461
+ }), (args) => addCmd(addProps(args, {
462
+ kind: "function",
463
+ slug: args.slug,
464
+ ...args.name !== void 0 ? { name: args.name } : {},
465
+ ...args.source !== void 0 ? { source: args.source } : {}
466
+ }))).command("bucket <name>", "Declare an Object Storage bucket", (yargs) => addSharedOptions(yargs).example("$0 config add bucket assets --access public_read", "Declares a bucket that allows anonymous reads").positional("name", {
467
+ describe: "Bucket name",
468
+ type: "string",
469
+ demandOption: true
470
+ }).options({ access: {
471
+ describe: "Anonymous access: private requires credentials, public_read allows anonymous reads",
472
+ type: "string",
473
+ choices: ["private", "public_read"],
474
+ default: "private"
475
+ } }), (args) => addCmd(addProps(args, {
476
+ kind: "bucket",
477
+ name: args.name,
478
+ access: args.access
479
+ }))).demandCommand(1, "Specify what to add: auth, data-api, ai-gateway, function, or bucket.")).command("init", "Scaffold a neon.ts policy and install the Neon config packages", (yargs) => yargs.options({
300
480
  install: {
301
481
  describe: "Install @neon/config and @neon/env if they're missing. On by default; use --no-install to just print the command.",
302
482
  type: "boolean",
@@ -803,4 +983,4 @@ const createBranchFromPolicyOnCheckout = async (props) => {
803
983
  }
804
984
  };
805
985
  //#endregion
806
- export { ConfigInstallFailed, applyCmd, applyFlags, applyPolicyOnCreate, builder, command, createBranchFromPolicyOnCheckout, describe, envFlag, envPullFlag, handler, hasNeonConfigFile, initCmd, neonConfigFilename, planCmd, status, config_exports as t };
986
+ export { ConfigInstallFailed, addCmd, applyCmd, applyFlags, applyPolicyOnCreate, builder, command, createBranchFromPolicyOnCheckout, describe, envFlag, envPullFlag, handler, hasNeonConfigFile, initCmd, neonConfigFilename, planCmd, status, config_exports as t };
@@ -6,10 +6,11 @@ import { getCliName } from "../utils/cli_name.js";
6
6
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
7
7
  import { writer } from "../writer.js";
8
8
  import { noPassthrough, single } from "../utils/flags.js";
9
+ import { AGENT_MCP_SKIP_MESSAGE } from "../init/copy.js";
9
10
  import { NEON_MCP_CATEGORIES, NEON_MCP_URL, existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
10
11
  import { mcpInstallableAgents, resolveInstallTargets } from "../mcp/targets.js";
11
12
  import { mintMcpApiKey, mintedKeyRevokeCommand, withdrawMintedKey } from "../mcp/mint.js";
12
- import { canPickAgentsInteractively } from "../utils/agent_picker.js";
13
+ import { canPickAgentsInteractively, skippableAgentPicker } from "../utils/agent_picker.js";
13
14
  import { resolveMcpPlan } from "../mcp/plan.js";
14
15
  //#region src/commands/mcp.ts
15
16
  var mcp_exports = /* @__PURE__ */ __exportAll({
@@ -113,7 +114,8 @@ const setupNeonMcp = async (options) => {
113
114
  readOnly: options.readOnly === true,
114
115
  projectId: options.projectId,
115
116
  categories: options.category ?? [],
116
- linkedProjectId
117
+ linkedProjectId,
118
+ pickAgents: skippableAgentPicker(options.allowAgentSkip, AGENT_MCP_SKIP_MESSAGE)
117
119
  });
118
120
  const { install, skipped } = resolveInstallTargets({
119
121
  agents: plan.agents,
@@ -4,11 +4,12 @@ import { recordCommandSuccessExtras } from "../analytics.js";
4
4
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
5
5
  import { writer } from "../writer.js";
6
6
  import { noPassthrough } from "../utils/flags.js";
7
+ import { AGENT_PLUGIN_SKIP_MESSAGE } from "../init/copy.js";
7
8
  import { getAgentDisplayName } from "../mcp/agents.js";
8
9
  import { NEON_MCP_URL } from "../mcp/install.js";
9
10
  import "../init/agents.js";
10
11
  import { pluginsInstallableAgents } from "../plugins/targets.js";
11
- import { canPickAgentsInteractively } from "../utils/agent_picker.js";
12
+ import { canPickAgentsInteractively, skippableAgentPicker } from "../utils/agent_picker.js";
12
13
  import { resolvePluginsPlan } from "../plugins/plan.js";
13
14
  import { NEON_PLUGIN_NAME, PLUGIN_SKILLS, PLUGIN_SOURCE, neonPluginsRetryCommand, pluginsAddArgs, runPluginsCli } from "../plugins/run.js";
14
15
  //#region src/commands/plugins.ts
@@ -71,10 +72,12 @@ const installPlugins = async (options) => {
71
72
  agents: options.agents ?? [],
72
73
  yes,
73
74
  cwd,
74
- interactive
75
+ interactive,
76
+ pickAgents: skippableAgentPicker(options.allowAgentSkip, AGENT_PLUGIN_SKIP_MESSAGE)
75
77
  });
76
78
  for (const agent of plan.skipped) log.warning("Skipping %s: no plugins mapping.", getAgentDisplayName(agent));
77
79
  for (const agent of plan.userScopeSkipped) log.warning("Skipping %s: plugins are user-level. Pass --global.", getAgentDisplayName(agent));
80
+ for (const { agent, command } of plan.missingCommands) log.warning("Skipping %s: the plugin installs through \"%s\", which was not found on PATH.", getAgentDisplayName(agent), command);
78
81
  const rows = [];
79
82
  const failed = [];
80
83
  const scope = scopeLabel(plan.scope);
@@ -147,7 +150,8 @@ const reportPluginsInstall = (props, outcome) => {
147
150
  title: "Plugins"
148
151
  });
149
152
  out.end();
150
- if (outcome.failed.length === 0) log.info(outcome.scope === "project" ? "Installed the Neon plugin (project)." : "Installed the Neon plugin (user).");
153
+ if (outcome.rows.length === 0) log.info("No Neon plugins installed.");
154
+ else if (outcome.failed.length === 0) log.info(outcome.scope === "project" ? "Installed the Neon plugin (project)." : "Installed the Neon plugin (user).");
151
155
  };
152
156
  const handler = async (props) => {
153
157
  const outcome = await installPlugins({
@@ -4,10 +4,11 @@ import { recordCommandSuccessExtras } from "../analytics.js";
4
4
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
5
5
  import { writer } from "../writer.js";
6
6
  import { noPassthrough } from "../utils/flags.js";
7
+ import { AGENT_SKILLS_SKIP_MESSAGE } from "../init/copy.js";
7
8
  import { getAgentDisplayName } from "../mcp/agents.js";
8
9
  import "../init/agents.js";
9
10
  import { mappedSkillsAgentNames, skillsInstallableAgents } from "../skills/targets.js";
10
- import { canPickAgentsInteractively } from "../utils/agent_picker.js";
11
+ import { canPickAgentsInteractively, skippableAgentPicker } from "../utils/agent_picker.js";
11
12
  import { skillsHelpValues, skillsYesHelp } from "../skills/catalog.js";
12
13
  import { confirmSkillsUpdate } from "../skills/wizard.js";
13
14
  import { assertSkillsCanRun, resolveSkillsPlan } from "../skills/plan.js";
@@ -102,7 +103,8 @@ const installSkills = async (options) => {
102
103
  skills: options.skills ?? [],
103
104
  yes,
104
105
  cwd,
105
- interactive
106
+ interactive,
107
+ pickAgents: skippableAgentPicker(options.allowAgentSkip, AGENT_SKILLS_SKIP_MESSAGE)
106
108
  });
107
109
  const mapped = mappedSkillsAgentNames(plan.agents);
108
110
  for (const agent of plan.skipped) log.warning("Skipping %s: no skills mapping.", getAgentDisplayName(agent));