@duyquangnvx/create-agent-cli 0.4.0 → 0.5.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/dist/bin.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { parseArgs, parseEnv } from "node:util";
2
3
  import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
- import { access, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
4
+ import { access, chmod, link, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
5
+ import { constants, existsSync, readFileSync, realpathSync } from "node:fs";
6
+ import { constants as constants$1, homedir, tmpdir } from "node:os";
4
7
  import { spawn } from "node:child_process";
5
- import { constants, homedir, tmpdir } from "node:os";
6
- import { constants as constants$1, existsSync, readFileSync, realpathSync } from "node:fs";
7
- import { parseArgs, parseEnv } from "node:util";
8
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
8
9
  import { fileURLToPath } from "node:url";
9
10
  //#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/util.js
10
11
  function getEnumValues(entries) {
@@ -2817,7 +2818,7 @@ function bucketFor(state, inst) {
2817
2818
  return bucket;
2818
2819
  }
2819
2820
  let handoff;
2820
- const open = [];
2821
+ const open$1 = [];
2821
2822
  const memo = {
2822
2823
  alloc(_inst, payload, empty) {
2823
2824
  const bucket = handoff;
@@ -2828,7 +2829,7 @@ const memo = {
2828
2829
  issues: null
2829
2830
  };
2830
2831
  bucket.set(payload.value, entry);
2831
- open.push(entry);
2832
+ open$1.push(entry);
2832
2833
  return empty;
2833
2834
  },
2834
2835
  guard(inst) {
@@ -2894,10 +2895,10 @@ const memo = {
2894
2895
  return payload;
2895
2896
  }
2896
2897
  handoff = bucket;
2897
- const depth = open.length;
2898
+ const depth = open$1.length;
2898
2899
  const result = base(payload, ctx);
2899
2900
  handoff = void 0;
2900
- const entry = open.length > depth ? open.pop() : void 0;
2901
+ const entry = open$1.length > depth ? open$1.pop() : void 0;
2901
2902
  if (result instanceof Promise) return result.then((r) => {
2902
2903
  if (entry) entry.issues = r.issues.length ? cloneIssues(r.issues) : NO_ISSUES;
2903
2904
  return r;
@@ -5581,6 +5582,54 @@ function untilAborted(signal) {
5581
5582
  });
5582
5583
  }
5583
5584
  //#endregion
5585
+ //#region ../agent-cli/dist/stdin.js
5586
+ const STDIN_META = "stdin";
5587
+ function isStdinSchema(schema) {
5588
+ return globalRegistry.get(schema)?.[STDIN_META] === true;
5589
+ }
5590
+ function endOfInputKeys(platform) {
5591
+ return platform === "win32" ? "Ctrl-Z then Enter" : "Ctrl-D";
5592
+ }
5593
+ /** The progress line shown before reading a terminal, so a person isn't left facing silence. */
5594
+ function readingLine(name, platform) {
5595
+ return `reading <${name}> from stdin; end it with ${endOfInputKeys(platform)}`;
5596
+ }
5597
+ function isTerminal(source) {
5598
+ return typeof source !== "string" && "isTTY" in source && source.isTTY === true;
5599
+ }
5600
+ /** The whole of `source` as UTF-8; rejects with the signal's reason once it aborts. */
5601
+ function readStdin(source, signal) {
5602
+ if (typeof source === "string") return signal.aborted ? Promise.reject(signal.reason) : Promise.resolve(source);
5603
+ return new Promise((resolve, reject) => {
5604
+ const chunks = [];
5605
+ const onData = (chunk) => {
5606
+ chunks.push(chunk instanceof Uint8Array ? chunk : Buffer.from(String(chunk)));
5607
+ };
5608
+ const onEnd = () => {
5609
+ stop();
5610
+ resolve(Buffer.concat(chunks).toString("utf8"));
5611
+ };
5612
+ const onError = (error) => {
5613
+ stop();
5614
+ reject(error);
5615
+ };
5616
+ const onAbort = () => {
5617
+ stop();
5618
+ reject(signal.reason);
5619
+ };
5620
+ const stop = () => {
5621
+ source.off("data", onData).off("end", onEnd).off("error", onError).pause();
5622
+ signal.removeEventListener("abort", onAbort);
5623
+ };
5624
+ if (signal.aborted) {
5625
+ reject(signal.reason);
5626
+ return;
5627
+ }
5628
+ signal.addEventListener("abort", onAbort, { once: true });
5629
+ source.on("data", onData).once("end", onEnd).once("error", onError);
5630
+ });
5631
+ }
5632
+ //#endregion
5584
5633
  //#region ../agent-cli/dist/input.js
5585
5634
  function isWrapper(schema) {
5586
5635
  return schema instanceof ZodOptional || schema instanceof ZodNullable || schema instanceof ZodDefault || schema instanceof ZodPrefault;
@@ -5597,11 +5646,13 @@ function describe(schema) {
5597
5646
  let description = current.description;
5598
5647
  let required = true;
5599
5648
  let defaultValue;
5649
+ let stdin = isStdinSchema(current);
5600
5650
  while (isWrapper(current)) {
5601
5651
  if (current instanceof ZodDefault || current instanceof ZodPrefault) defaultValue = current.def.defaultValue;
5602
5652
  required = false;
5603
5653
  current = current.unwrap();
5604
5654
  description ??= current.description;
5655
+ stdin ||= isStdinSchema(current);
5605
5656
  }
5606
5657
  const variadic = current instanceof ZodArray;
5607
5658
  if (current instanceof ZodArray) current = current.element;
@@ -5611,10 +5662,12 @@ function describe(schema) {
5611
5662
  defaultValue,
5612
5663
  description,
5613
5664
  choices: current instanceof ZodEnum ? current.options.map(String) : void 0,
5614
- variadic
5665
+ variadic,
5666
+ stdin
5615
5667
  };
5616
5668
  }
5617
- function argLabel(key, { variadic, required }) {
5669
+ function argLabel(key, { variadic, required, stdin }) {
5670
+ if (stdin) return required ? `<${key}|->` : `[${key}|-]`;
5618
5671
  if (!variadic) return `<${key}>`;
5619
5672
  return required ? `<${key}...>` : `[${key}...]`;
5620
5673
  }
@@ -5635,7 +5688,8 @@ function optionField(key, schema) {
5635
5688
  const name = kebab(key);
5636
5689
  const isBoolean = described.type === "boolean";
5637
5690
  const label = isBoolean && described.defaultValue === true ? `--no-${name}` : `--${name}`;
5638
- const usage = isBoolean ? label : `${label} <${described.choices?.join("|") ?? "value"}>`;
5691
+ const value = `<${described.choices?.join("|") ?? "value"}>${described.variadic ? "..." : ""}`;
5692
+ const usage = isBoolean ? label : `${label} ${value}`;
5639
5693
  return {
5640
5694
  key,
5641
5695
  name,
@@ -5651,17 +5705,32 @@ function fieldsOf$1(schema, toField) {
5651
5705
  function argFields(schema) {
5652
5706
  return fieldsOf$1(schema, argField);
5653
5707
  }
5708
+ function keysOf(fields) {
5709
+ return fields.map(({ key }) => `'${key}'`).join(", ");
5710
+ }
5654
5711
  /** Throws when an arg that takes every remaining operand is not the last arg. */
5655
5712
  function checkArrayArgLast(name, args) {
5656
5713
  const misplaced = argFields(args).slice(0, -1).filter((field) => field.variadic);
5657
- if (misplaced.length > 0) {
5658
- const keys = misplaced.map(({ key }) => `'${key}'`).join(", ");
5659
- throw new Error(`Command '${name}' declares array arg ${keys} before its last arg; only the last arg may be an array`);
5714
+ if (misplaced.length > 0) throw new Error(`Command '${name}' declares array arg ${keysOf(misplaced)} before its last arg; only the last arg may be an array`);
5715
+ }
5716
+ /** Throws unless at most one arg, and no option, is marked with `orStdin`. */
5717
+ function checkStdinFields(name, args, options) {
5718
+ const stdinArgs = argFields(args).filter((field) => field.stdin);
5719
+ if (stdinArgs.length > 1) throw new Error(`Command '${name}' marks args ${keysOf(stdinArgs)} with orStdin; only one arg may read stdin`);
5720
+ const stdinOptions = optionFields(options).filter((field) => field.stdin);
5721
+ if (stdinOptions.length > 0) {
5722
+ const noun = stdinOptions.length === 1 ? "option" : "options";
5723
+ throw new Error(`Command '${name}' marks ${noun} ${keysOf(stdinOptions)} with orStdin; only an arg may read stdin`);
5660
5724
  }
5661
5725
  }
5662
5726
  function optionFields(schema) {
5663
5727
  return fieldsOf$1(schema, optionField);
5664
5728
  }
5729
+ /** Throws when an option is an array of booleans, which a switch cannot express. */
5730
+ function checkNoBooleanArrayOption(name, options) {
5731
+ const booleans = optionFields(options).filter((field) => field.variadic && field.type === "boolean");
5732
+ if (booleans.length > 0) throw new Error(`Command '${name}' declares boolean array option ${keysOf(booleans)}; a switch cannot repeat, use a boolean`);
5733
+ }
5665
5734
  //#endregion
5666
5735
  //#region ../agent-cli/dist/errors.js
5667
5736
  var CliError = class extends Error {
@@ -5692,6 +5761,9 @@ function errnoOf(error) {
5692
5761
  function isMissingFile(error) {
5693
5762
  return errnoOf(error) === "ENOENT";
5694
5763
  }
5764
+ function isNothingThere(error) {
5765
+ return isMissingFile(error) || errnoOf(error) === "ENOTDIR";
5766
+ }
5695
5767
  function debugVariable(cliName) {
5696
5768
  return `${cliName.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_DEBUG`;
5697
5769
  }
@@ -5756,6 +5828,89 @@ function formatRuntimeError(error, format) {
5756
5828
  }
5757
5829
  return errors.map((each) => errorLines(each, format)).join("");
5758
5830
  }
5831
+ //#endregion
5832
+ //#region ../agent-cli/dist/tokenize.js
5833
+ const NEGATIVE_NUMBER = /^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/;
5834
+ function isOptionLike(word) {
5835
+ return word !== void 0 && word.length > 1 && word[0] === "-" && !NEGATIVE_NUMBER.test(word);
5836
+ }
5837
+ function parseArgsOptions(known) {
5838
+ return Object.fromEntries(known.map(({ flag, takes }) => [flag.slice(2), { type: takes === "value" ? "string" : "boolean" }]));
5839
+ }
5840
+ function flagItem(token, { flag, takes }, words) {
5841
+ if (takes !== "optional value" || token.inlineValue === true) return {
5842
+ kind: "flag",
5843
+ flag,
5844
+ value: token.value ?? true
5845
+ };
5846
+ const took = token.index + 1;
5847
+ const next = words[took];
5848
+ return next === void 0 || isOptionLike(next) ? {
5849
+ kind: "flag",
5850
+ flag,
5851
+ value: true
5852
+ } : {
5853
+ kind: "flag",
5854
+ flag,
5855
+ value: next,
5856
+ took
5857
+ };
5858
+ }
5859
+ function itemOf(token, words, byFlag) {
5860
+ const word = words[token.index] ?? "";
5861
+ if (token.kind === "option-terminator") return { kind: "end" };
5862
+ if (token.kind === "positional" || NEGATIVE_NUMBER.test(word)) return {
5863
+ kind: "operand",
5864
+ value: word
5865
+ };
5866
+ const flag = byFlag.get(token.rawName);
5867
+ return flag === void 0 || flag.takes === "nothing" && token.inlineValue === true ? {
5868
+ kind: "unknown option",
5869
+ word
5870
+ } : flagItem(token, flag, words);
5871
+ }
5872
+ /** Splits a Command's words into operands, unknown options and the values of known flags. */
5873
+ function tokenize(words, known) {
5874
+ const byFlag = new Map(known.map((flag) => [flag.flag, flag]));
5875
+ const { tokens } = parseArgs({
5876
+ args: [...words],
5877
+ options: parseArgsOptions(known),
5878
+ strict: false,
5879
+ allowPositionals: true,
5880
+ tokens: true
5881
+ });
5882
+ const operands = [];
5883
+ const unknownOptions = [];
5884
+ const occurrences = /* @__PURE__ */ new Map();
5885
+ const done = /* @__PURE__ */ new Set();
5886
+ let literal = false;
5887
+ let afterUnknownOption = false;
5888
+ for (const token of tokens) {
5889
+ if (done.has(token.index)) continue;
5890
+ done.add(token.index);
5891
+ const item = itemOf(token, words, byFlag);
5892
+ if (item.kind === "end") literal = true;
5893
+ else if (item.kind === "operand") {
5894
+ operands.push({
5895
+ value: item.value,
5896
+ afterUnknownOption: !literal && afterUnknownOption
5897
+ });
5898
+ afterUnknownOption = false;
5899
+ } else if (item.kind === "unknown option") {
5900
+ unknownOptions.push(item.word);
5901
+ afterUnknownOption = true;
5902
+ } else {
5903
+ occurrences.set(item.flag, [...occurrences.get(item.flag) ?? [], item.value]);
5904
+ if (item.took !== void 0) done.add(item.took);
5905
+ }
5906
+ }
5907
+ return {
5908
+ operands,
5909
+ unknownOptions,
5910
+ values: new Map([...occurrences].map(([flag, all]) => [flag, all[all.length - 1] ?? true])),
5911
+ occurrences
5912
+ };
5913
+ }
5759
5914
  const SHELL_SAFE = /^[\w@%+=:,./-]+$/;
5760
5915
  function quoted$1(token) {
5761
5916
  return SHELL_SAFE.test(token) ? token : `'${token.replaceAll("'", `'\\''`)}'`;
@@ -5763,10 +5918,6 @@ function quoted$1(token) {
5763
5918
  function pasteable(cliName, tokens) {
5764
5919
  return [cliName, ...tokens].map(quoted$1).join(" ");
5765
5920
  }
5766
- /** The caller's command line as typed, ready to paste. */
5767
- function commandLineOf({ cliName, argv }) {
5768
- return pasteable(cliName, argv);
5769
- }
5770
5921
  function split(argv) {
5771
5922
  const end = argv.indexOf("--");
5772
5923
  return end === -1 ? {
@@ -5777,19 +5928,29 @@ function split(argv) {
5777
5928
  operands: argv.slice(end)
5778
5929
  };
5779
5930
  }
5780
- function without(flag, tokens) {
5781
- return tokens.filter((token, index) => token !== flag && !token.startsWith(`${flag}=`) && tokens[index - 1] !== flag);
5931
+ function tookValue(takes, next) {
5932
+ return takes === "value" || takes === "optional value" && next !== void 0 && !isOptionLike(next);
5933
+ }
5934
+ function without(flag, takes, tokens) {
5935
+ const kept = [];
5936
+ for (let index = 0; index < tokens.length; index += 1) {
5937
+ const token = tokens[index] ?? "";
5938
+ if (token === flag) {
5939
+ if (tookValue(takes, tokens[index + 1])) index += 1;
5940
+ } else if (!token.startsWith(`${flag}=`)) kept.push(token);
5941
+ }
5942
+ return kept;
5782
5943
  }
5783
5944
  /**
5784
- * The caller's command line, ready to paste, with `flag value` (or `flag` alone, for a flag that
5785
- * takes nothing) replacing any value they gave.
5945
+ * The caller's command line as typed, ready to paste, with each flag in `remove` dropped along with
5946
+ * its value and the words in `add` placed before any `--`.
5786
5947
  */
5787
- function commandLineWith({ cliName, argv }, flag, value) {
5948
+ function commandLineOf({ cliName, argv, flags }, { add = [], remove = [] } = {}) {
5788
5949
  const { options, operands } = split(argv);
5789
- const added = value === void 0 ? [flag] : [flag, value];
5950
+ const takes = new Map(flags.map((known) => [known.flag, known.takes]));
5790
5951
  return pasteable(cliName, [
5791
- ...without(flag, options),
5792
- ...added,
5952
+ ...remove.reduce((tokens, flag) => without(flag, takes.get(flag) ?? "nothing", tokens), options),
5953
+ ...add,
5793
5954
  ...operands
5794
5955
  ]);
5795
5956
  }
@@ -5942,7 +6103,10 @@ function renderRows(rows, fields) {
5942
6103
  function printedPage(page, invocation) {
5943
6104
  return page.nextCursor === void 0 ? { items: page.items } : {
5944
6105
  items: page.items,
5945
- next: commandLineWith(invocation, PAGE_FLAGS.cursor.flag, String(page.nextCursor))
6106
+ next: commandLineOf(invocation, {
6107
+ remove: [PAGE_FLAGS.cursor.flag],
6108
+ add: [PAGE_FLAGS.cursor.flag, String(page.nextCursor)]
6109
+ })
5946
6110
  };
5947
6111
  }
5948
6112
  /** The line that ends a rendered page, so the caller always knows whether and how to get more. */
@@ -6058,6 +6222,8 @@ function checkedCommand(command) {
6058
6222
  checkNoStandardFlags(command);
6059
6223
  checkForce(command);
6060
6224
  checkArrayArgLast(command.name, command.args);
6225
+ checkStdinFields(command.name, command.args, command.options);
6226
+ checkNoBooleanArrayOption(command.name, command.options);
6061
6227
  return command;
6062
6228
  }
6063
6229
  function defineCommandFor() {
@@ -6135,22 +6301,259 @@ function lastLines(output, count) {
6135
6301
  return output.trim().split("\n").slice(-count).join("\n");
6136
6302
  }
6137
6303
  //#endregion
6304
+ //#region ../agent-cli/dist/tools.js
6305
+ const TOOL_MISSING = "TOOL_MISSING";
6306
+ const WINDOWS_EXTENSIONS = ".COM;.EXE;.BAT;.CMD";
6307
+ function pathOf(env) {
6308
+ return env[Object.keys(env).find((name) => name.toUpperCase() === "PATH") ?? "PATH"] ?? "";
6309
+ }
6310
+ function extensionsOf(env) {
6311
+ return process.platform === "win32" ? (env.PATHEXT ?? WINDOWS_EXTENSIONS).split(";") : [""];
6312
+ }
6313
+ async function isExecutable(file) {
6314
+ try {
6315
+ await access(file, constants.X_OK);
6316
+ return true;
6317
+ } catch (error) {
6318
+ if (isNothingThere(error) || errnoOf(error) === "EACCES") return false;
6319
+ throw error;
6320
+ }
6321
+ }
6322
+ /** The file names `name` may have on `PATH`: itself when it already ends in one of the extensions. */
6323
+ function fileNamesOf(name, env) {
6324
+ const extensions = extensionsOf(env);
6325
+ const own = extname(name).toUpperCase();
6326
+ return own !== "" && extensions.some((extension) => extension.toUpperCase() === own) ? [name] : extensions.map((extension) => `${name}${extension}`);
6327
+ }
6328
+ /** Where `name` is found on `PATH` from `env`, or undefined. */
6329
+ async function findTool(name, env) {
6330
+ const candidates = pathOf(env).split(delimiter).filter((folder) => folder !== "").flatMap((folder) => fileNamesOf(name, env).map((file) => join(folder, file)));
6331
+ for (const file of candidates) if (await isExecutable(file)) return file;
6332
+ }
6333
+ /** Fails with `TOOL_MISSING`, naming every missing tool at once, unless all are on `PATH`. */
6334
+ async function checkTools(tools, env) {
6335
+ const found = await Promise.all(tools.map((tool) => findTool(tool.name, env)));
6336
+ const missing = tools.filter((_tool, index) => found[index] === void 0);
6337
+ if (missing.length === 0) return;
6338
+ const names = missing.map(({ name }) => name).join(", ");
6339
+ throw new CliError({
6340
+ code: TOOL_MISSING,
6341
+ message: `${missing.length === 1 ? "a tool it runs is" : "tools it runs are"} not on PATH: ${names}`,
6342
+ hint: `install ${missing.length === 1 ? "it" : "them"}, then run the same command again: ${missing.map(({ install }) => install).join("; ")}`
6343
+ });
6344
+ }
6345
+ function toolsIn(node) {
6346
+ return isCommand(node) ? [...node.tools ?? []] : Object.values(node).flatMap(toolsIn);
6347
+ }
6348
+ /** One `doctor` row per distinct tool the CLI and its Commands declare, with its install command as the fix. */
6349
+ function toolRows(tools, commands) {
6350
+ return [...new Map([...tools, ...toolsIn(commands)].map((tool) => [tool.name, tool])).values()].map(({ name, install }) => ({
6351
+ name,
6352
+ run: async (ctx) => {
6353
+ const file = await findTool(name, ctx.env);
6354
+ return file === void 0 ? {
6355
+ state: "problem",
6356
+ message: `${name} is not on PATH`,
6357
+ fix: install
6358
+ } : {
6359
+ state: "ok",
6360
+ message: `found at ${file}`
6361
+ };
6362
+ }
6363
+ }));
6364
+ }
6365
+ //#endregion
6366
+ //#region ../agent-cli/dist/spawn.js
6367
+ const TOOL_FAILED = "TOOL_FAILED";
6368
+ const STDERR_TAIL_LINES$1 = 20;
6369
+ const SIGNAL_EXIT_BASE = 128;
6370
+ const BATCH_FILE = /\.(?:cmd|bat)$/i;
6371
+ const CMD_SHIM = /\.cmd$/i;
6372
+ const CMD_META = /([()\][%!^"`<>&|;, *?])/g;
6373
+ const PATH_SEPARATOR = /[\\/]/;
6374
+ const TRAILING_CR = /\r$/;
6375
+ function escapedForCmd(text) {
6376
+ return text.replace(CMD_META, "^$1");
6377
+ }
6378
+ function batchArgument(arg, shim) {
6379
+ const escaped = escapedForCmd(`"${arg.replace(/(\\*)"/g, "$1$1\\\"").replace(/(\\*)$/, "$1$1")}"`);
6380
+ return shim ? escapedForCmd(escaped) : escaped;
6381
+ }
6382
+ function batchCommandLine(command, file, args) {
6383
+ const shim = CMD_SHIM.test(file);
6384
+ return [escapedForCmd(command), ...args.map((arg) => batchArgument(arg, shim))].join(" ");
6385
+ }
6386
+ async function windowsLaunch(command, args, env) {
6387
+ const file = PATH_SEPARATOR.test(command) ? command : await findTool(command, env);
6388
+ if (file === void 0 || !BATCH_FILE.test(file)) return {
6389
+ command,
6390
+ args,
6391
+ shell: false
6392
+ };
6393
+ return {
6394
+ command: batchCommandLine(command, file, args),
6395
+ args: [],
6396
+ shell: true
6397
+ };
6398
+ }
6399
+ function stop(child) {
6400
+ if (process.platform !== "win32" || child.pid === void 0) {
6401
+ child.kill();
6402
+ return Promise.resolve();
6403
+ }
6404
+ const pid = String(child.pid);
6405
+ return new Promise((resolve) => {
6406
+ const taskkill = spawn("taskkill", [
6407
+ "/pid",
6408
+ pid,
6409
+ "/t",
6410
+ "/f"
6411
+ ], {
6412
+ stdio: "ignore",
6413
+ windowsHide: true
6414
+ });
6415
+ taskkill.once("error", () => {
6416
+ child.kill();
6417
+ resolve();
6418
+ });
6419
+ taskkill.once("exit", () => resolve());
6420
+ });
6421
+ }
6422
+ /** Calls `onLine` with each complete line pushed, and with the unterminated rest at the end. */
6423
+ function lineSplitter(onLine) {
6424
+ let rest = "";
6425
+ const emit = (line) => onLine?.(line.replace(TRAILING_CR, ""));
6426
+ return {
6427
+ push(text) {
6428
+ if (onLine === void 0) return;
6429
+ const lines = (rest + text).split("\n");
6430
+ rest = lines.pop() ?? "";
6431
+ for (const line of lines) emit(line);
6432
+ },
6433
+ end() {
6434
+ if (rest !== "") emit(rest);
6435
+ rest = "";
6436
+ }
6437
+ };
6438
+ }
6439
+ /**
6440
+ * Runs a child process to its end; it is killed on abort, and the promise settles only once it has
6441
+ * exited. A `detached` child leaves the terminal's process group on POSIX, and so its Ctrl-C.
6442
+ */
6443
+ async function spawnChild(command, args, { cwd, env, signal, onLine, detached = false }) {
6444
+ signal.throwIfAborted();
6445
+ const launch = process.platform === "win32" ? await windowsLaunch(command, args, env) : {
6446
+ command,
6447
+ args,
6448
+ shell: false
6449
+ };
6450
+ signal.throwIfAborted();
6451
+ return new Promise((resolve, reject) => {
6452
+ const child = spawn(launch.command, launch.args, {
6453
+ cwd,
6454
+ env,
6455
+ shell: launch.shell,
6456
+ windowsHide: true,
6457
+ stdio: [
6458
+ "ignore",
6459
+ "pipe",
6460
+ "pipe"
6461
+ ],
6462
+ detached: detached && process.platform !== "win32"
6463
+ });
6464
+ let stdout = "";
6465
+ let stderr = "";
6466
+ const stdoutLines = lineSplitter(onLine);
6467
+ const stderrLines = lineSplitter(onLine);
6468
+ child.stdout.setEncoding("utf8").on("data", (chunk) => {
6469
+ stdout += chunk;
6470
+ stdoutLines.push(chunk);
6471
+ });
6472
+ child.stderr.setEncoding("utf8").on("data", (chunk) => {
6473
+ stderr += chunk;
6474
+ stderrLines.push(chunk);
6475
+ });
6476
+ let stopped = Promise.resolve();
6477
+ const kill = () => {
6478
+ stopped = stop(child);
6479
+ };
6480
+ signal.addEventListener("abort", kill, { once: true });
6481
+ child.on("error", (error) => {
6482
+ signal.removeEventListener("abort", kill);
6483
+ reject(error);
6484
+ });
6485
+ child.on("close", (code, killedBy) => {
6486
+ signal.removeEventListener("abort", kill);
6487
+ if (signal.aborted) {
6488
+ stopped.then(() => reject(signal.reason));
6489
+ return;
6490
+ }
6491
+ stdoutLines.end();
6492
+ stderrLines.end();
6493
+ resolve({
6494
+ stdout,
6495
+ stderr,
6496
+ exitCode: code ?? SIGNAL_EXIT_BASE + (killedBy === null ? 0 : constants$1.signals[killedBy])
6497
+ });
6498
+ });
6499
+ });
6500
+ }
6501
+ const realSpawn = (command, args, options) => spawnChild(command, args, options);
6502
+ function failedError(command, { exitCode, stderr }) {
6503
+ const tail = lastLines(stderr, STDERR_TAIL_LINES$1);
6504
+ return new CliError({
6505
+ code: TOOL_FAILED,
6506
+ message: tail === "" ? `${command} exited with ${exitCode} and wrote nothing to stderr` : `${command} exited with ${exitCode}; its stderr ends:\n${tail}`,
6507
+ hint: `the output of ${command} above says why it failed`
6508
+ });
6509
+ }
6510
+ function missingError(command, cause) {
6511
+ return new CliError({
6512
+ code: TOOL_MISSING,
6513
+ message: `${command} could not be started: it is not on PATH`,
6514
+ hint: `install ${command}, then run the same command again`
6515
+ }, { cause });
6516
+ }
6517
+ /** `ctx.spawn`: `spawn` bound to the run, its start failures classified by errno. */
6518
+ function contextSpawn(run, defaults, fileError) {
6519
+ return async (command, args = [], options = {}) => {
6520
+ let result;
6521
+ try {
6522
+ result = await run(command, args, {
6523
+ cwd: options.cwd ?? defaults.cwd,
6524
+ env: options.env ?? defaults.env,
6525
+ signal: defaults.signal,
6526
+ onLine: options.progress === true ? defaults.progress : void 0
6527
+ });
6528
+ } catch (error) {
6529
+ if (isMissingFile(error)) throw missingError(command, error);
6530
+ throw fileError(error, {
6531
+ path: command,
6532
+ access: "read"
6533
+ });
6534
+ }
6535
+ if (options.check === true && result.exitCode !== 0) throw failedError(command, result);
6536
+ return result;
6537
+ };
6538
+ }
6539
+ //#endregion
6138
6540
  //#region ../agent-cli/dist/npm.js
6139
6541
  const REGISTRY_UNREACHABLE = "REGISTRY_UNREACHABLE";
6140
6542
  const REGISTRY_AUTH = "REGISTRY_AUTH";
6141
6543
  const INSTALL_FAILED = "INSTALL_FAILED";
6142
6544
  const NPM_FAILED = "NPM_FAILED";
6143
6545
  const NO_LATEST = "NO_LATEST";
6144
- function npmContext(cli, install, ctx) {
6546
+ function npmContext(cli, install, ctx, node) {
6145
6547
  return {
6146
6548
  cliName: cli.name,
6147
6549
  install,
6148
6550
  env: ctx.env,
6149
- signal: ctx.signal
6551
+ signal: ctx.signal,
6552
+ node
6150
6553
  };
6151
6554
  }
6152
6555
  const NPMJS = "https://registry.npmjs.org/";
6153
- const STDERR_TAIL_LINES$1 = 10;
6556
+ const STDERR_TAIL_LINES = 10;
6154
6557
  const RM_RETRIES = 3;
6155
6558
  const NETWORK_CODES = /* @__PURE__ */ new Set([
6156
6559
  "ENOTFOUND",
@@ -6180,40 +6583,36 @@ function installCommandLine(install, version = "latest") {
6180
6583
  ...registryFlags(install)
6181
6584
  ].join(" ");
6182
6585
  }
6586
+ function npmOf(ctx, windows) {
6587
+ const beside = ctx.node === void 0 ? void 0 : join(dirname(ctx.node), windows ? "npm.cmd" : "npm");
6588
+ if (beside === void 0 || !existsSync(beside)) return {
6589
+ path: "npm",
6590
+ env: ctx.env
6591
+ };
6592
+ if (windows) return {
6593
+ path: beside,
6594
+ env: ctx.env
6595
+ };
6596
+ const PATH = [dirname(beside), ctx.env.PATH].filter(Boolean).join(delimiter);
6597
+ return {
6598
+ path: beside,
6599
+ env: {
6600
+ ...ctx.env,
6601
+ PATH
6602
+ }
6603
+ };
6604
+ }
6183
6605
  function spawnNpm(args, ctx, signal) {
6184
- const windows = process.platform === "win32";
6185
- const command = windows ? ["npm", ...args.map((arg) => `"${arg.replaceAll("\"", "\"\"")}"`)].join(" ") : "npm";
6186
- return new Promise((resolve, reject) => {
6187
- const child = spawn(command, windows ? [] : args, {
6188
- env: ctx.env,
6189
- shell: windows,
6190
- windowsHide: true,
6191
- stdio: [
6192
- "ignore",
6193
- "pipe",
6194
- "pipe"
6195
- ],
6196
- detached: signal === null && !windows,
6197
- signal: signal ?? void 0
6198
- });
6199
- let stdout = "";
6200
- let stderr = "";
6201
- child.stdout.setEncoding("utf8").on("data", (chunk) => {
6202
- stdout += chunk;
6203
- });
6204
- child.stderr.setEncoding("utf8").on("data", (chunk) => {
6205
- stderr += chunk;
6206
- });
6207
- child.on("error", reject);
6208
- child.on("close", (exitCode) => resolve({
6209
- exitCode,
6210
- stdout,
6211
- stderr
6212
- }));
6606
+ const npm = npmOf(ctx, process.platform === "win32");
6607
+ return spawnChild(npm.path, args, {
6608
+ cwd: process.cwd(),
6609
+ env: npm.env,
6610
+ signal: signal ?? new AbortController().signal,
6611
+ detached: signal === null
6213
6612
  });
6214
6613
  }
6215
6614
  function tail$1(stderr) {
6216
- const lines = lastLines(stderr, STDERR_TAIL_LINES$1);
6615
+ const lines = lastLines(stderr, STDERR_TAIL_LINES);
6217
6616
  return lines === "" ? "" : `\n${lines}`;
6218
6617
  }
6219
6618
  function failureOf(call, answer, ctx) {
@@ -6361,7 +6760,10 @@ const CONFIRMATION_REQUIRED = "CONFIRMATION_REQUIRED";
6361
6760
  const CONFIRMATION_MISMATCH = "CONFIRMATION_MISMATCH";
6362
6761
  function checkConfirmation({ invocation, commandLine, given }, expected) {
6363
6762
  if (given === expected) return;
6364
- const hint = `to confirm, run \`${commandLineWith(invocation, CONFIRM_FLAG.flag, expected)}\``;
6763
+ const hint = `to confirm, run \`${commandLineOf(invocation, {
6764
+ remove: [CONFIRM_FLAG.flag],
6765
+ add: [CONFIRM_FLAG.flag, expected]
6766
+ })}\``;
6365
6767
  if (given === void 0) throw new UsageError({
6366
6768
  code: CONFIRMATION_REQUIRED,
6367
6769
  message: `\`${commandLine}\` is destructive and needs ${CONFIRM_FLAG.flag}`,
@@ -6382,22 +6784,42 @@ function checkCost({ invocation, commandLine, yes }, cost) {
6382
6784
  throw new CliError({
6383
6785
  code: NOT_CONFIRMED,
6384
6786
  message: `\`${commandLine}\` needs ${YES_FLAG.flag}: ${cost}`,
6385
- hint: `to approve the cost, run \`${commandLineWith(invocation, YES_FLAG.flag)}\``
6787
+ hint: `to approve the cost, run \`${commandLineOf(invocation, { add: [YES_FLAG.flag] })}\``
6386
6788
  });
6387
6789
  }
6388
6790
  //#endregion
6389
6791
  //#region ../agent-cli/dist/agent.js
6390
- const AGENT_VARIABLES = [
6391
- "CLAUDECODE",
6392
- "CODEX_CI",
6393
- "CODEX_SANDBOX",
6394
- "GEMINI_CLI",
6395
- "CURSOR_AGENT",
6396
- "CI"
6792
+ const AGENT_NAME = /^[a-zA-Z0-9_-]+$/u;
6793
+ const isSet = (name) => (env) => (env[name] ?? "") !== "";
6794
+ const equals = (name, value) => (env) => env[name] === value;
6795
+ const MARKERS = [
6796
+ (env) => AGENT_NAME.test(env.AI_AGENT ?? ""),
6797
+ equals("AGENT", "amp"),
6798
+ isSet("CODEX_SANDBOX"),
6799
+ isSet("CODEX_CI"),
6800
+ isSet("CODEX_THREAD_ID"),
6801
+ isSet("GEMINI_CLI"),
6802
+ isSet("COPILOT_CLI"),
6803
+ isSet("OPENCODE"),
6804
+ isSet("ANTIGRAVITY_AGENT"),
6805
+ isSet("AUGMENT_AGENT"),
6806
+ isSet("REPL_ID"),
6807
+ isSet("CLAUDE_CODE_IS_COWORK"),
6808
+ isSet("CLAUDECODE"),
6809
+ isSet("CLAUDE_CODE"),
6810
+ isSet("CURSOR_TRACE_ID"),
6811
+ isSet("CURSOR_AGENT"),
6812
+ equals("CURSOR_EXTENSION_HOST_ROLE", "agent-exec"),
6813
+ equals("TERM_PROGRAM", "kiro"),
6814
+ (env) => ["/.pi/agent", "\\.pi\\agent"].some((dir) => (env.PATH ?? "").includes(dir)),
6815
+ isSet("GOOSE_PROVIDER")
6397
6816
  ];
6398
- /** Whether a Coding agent, not a person, is running the Agent CLI. */
6817
+ /**
6818
+ * Whether a Coding agent, not a person, is running the Agent CLI. Some Coding agents run commands
6819
+ * in a PTY, so a TTY alone doesn't mean a person is watching.
6820
+ */
6399
6821
  function isAgent(env) {
6400
- return AGENT_VARIABLES.some((name) => env[name] !== void 0);
6822
+ return MARKERS.some((marker) => marker(env));
6401
6823
  }
6402
6824
  //#endregion
6403
6825
  //#region ../agent-cli/dist/errno.js
@@ -6405,26 +6827,38 @@ const PERMISSION_DENIED = "PERMISSION_DENIED";
6405
6827
  const DISK_FULL = "DISK_FULL";
6406
6828
  const NOT_FOUND = "NOT_FOUND";
6407
6829
  const ALREADY_EXISTS = "ALREADY_EXISTS";
6408
- function permissionHint({ path, access, callerLine, env }) {
6830
+ const NOT_A_FILE = "NOT_A_FILE";
6831
+ function permissionHint({ path, access, callerLine, env, platform }) {
6832
+ if (platform === "win32") return `a program may hold ${path} open: close it, then run the same command again`;
6409
6833
  if (!isAgent(env)) return `check the permissions of ${path} and its folder`;
6410
6834
  return `your sandbox may block it: ask your user to run \`! ${callerLine}\`, or ${access === "read" ? `to allow reading ${path} in the sandbox` : `to allow ${path} in the sandbox's write list`}`;
6411
6835
  }
6412
- /** The error a file system failure on `path` stands for; any other error comes back unchanged. */
6836
+ function fileErrorOf(scope) {
6837
+ return (error, at) => classifyErrno(error, {
6838
+ ...at,
6839
+ ...scope
6840
+ });
6841
+ }
6413
6842
  function classifyErrno(error, context) {
6414
6843
  const { path } = context;
6415
6844
  switch (errnoOf(error)) {
6416
6845
  case "EACCES":
6417
- case "EPERM": return new CliError({
6846
+ case "EPERM":
6847
+ case "EROFS":
6848
+ case "EBUSY": return new CliError({
6418
6849
  code: PERMISSION_DENIED,
6419
6850
  message: `permission denied: ${path}`,
6420
6851
  hint: permissionHint(context)
6421
6852
  }, { cause: error });
6422
- case "ENOSPC": return new CliError({
6853
+ case "ENOSPC":
6854
+ case "EDQUOT": return new CliError({
6423
6855
  code: DISK_FULL,
6424
6856
  message: `no space left on the disk holding ${path}`,
6425
6857
  hint: "free space on that disk, then run the same command again"
6426
6858
  }, { cause: error });
6427
- case "ENOENT": return new CliError({
6859
+ case "ENOENT":
6860
+ case "ENOTDIR":
6861
+ case "ENAMETOOLONG": return new CliError({
6428
6862
  code: NOT_FOUND,
6429
6863
  message: `not found: ${path}`,
6430
6864
  hint: `check that ${path} and its folder exist`
@@ -6434,6 +6868,11 @@ function classifyErrno(error, context) {
6434
6868
  message: `already exists: ${path}`,
6435
6869
  hint: `pass another path, or remove ${path}`
6436
6870
  }, { cause: error });
6871
+ case "EISDIR": return new CliError({
6872
+ code: NOT_A_FILE,
6873
+ message: `not a file: ${path} is a folder`,
6874
+ hint: `pass the path of a file, not of the folder ${path}`
6875
+ }, { cause: error });
6437
6876
  default: return error;
6438
6877
  }
6439
6878
  }
@@ -6522,7 +6961,7 @@ var RunControl = class {
6522
6961
  else outer?.addEventListener("abort", this.#onOuterAbort, { once: true });
6523
6962
  if (runDeadline !== void 0) {
6524
6963
  const { ms, hint } = runDeadline;
6525
- after(clock, ms, this.#disposed.signal, () => this.#controller.abort(deadlineError(commandLine, ms, hint)));
6964
+ after(clock, ms, this.#disposed.signal, () => this.#controller.abort(deadlineError(commandLine, ms, hint())));
6526
6965
  }
6527
6966
  }
6528
6967
  deadline(ms) {
@@ -6554,7 +6993,7 @@ var RunControl = class {
6554
6993
  return value;
6555
6994
  } finally {
6556
6995
  await this.#stepsDone();
6557
- this.#dispose();
6996
+ this.close();
6558
6997
  }
6559
6998
  }
6560
6999
  /**
@@ -6583,13 +7022,192 @@ var RunControl = class {
6583
7022
  async #stepsDone() {
6584
7023
  while (this.#steps.size > 0) await Promise.all(this.#steps.keys());
6585
7024
  }
6586
- #dispose() {
7025
+ /** Stops the run's timers and its hold on the caller's signal, for a run that never settles. */
7026
+ close() {
6587
7027
  this.#graceCancel.abort();
6588
7028
  this.#disposed.abort();
6589
7029
  this.#outer?.removeEventListener("abort", this.#onOuterAbort);
6590
7030
  }
6591
7031
  };
6592
7032
  //#endregion
7033
+ //#region ../agent-cli/dist/operator.js
7034
+ const NEEDS_OPERATOR = "NEEDS_OPERATOR";
7035
+ /** Fails with `NEEDS_OPERATOR` when a Coding agent runs an Operator Command. */
7036
+ function checkOperator({ operator, commandLine }, env, callerLine) {
7037
+ if (!operator || !isAgent(env)) return;
7038
+ throw new CliError({
7039
+ code: NEEDS_OPERATOR,
7040
+ message: `\`${commandLine}\` must be run by a person at a terminal, not a Coding agent`,
7041
+ hint: `ask the operator to type: ! ${callerLine}`
7042
+ });
7043
+ }
7044
+ //#endregion
7045
+ //#region ../agent-cli/dist/files.js
7046
+ const TEMP_FILE = /^\..+\.agent-cli-(\d+)\.tmp$/;
7047
+ /** Where the Runtime writes `path` before moving it into place; a dead writer's is swept. */
7048
+ function tempPathOf(path) {
7049
+ return join(dirname(path), `.${basename(path)}.agent-cli-${process.pid}.tmp`);
7050
+ }
7051
+ function isAlive(pid) {
7052
+ try {
7053
+ process.kill(pid, 0);
7054
+ return true;
7055
+ } catch (error) {
7056
+ return errnoOf(error) === "EPERM";
7057
+ }
7058
+ }
7059
+ async function sweep(folder) {
7060
+ const dead = (await readdir(folder)).filter((name) => {
7061
+ const pid = TEMP_FILE.exec(name)?.[1];
7062
+ return pid !== void 0 && !isAlive(Number(pid));
7063
+ });
7064
+ await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
7065
+ }
7066
+ /** The file a symlink at `path` points to, so a replacing write keeps the link. */
7067
+ async function targetOf$1(path) {
7068
+ try {
7069
+ return await realpath(path);
7070
+ } catch (error) {
7071
+ if (isMissingFile(error)) return path;
7072
+ throw error;
7073
+ }
7074
+ }
7075
+ async function writeWithMode(path, data, mode) {
7076
+ if (mode === void 0) {
7077
+ await writeFile(path, data);
7078
+ return;
7079
+ }
7080
+ await writeFile(path, data, { mode });
7081
+ await chmod(path, mode);
7082
+ }
7083
+ /** What `link` answers on a file system without hard links, such as exFAT. */
7084
+ const NO_HARD_LINKS = /* @__PURE__ */ new Set([
7085
+ "EPERM",
7086
+ "ENOTSUP",
7087
+ "EXDEV"
7088
+ ]);
7089
+ /** A link fails atomically when `path` is taken, so no reader ever sees half a file there. */
7090
+ async function linkNew(temp, path, data, mode) {
7091
+ try {
7092
+ await link(temp, path);
7093
+ } catch (error) {
7094
+ if (!NO_HARD_LINKS.has(errnoOf(error))) throw error;
7095
+ await createNew(path, data, mode);
7096
+ }
7097
+ }
7098
+ /** A file this call created and could not finish is removed, so a rerun does not find half of it. */
7099
+ async function createNew(path, data, mode) {
7100
+ const handle = await open(path, "wx", mode);
7101
+ try {
7102
+ await handle.writeFile(data);
7103
+ if (mode !== void 0) await handle.chmod(mode);
7104
+ } catch (error) {
7105
+ await handle.close();
7106
+ await rm(path, { force: true });
7107
+ throw error;
7108
+ }
7109
+ await handle.close();
7110
+ }
7111
+ const RENAME_ATTEMPTS = 10;
7112
+ const RENAME_BACKOFF_MS = 10;
7113
+ const HELD_BY_ANOTHER_PROGRAM = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
7114
+ async function renameRetrying(from, to) {
7115
+ for (let attempt = 1;; attempt++) try {
7116
+ await rename(from, to);
7117
+ return;
7118
+ } catch (error) {
7119
+ if (!(process.platform === "win32" && HELD_BY_ANOTHER_PROGRAM.has(errnoOf(error))) || attempt === RENAME_ATTEMPTS) throw error;
7120
+ await setTimeout$1(attempt * RENAME_BACKOFF_MS);
7121
+ }
7122
+ }
7123
+ /** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
7124
+ function realFiles() {
7125
+ const swept = /* @__PURE__ */ new Set();
7126
+ return {
7127
+ exists: async (path) => {
7128
+ try {
7129
+ await access(path);
7130
+ return true;
7131
+ } catch (error) {
7132
+ if (isNothingThere(error)) return false;
7133
+ throw error;
7134
+ }
7135
+ },
7136
+ write: async (path, data, options = {}) => {
7137
+ await mkdir(dirname(path), { recursive: true });
7138
+ const target = options.exclusive === true ? path : await targetOf$1(path);
7139
+ const folder = dirname(target);
7140
+ if (!swept.has(folder)) {
7141
+ await sweep(folder);
7142
+ swept.add(folder);
7143
+ }
7144
+ const temp = tempPathOf(target);
7145
+ try {
7146
+ await writeWithMode(temp, data, options.mode);
7147
+ if (options.exclusive === true) {
7148
+ await linkNew(temp, target, data, options.mode);
7149
+ await rm(temp);
7150
+ } else await renameRetrying(temp, target);
7151
+ } catch (error) {
7152
+ await rm(temp, { force: true });
7153
+ throw error;
7154
+ }
7155
+ },
7156
+ read: (path) => readFile(path),
7157
+ list: (folder) => readdir(folder),
7158
+ remove: (path) => rm(path, {
7159
+ recursive: true,
7160
+ force: true
7161
+ }),
7162
+ mkdir: async (path, options) => {
7163
+ if (options?.exclusive !== true) {
7164
+ await mkdir(path, { recursive: true });
7165
+ return;
7166
+ }
7167
+ await mkdir(dirname(path), { recursive: true });
7168
+ await mkdir(path);
7169
+ },
7170
+ stat: async (path) => {
7171
+ try {
7172
+ const { size, dev, ino } = await stat(path, { bigint: true });
7173
+ return {
7174
+ size: Number(size),
7175
+ id: `${dev}:${ino}`
7176
+ };
7177
+ } catch (error) {
7178
+ if (isNothingThere(error)) return void 0;
7179
+ throw error;
7180
+ }
7181
+ }
7182
+ };
7183
+ }
7184
+ /**
7185
+ * `files` resolving paths against `cwd`, its failures classified by errno; `list` drops the
7186
+ * Runtime's temp files and sorts.
7187
+ */
7188
+ function classifiedFiles(files, { cwd, fileError }) {
7189
+ async function at(access, path, work) {
7190
+ const target = resolve(cwd, path);
7191
+ try {
7192
+ return await work(target);
7193
+ } catch (error) {
7194
+ throw fileError(error, {
7195
+ path: target,
7196
+ access
7197
+ });
7198
+ }
7199
+ }
7200
+ return {
7201
+ exists: (path) => at("read", path, (target) => files.exists(target)),
7202
+ write: (path, data, options) => at("write", path, (target) => files.write(target, data, options)),
7203
+ read: (path) => at("read", path, (target) => files.read(target)),
7204
+ list: (folder) => at("read", folder, async (target) => (await files.list(target)).filter((name) => !TEMP_FILE.test(name)).sort()),
7205
+ remove: (path) => at("write", path, (target) => files.remove(target)),
7206
+ mkdir: (path, options) => at("write", path, (target) => files.mkdir(target, options)),
7207
+ stat: (path) => at("read", path, (target) => files.stat(target))
7208
+ };
7209
+ }
7210
+ //#endregion
6593
7211
  //#region ../agent-cli/dist/outputs.js
6594
7212
  const OUTPUT_EXISTS = "OUTPUT_EXISTS";
6595
7213
  /** Refuses a run that would overwrite existing outputs unless the caller passed `--force`. */
@@ -6602,9 +7220,62 @@ async function checkOutputs({ invocation, commandLine, force, files }, outputs)
6602
7220
  throw new CliError({
6603
7221
  code: OUTPUT_EXISTS,
6604
7222
  message: `\`${commandLine}\` would overwrite ${existing.join(", ")}`,
6605
- hint: `to replace ${them}, run \`${commandLineWith(invocation, FORCE_FLAG.flag)}\``
7223
+ hint: `to replace ${them}, run \`${commandLineOf(invocation, { add: [FORCE_FLAG.flag] })}\``
6606
7224
  });
6607
7225
  }
7226
+ /** Guards the writes to declared outputs of `files`, whose paths resolve against `cwd`. */
7227
+ function outputsGuard(files, cwd) {
7228
+ const unwritten = /* @__PURE__ */ new Set();
7229
+ let forceLine = "";
7230
+ const exclusiveWrite = async (path, data, options) => {
7231
+ const target = resolve(cwd, path);
7232
+ unwritten.delete(target);
7233
+ try {
7234
+ await files.write(path, data, {
7235
+ ...options,
7236
+ exclusive: true
7237
+ });
7238
+ } catch (error) {
7239
+ unwritten.add(target);
7240
+ if (!(error instanceof CliError) || error.code !== "ALREADY_EXISTS") throw error;
7241
+ throw new CliError({
7242
+ code: ALREADY_EXISTS,
7243
+ message: error.message,
7244
+ hint: `to replace it, run \`${forceLine}\``
7245
+ }, { cause: error });
7246
+ }
7247
+ };
7248
+ return {
7249
+ files: {
7250
+ ...files,
7251
+ write: (path, data, options) => unwritten.has(resolve(cwd, path)) ? exclusiveWrite(path, data, options) : files.write(path, data, options)
7252
+ },
7253
+ guard: (outputs, line) => {
7254
+ forceLine = line;
7255
+ for (const path of outputs) unwritten.add(resolve(cwd, path));
7256
+ }
7257
+ };
7258
+ }
7259
+ /**
7260
+ * Creates each output folder and a temp file in it, so a sandbox denial fails before `apply`
7261
+ * spends anything. The temp file's name is the Runtime's, so a crashed run's leftover is swept.
7262
+ */
7263
+ async function probeOutputs({ files, cwd, fileError }, outputs) {
7264
+ const folders = new Set(outputs.map((path) => dirname(resolve(cwd, path))));
7265
+ await Promise.all([...folders].map(async (folder) => {
7266
+ const probe = tempPathOf(join(folder, "outputs-probe"));
7267
+ try {
7268
+ await files.mkdir(folder);
7269
+ await files.write(probe, "");
7270
+ await files.remove(probe);
7271
+ } catch (error) {
7272
+ throw fileError(error, {
7273
+ path: folder,
7274
+ access: "write"
7275
+ });
7276
+ }
7277
+ }));
7278
+ }
6608
7279
  //#endregion
6609
7280
  //#region ../agent-cli/dist/project.js
6610
7281
  const PROJECT_INVALID = "PROJECT_INVALID";
@@ -6652,146 +7323,6 @@ function projectOf(projectFile, cwd) {
6652
7323
  return projectFrom(projectFile, resolve(cwd));
6653
7324
  }
6654
7325
  //#endregion
6655
- //#region ../agent-cli/dist/tools.js
6656
- const TOOL_MISSING = "TOOL_MISSING";
6657
- const WINDOWS_EXTENSIONS = ".COM;.EXE;.BAT;.CMD";
6658
- const NOT_EXECUTABLE = /* @__PURE__ */ new Set([
6659
- "ENOENT",
6660
- "ENOTDIR",
6661
- "EACCES"
6662
- ]);
6663
- function pathOf(env) {
6664
- return env[Object.keys(env).find((name) => name.toUpperCase() === "PATH") ?? "PATH"] ?? "";
6665
- }
6666
- function extensionsOf(env) {
6667
- return process.platform === "win32" ? (env.PATHEXT ?? WINDOWS_EXTENSIONS).split(";") : [""];
6668
- }
6669
- async function isExecutable(file) {
6670
- try {
6671
- await access(file, constants$1.X_OK);
6672
- return true;
6673
- } catch (error) {
6674
- if (NOT_EXECUTABLE.has(errnoOf(error) ?? "")) return false;
6675
- throw error;
6676
- }
6677
- }
6678
- /** Where `name` is found on `PATH` from `env`, or undefined. */
6679
- async function findTool(name, env) {
6680
- const candidates = pathOf(env).split(delimiter).filter((folder) => folder !== "").flatMap((folder) => extensionsOf(env).map((extension) => join(folder, `${name}${extension}`)));
6681
- for (const file of candidates) if (await isExecutable(file)) return file;
6682
- }
6683
- /** Fails with `TOOL_MISSING`, naming every missing tool at once, unless all are on `PATH`. */
6684
- async function checkTools(tools, env) {
6685
- const found = await Promise.all(tools.map((tool) => findTool(tool.name, env)));
6686
- const missing = tools.filter((_tool, index) => found[index] === void 0);
6687
- if (missing.length === 0) return;
6688
- const names = missing.map(({ name }) => name).join(", ");
6689
- throw new CliError({
6690
- code: TOOL_MISSING,
6691
- message: `${missing.length === 1 ? "a tool it runs is" : "tools it runs are"} not on PATH: ${names}`,
6692
- hint: `install ${missing.length === 1 ? "it" : "them"}, then run the same command again: ${missing.map(({ install }) => install).join("; ")}`
6693
- });
6694
- }
6695
- function toolsIn(node) {
6696
- return isCommand(node) ? [...node.tools ?? []] : Object.values(node).flatMap(toolsIn);
6697
- }
6698
- /** One `doctor` row per distinct tool the Commands declare, with its install command as the fix. */
6699
- function toolRows(commands) {
6700
- return [...new Map(toolsIn(commands).map((tool) => [tool.name, tool])).values()].map(({ name, install }) => ({
6701
- name,
6702
- run: async (ctx) => {
6703
- const file = await findTool(name, ctx.env);
6704
- return file === void 0 ? {
6705
- state: "problem",
6706
- message: `${name} is not on PATH`,
6707
- fix: install
6708
- } : {
6709
- state: "ok",
6710
- message: `found at ${file}`
6711
- };
6712
- }
6713
- }));
6714
- }
6715
- //#endregion
6716
- //#region ../agent-cli/dist/spawn.js
6717
- const TOOL_FAILED = "TOOL_FAILED";
6718
- const STDERR_TAIL_LINES = 20;
6719
- const SIGNAL_EXIT_BASE = 128;
6720
- const realSpawn = (command, args, { cwd, env, signal }) => new Promise((resolve, reject) => {
6721
- signal.throwIfAborted();
6722
- const child = spawn(command, args, {
6723
- cwd,
6724
- env,
6725
- stdio: [
6726
- "ignore",
6727
- "pipe",
6728
- "pipe"
6729
- ]
6730
- });
6731
- const stdout = [];
6732
- const stderr = [];
6733
- child.stdout.on("data", (chunk) => stdout.push(chunk));
6734
- child.stderr.on("data", (chunk) => stderr.push(chunk));
6735
- const kill = () => {
6736
- child.kill();
6737
- };
6738
- signal.addEventListener("abort", kill, { once: true });
6739
- child.on("error", (error) => {
6740
- signal.removeEventListener("abort", kill);
6741
- reject(error);
6742
- });
6743
- child.on("close", (code, killedBy) => {
6744
- signal.removeEventListener("abort", kill);
6745
- if (signal.aborted) {
6746
- reject(signal.reason);
6747
- return;
6748
- }
6749
- resolve({
6750
- stdout: Buffer.concat(stdout).toString(),
6751
- stderr: Buffer.concat(stderr).toString(),
6752
- exitCode: code ?? SIGNAL_EXIT_BASE + (killedBy === null ? 0 : constants.signals[killedBy])
6753
- });
6754
- });
6755
- });
6756
- function failedError(command, { exitCode, stderr }) {
6757
- const tail = lastLines(stderr, STDERR_TAIL_LINES);
6758
- return new CliError({
6759
- code: TOOL_FAILED,
6760
- message: tail === "" ? `${command} exited with ${exitCode} and wrote nothing to stderr` : `${command} exited with ${exitCode}; its stderr ends:\n${tail}`,
6761
- hint: `the output of ${command} above says why it failed`
6762
- });
6763
- }
6764
- function missingError(command, cause) {
6765
- return new CliError({
6766
- code: TOOL_MISSING,
6767
- message: `${command} could not be started: it is not on PATH`,
6768
- hint: `install ${command}, then run the same command again`
6769
- }, { cause });
6770
- }
6771
- /** `ctx.spawn`: `spawn` bound to the run, its start failures classified by errno. */
6772
- function contextSpawn(run, defaults, callerLine) {
6773
- return async (command, args = [], options = {}) => {
6774
- let result;
6775
- try {
6776
- result = await run(command, args, {
6777
- cwd: options.cwd ?? defaults.cwd,
6778
- env: options.env ?? defaults.env,
6779
- signal: defaults.signal
6780
- });
6781
- } catch (error) {
6782
- if (isMissingFile(error)) throw missingError(command, error);
6783
- throw classifyErrno(error, {
6784
- path: command,
6785
- access: "read",
6786
- callerLine,
6787
- env: defaults.env
6788
- });
6789
- }
6790
- if (options.check === true && result.exitCode !== 0) throw failedError(command, result);
6791
- return result;
6792
- };
6793
- }
6794
- //#endregion
6795
7326
  //#region ../agent-cli/dist/exit-classes.js
6796
7327
  const SIGNAL_EXIT_CODES = {
6797
7328
  SIGINT: 130,
@@ -6814,6 +7345,7 @@ function runtimeCodes() {
6814
7345
  [CONFIRMATION_REQUIRED]: "caller",
6815
7346
  [CONFIRMATION_MISMATCH]: "caller",
6816
7347
  [NOT_CONFIRMED]: "operator",
7348
+ [NEEDS_OPERATOR]: "operator",
6817
7349
  [OUTPUT_EXISTS]: "caller",
6818
7350
  [INTERRUPTED]: "interrupted",
6819
7351
  [DEADLINE_EXCEEDED]: "wait",
@@ -6822,6 +7354,7 @@ function runtimeCodes() {
6822
7354
  [DISK_FULL]: "operator",
6823
7355
  [NOT_FOUND]: "caller",
6824
7356
  [ALREADY_EXISTS]: "caller",
7357
+ [NOT_A_FILE]: "caller",
6825
7358
  [TOOL_FAILED]: "operator",
6826
7359
  [TOOL_MISSING]: "operator",
6827
7360
  [PROJECT_INVALID]: "operator",
@@ -6885,6 +7418,53 @@ function exitCodeOf(error, errors, stop) {
6885
7418
  return exitClass === "interrupted" ? SIGNAL_EXIT_CODES[stopSignalOf(stop?.reason)] : EXIT_CODES[exitClass];
6886
7419
  }
6887
7420
  //#endregion
7421
+ //#region ../agent-cli/dist/command-spec.js
7422
+ /** The group's Commands and groups that help and suggestions may name: hidden Commands left out. */
7423
+ function visibleCommands({ commands }) {
7424
+ return Object.fromEntries(Object.entries(commands).filter(([, node]) => node.kind === "group" || !node.hidden));
7425
+ }
7426
+ function declaredOf(command) {
7427
+ if (!hasPlan(command)) return {
7428
+ outputs: false,
7429
+ force: void 0,
7430
+ cost: false
7431
+ };
7432
+ return {
7433
+ outputs: command.outputs !== void 0,
7434
+ force: command.force,
7435
+ cost: command.cost !== void 0
7436
+ };
7437
+ }
7438
+ function commandSpec(commandLine, command) {
7439
+ const list = command.rows === void 0 ? void 0 : listSpec(command.rows);
7440
+ return {
7441
+ kind: "command",
7442
+ commandLine,
7443
+ command,
7444
+ description: command.description,
7445
+ help: command.help,
7446
+ hidden: command.hidden === true,
7447
+ operator: command.operator === true,
7448
+ effect: command.effect,
7449
+ examples: command.examples,
7450
+ args: argFields(command.args),
7451
+ options: optionFields(command.options),
7452
+ standardFlags: standardFlagsFor(command.effect, declaredOf(command), list),
7453
+ list,
7454
+ output: list?.output ?? command.output
7455
+ };
7456
+ }
7457
+ function groupSpec(commandLine, commands) {
7458
+ return {
7459
+ kind: "group",
7460
+ commandLine,
7461
+ commands: Object.fromEntries(Object.entries(commands).map(([key, node]) => [key, specOf(`${commandLine} ${key}`, node)]))
7462
+ };
7463
+ }
7464
+ function specOf(commandLine, node) {
7465
+ return isCommand(node) ? commandSpec(commandLine, node) : groupSpec(commandLine, node);
7466
+ }
7467
+ //#endregion
6888
7468
  //#region ../agent-cli/dist/help.js
6889
7469
  function table(rows) {
6890
7470
  const width = Math.max(...rows.map(([left]) => left.length));
@@ -6898,10 +7478,11 @@ function section(title, rows, style) {
6898
7478
  ];
6899
7479
  }
6900
7480
  function summaryOf(node) {
6901
- return node.kind === "command" ? node.description : `group: ${Object.keys(node.commands).join(", ")}`;
7481
+ if (node.kind === "group") return `group: ${Object.keys(visibleCommands(node)).join(", ")}`;
7482
+ return node.operator ? `${node.description} (operator only)` : node.description;
6902
7483
  }
6903
- function commandRows({ commands }) {
6904
- return Object.entries(commands).map(([key, node]) => [key, summaryOf(node)]);
7484
+ function commandRows(group) {
7485
+ return Object.entries(visibleCommands(group)).map(([key, node]) => [key, summaryOf(node)]);
6905
7486
  }
6906
7487
  function lines(...parts) {
6907
7488
  return `${parts.join("\n")}\n`;
@@ -6929,11 +7510,11 @@ function exitCodeRows(cli) {
6929
7510
  }
6930
7511
  function firstExample(node) {
6931
7512
  if (node.kind === "command") return node.examples.slice(0, 1);
6932
- return Object.values(node.commands).flatMap(firstExample).slice(0, 1);
7513
+ return Object.values(visibleCommands(node)).flatMap(firstExample).slice(0, 1);
6933
7514
  }
6934
7515
  function rootExamples(root) {
6935
7516
  if (root.kind === "command") return [...root.examples];
6936
- const { doctor, ...others } = root.commands;
7517
+ const { doctor, ...others } = visibleCommands(root);
6937
7518
  return [...Object.values(others), ...doctor === void 0 ? [] : [doctor]].flatMap(firstExample);
6938
7519
  }
6939
7520
  function examplesSection(examples, style) {
@@ -6952,6 +7533,7 @@ const EFFECT_NOTES = {
6952
7533
  destructive: "changes something hard to undo; show the Plan with --dry-run, run with --confirm",
6953
7534
  serve: "changes nothing; prints its data at once, then runs until Ctrl-C, which exits 0"
6954
7535
  };
7536
+ const OPERATOR_NOTE = "only a person at a terminal runs it; a Coding agent asks the operator to type it after `!`";
6955
7537
  function choicesOf(field) {
6956
7538
  return field.choices === void 0 ? [] : [`one of: ${field.choices.join(", ")}`];
6957
7539
  }
@@ -6981,8 +7563,10 @@ function commandLines(spec, style, { examples }) {
6981
7563
  ...examples ? examplesSection(spec.examples, style) : [],
6982
7564
  ...section("Arguments", spec.args.map(argRow), style),
6983
7565
  ...section("Options", spec.options.map(optionRow), style),
7566
+ ...spec.help === void 0 ? [] : ["", spec.help],
6984
7567
  "",
6985
7568
  `${style.bold("Effect:")} ${spec.effect} (${EFFECT_NOTES[spec.effect]})`,
7569
+ ...spec.operator ? [`${style.bold("Operator Command:")} ${OPERATOR_NOTE}`] : [],
6986
7570
  ...section("Standard flags", spec.standardFlags.map(flagRow), style)
6987
7571
  ];
6988
7572
  }
@@ -7004,7 +7588,9 @@ function commandData(spec) {
7004
7588
  return {
7005
7589
  name: spec.commandLine,
7006
7590
  description: spec.description,
7591
+ help: spec.help ?? null,
7007
7592
  effect: spec.effect,
7593
+ operator: spec.operator,
7008
7594
  examples: spec.examples,
7009
7595
  args: inputSchema(spec.command.args ?? object({})),
7010
7596
  options: optionsSchema(spec.options),
@@ -7022,7 +7608,7 @@ function nodeData(node) {
7022
7608
  function groupData(group) {
7023
7609
  return {
7024
7610
  name: group.commandLine,
7025
- commands: Object.values(group.commands).map(nodeData)
7611
+ commands: Object.values(visibleCommands(group)).map(nodeData)
7026
7612
  };
7027
7613
  }
7028
7614
  function printed$1(data) {
@@ -7064,6 +7650,10 @@ function printed(value, render, { json, invocation, style, fields }) {
7064
7650
  items: rows
7065
7651
  })}\n` : `${renderRows(rows, fields)}${pageFooter(page)}`;
7066
7652
  }
7653
+ /** A progress message as stderr carries it: one JSON object per line under `--json` (ADR 0006). */
7654
+ function progressLine(message, json) {
7655
+ return json ? `${JSON.stringify({ progress: message })}\n` : `${message}\n`;
7656
+ }
7067
7657
  function errorText(error, format) {
7068
7658
  const text = formatRuntimeError(error, {
7069
7659
  ...format,
@@ -7150,12 +7740,51 @@ function versionResult(cli, packageRoot) {
7150
7740
  };
7151
7741
  }
7152
7742
  //#endregion
7743
+ //#region ../agent-cli/dist/skill-source.js
7744
+ /** The branch a release moves to the released commit, which holds the skill of that version. */
7745
+ const RELEASE_BRANCH = "release";
7746
+ const GITHUB_SHORTHAND = /^[\w.-]+\/[\w.-]+$/;
7747
+ const GIT_URL_PROTOCOLS = /* @__PURE__ */ new Set([
7748
+ "https:",
7749
+ "http:",
7750
+ "ssh:",
7751
+ "git:"
7752
+ ]);
7753
+ const SCP_STYLE = /^[\w.-]+@[\w.-]+:[\w./-]+$/;
7754
+ const manifestSchema = object({ repository: union([string(), object({ url: string() })]).optional() });
7755
+ function sourceFromRepository(repository) {
7756
+ if (GITHUB_SHORTHAND.test(repository)) return repository;
7757
+ const url = repository.replace(/^git\+/, "");
7758
+ if (!(URL.canParse(url) && GIT_URL_PROTOCOLS.has(new URL(url).protocol)) && !SCP_STYLE.test(url)) return void 0;
7759
+ return `${url.endsWith(".git") ? url : `${url}.git`}#${RELEASE_BRANCH}`;
7760
+ }
7761
+ async function repositoryAt(packageRoot) {
7762
+ let manifest;
7763
+ try {
7764
+ manifest = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
7765
+ } catch {
7766
+ return;
7767
+ }
7768
+ const parsed = manifestSchema.safeParse(manifest);
7769
+ const repository = parsed.success ? parsed.data.repository : void 0;
7770
+ return typeof repository === "object" ? repository.url : repository;
7771
+ }
7772
+ /** What `npx skills add` installs the CLI's skill from: the declared source, else the package's repository. */
7773
+ async function skillSource(declared, packageRoot) {
7774
+ if (declared !== void 0) return declared.source;
7775
+ const repository = await repositoryAt(packageRoot);
7776
+ return repository === void 0 ? void 0 : sourceFromRepository(repository);
7777
+ }
7778
+ //#endregion
7153
7779
  //#region ../agent-cli/dist/skills.js
7154
7780
  /** Where the skills CLI (`npx skills add`) puts a skill, under a project and under home. */
7155
7781
  const SKILL_DIRS = [[".claude", "skills"], [".agents", "skills"]];
7156
7782
  function skillUpdateCommand(cliName) {
7157
7783
  return `npx -y skills update ${cliName}`;
7158
7784
  }
7785
+ function skillAddCommand(source, cliName) {
7786
+ return `npx -y skills add ${source} --skill ${cliName} -g`;
7787
+ }
7159
7788
  const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
7160
7789
  const METADATA_VERSION = /^metadata:[ \t]*\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+version:[ \t]*["']?([^"'\s]+)["']?[ \t]*$/m;
7161
7790
  /** The version a SKILL.md's frontmatter `metadata.version` states. */
@@ -7260,7 +7889,7 @@ function projectRow(projectFile) {
7260
7889
  }
7261
7890
  };
7262
7891
  }
7263
- function installRow(cli, bin) {
7892
+ function installRow(cli, { bin, node }) {
7264
7893
  return {
7265
7894
  name: "install",
7266
7895
  advisory: true,
@@ -7268,7 +7897,7 @@ function installRow(cli, bin) {
7268
7897
  const { install } = cli;
7269
7898
  if (install === void 0) return ok(`${cli.name} declares no install, so it has no \`update\``);
7270
7899
  try {
7271
- return ok(`a global npm install under ${(await globalInstallOf(bin, npmContext(cli, install, ctx))).prefix}`);
7900
+ return ok(`a global npm install under ${(await globalInstallOf(bin, npmContext(cli, install, ctx, node))).prefix}`);
7272
7901
  } catch (error) {
7273
7902
  if (error instanceof CliError && error.code === "NOT_GLOBAL_INSTALL") return {
7274
7903
  state: "problem",
@@ -7280,14 +7909,14 @@ function installRow(cli, bin) {
7280
7909
  }
7281
7910
  };
7282
7911
  }
7283
- function registryRow(cli) {
7912
+ function registryRow(cli, node) {
7284
7913
  return {
7285
7914
  name: "registry",
7286
7915
  advisory: true,
7287
7916
  run: async (ctx) => {
7288
7917
  const { install } = cli;
7289
7918
  if (install === void 0) return ok(`${cli.name} declares no registry to compare this build with`);
7290
- const latest = await latestVersion(npmContext(cli, install, ctx));
7919
+ const latest = await latestVersion(npmContext(cli, install, ctx, node));
7291
7920
  const standing = standingOf(cli.version, latest);
7292
7921
  const head = `${install.registry} has ${latest} as latest: this build`;
7293
7922
  if (standing === "behind") return {
@@ -7318,7 +7947,15 @@ function skillRow(cli) {
7318
7947
  name: "skill",
7319
7948
  run: async (ctx) => {
7320
7949
  const copies = await skillCopies(cli.name, cli.version, ctx.cwd, ctx.env);
7321
- if (copies.length === 0) return ok(`no copy of the ${cli.name} skill is installed`);
7950
+ if (copies.length === 0) {
7951
+ const message = `no copy of the ${cli.name} skill is installed`;
7952
+ const source = await skillSource(cli.skill, ctx.packageRoot);
7953
+ return source === void 0 ? ok(message) : {
7954
+ state: "ok",
7955
+ message,
7956
+ fix: skillAddCommand(source, cli.name)
7957
+ };
7958
+ }
7322
7959
  const fix = skillFix(cli, copies);
7323
7960
  const message = copies.map(describeCopy).join("; ");
7324
7961
  return fix === void 0 ? ok(message) : {
@@ -7330,15 +7967,15 @@ function skillRow(cli) {
7330
7967
  };
7331
7968
  }
7332
7969
  /** Every row `doctor` reports, in order: the fixed rows around the CLI's own and its tools'. */
7333
- function doctorChecks(cli, bin) {
7970
+ function doctorChecks(cli, launch) {
7334
7971
  return [
7335
7972
  buildRow(cli),
7336
7973
  nodeRow(cli),
7337
7974
  ...cli.project === void 0 ? [] : [projectRow(cli.project)],
7338
7975
  ...cli.doctor ?? [],
7339
- ...toolRows(cli.commands),
7340
- installRow(cli, bin),
7341
- registryRow(cli),
7976
+ ...toolRows(cli.tools ?? [], cli.commands),
7977
+ installRow(cli, launch),
7978
+ registryRow(cli, launch.node),
7342
7979
  skillRow(cli)
7343
7980
  ];
7344
7981
  }
@@ -7351,9 +7988,14 @@ const MIN_LABEL_WIDTH = 8;
7351
7988
  const reportSchema = object({
7352
7989
  rows: array(object({
7353
7990
  check: string(),
7354
- state: _enum(["ok", "problem"]),
7991
+ state: _enum([
7992
+ "ok",
7993
+ "problem",
7994
+ "skipped"
7995
+ ]),
7355
7996
  message: string(),
7356
- fix: string().optional()
7997
+ fix: string().optional(),
7998
+ code: string().optional()
7357
7999
  })),
7358
8000
  next: array(string())
7359
8001
  });
@@ -7364,7 +8006,8 @@ function problemOf(check, error, { signal, deadline, ms }) {
7364
8006
  };
7365
8007
  if (error instanceof CliError) return {
7366
8008
  state: "problem",
7367
- message: `${error.message}; ${error.hint}`
8009
+ message: `${error.message}; ${error.hint}`,
8010
+ code: error.code
7368
8011
  };
7369
8012
  return {
7370
8013
  state: "problem",
@@ -7381,6 +8024,7 @@ async function rowOf(check, ctx) {
7381
8024
  try {
7382
8025
  finding = await Promise.race([Promise.resolve().then(() => check.run(withSignal(ctx, signal))), untilAborted(signal)]);
7383
8026
  } catch (error) {
8027
+ if (ctx.signal.aborted) return void 0;
7384
8028
  finding = problemOf(check, error, {
7385
8029
  signal: ctx.signal,
7386
8030
  deadline: signal,
@@ -7406,15 +8050,16 @@ function render({ rows, next }, style) {
7406
8050
  return `${check.padEnd(width)}${label}${" ".repeat(stateWidth - state.length)}${message}`;
7407
8051
  }), ...next.map((fix) => `${"next".padEnd(width)}${fix}`)].join("\n");
7408
8052
  }
7409
- function failure(cliName, problems, report) {
7410
- return new CliError({
7411
- code: DOCTOR_PROBLEMS,
7412
- message: `${cliName} doctor found ${problems.length === 1 ? "a problem" : `${problems.length} problems`}: ${problems.join(", ")}`,
7413
- hint: report.next.length === 0 ? "each problem row says what is wrong" : "run the commands under next, then run doctor again"
7414
- });
8053
+ function failure(cliName, problems) {
8054
+ const errors = problems.map(({ check, message, fix, code }) => new CliError({
8055
+ code: code ?? "DOCTOR_PROBLEMS",
8056
+ message: `${cliName} doctor found a problem in ${check}: ${message}`,
8057
+ hint: fix === void 0 ? "the row says what is wrong" : `run ${fix}, then run doctor again`
8058
+ }));
8059
+ return new AggregateError(errors);
7415
8060
  }
7416
8061
  /** The built-in `doctor`: the fixed rows around the CLI's own, each under its own deadline. */
7417
- function doctorCommand(cli, bin) {
8062
+ function doctorCommand(cli, launch) {
7418
8063
  return defineCommand$1({
7419
8064
  name: "doctor",
7420
8065
  description: "check the setup and say what fixes each problem",
@@ -7428,17 +8073,20 @@ function doctorCommand(cli, bin) {
7428
8073
  effect: "read",
7429
8074
  output: reportSchema,
7430
8075
  run: async (_input, ctx) => {
7431
- const checked = await Promise.all(doctorChecks(cli, bin).map((check) => rowOf(check, ctx)));
7432
- ctx.signal.throwIfAborted();
8076
+ const checked = (await Promise.all(doctorChecks(cli, launch).map((check) => rowOf(check, ctx)))).filter((row) => row !== void 0);
7433
8077
  const rows = checked.map(({ row }) => row);
7434
8078
  const report = {
7435
8079
  rows,
7436
8080
  next: nextOf(rows)
7437
8081
  };
7438
- const problems = checked.filter(({ row, advisory }) => row.state === "problem" && !advisory).map(({ row }) => row.check);
8082
+ if (ctx.signal.aborted) {
8083
+ ctx.deliver(report);
8084
+ ctx.signal.throwIfAborted();
8085
+ }
8086
+ const problems = checked.filter(({ row, advisory }) => row.state === "problem" && !advisory).map(({ row }) => row);
7439
8087
  if (problems.length > 0) {
7440
8088
  ctx.deliver(report);
7441
- throw failure(cli.name, problems, report);
8089
+ throw failure(cli.name, problems);
7442
8090
  }
7443
8091
  return report;
7444
8092
  },
@@ -7517,7 +8165,7 @@ function interruptedAfterInstall(plan) {
7517
8165
  });
7518
8166
  }
7519
8167
  /** The built-in `update`, for a CLI installed globally with npm. */
7520
- function updateCommand(cli, install, bin) {
8168
+ function updateCommand(cli, install, { bin, node }) {
7521
8169
  return defineCommand$1({
7522
8170
  name: "update",
7523
8171
  description: "install the latest version of this CLI from its registry",
@@ -7531,7 +8179,7 @@ function updateCommand(cli, install, bin) {
7531
8179
  effect: "write",
7532
8180
  output: updateSchema,
7533
8181
  plan: async (_input, ctx) => {
7534
- const npm = npmContext(cli, install, ctx);
8182
+ const npm = npmContext(cli, install, ctx, node);
7535
8183
  const copy = await globalInstallOf(bin, npm);
7536
8184
  return {
7537
8185
  package: install.package,
@@ -7552,7 +8200,7 @@ function updateCommand(cli, install, bin) {
7552
8200
  };
7553
8201
  ctx.signal.throwIfAborted();
7554
8202
  ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
7555
- await ctx.runToEnd(`installing ${plan.package}@${latest}`, () => installGlobally(latest, npmContext(cli, install, ctx)));
8203
+ await ctx.runToEnd(`installing ${plan.package}@${latest}`, () => installGlobally(latest, npmContext(cli, install, ctx, node)));
7556
8204
  const update = {
7557
8205
  from,
7558
8206
  latest,
@@ -7576,10 +8224,10 @@ function updateCommand(cli, install, bin) {
7576
8224
  //#endregion
7577
8225
  //#region ../agent-cli/dist/built-ins.js
7578
8226
  /** The Commands the Runtime adds to a multi-Command CLI: `update` only when it declares an install. */
7579
- function builtInCommands(cli, bin) {
8227
+ function builtInCommands(cli, launch) {
7580
8228
  return {
7581
- doctor: doctorCommand(cli, bin),
7582
- ...cli.install === void 0 ? {} : { update: updateCommand(cli, cli.install, bin) }
8229
+ doctor: doctorCommand(cli, launch),
8230
+ ...cli.install === void 0 ? {} : { update: updateCommand(cli, cli.install, launch) }
7583
8231
  };
7584
8232
  }
7585
8233
  //#endregion
@@ -7592,7 +8240,10 @@ function defineCli$1(cli) {
7592
8240
  checkOwnCodes(cli);
7593
8241
  if (cli.commands === void 0) return cli;
7594
8242
  const { commands } = cli;
7595
- const taken = Object.keys(builtInCommands(cli, void 0)).filter((name) => commands[name] !== void 0);
8243
+ const taken = Object.keys(builtInCommands(cli, {
8244
+ bin: void 0,
8245
+ node: void 0
8246
+ })).filter((name) => commands[name] !== void 0);
7596
8247
  if (taken.length > 0) throw new Error(`${cli.name} defines ${taken.join(", ")}, a built-in Command it cannot replace`);
7597
8248
  return cli;
7598
8249
  }
@@ -7620,46 +8271,6 @@ function cliError(_table, code, init, options) {
7620
8271
  }, options);
7621
8272
  }
7622
8273
  //#endregion
7623
- //#region ../agent-cli/dist/command-spec.js
7624
- function declaredOf(command) {
7625
- if (!hasPlan(command)) return {
7626
- outputs: false,
7627
- force: void 0,
7628
- cost: false
7629
- };
7630
- return {
7631
- outputs: command.outputs !== void 0,
7632
- force: command.force,
7633
- cost: command.cost !== void 0
7634
- };
7635
- }
7636
- function commandSpec(commandLine, command) {
7637
- const list = command.rows === void 0 ? void 0 : listSpec(command.rows);
7638
- return {
7639
- kind: "command",
7640
- commandLine,
7641
- command,
7642
- description: command.description,
7643
- effect: command.effect,
7644
- examples: command.examples,
7645
- args: argFields(command.args),
7646
- options: optionFields(command.options),
7647
- standardFlags: standardFlagsFor(command.effect, declaredOf(command), list),
7648
- list,
7649
- output: list?.output ?? command.output
7650
- };
7651
- }
7652
- function groupSpec(commandLine, commands) {
7653
- return {
7654
- kind: "group",
7655
- commandLine,
7656
- commands: Object.fromEntries(Object.entries(commands).map(([key, node]) => [key, specOf(`${commandLine} ${key}`, node)]))
7657
- };
7658
- }
7659
- function specOf(commandLine, node) {
7660
- return isCommand(node) ? commandSpec(commandLine, node) : groupSpec(commandLine, node);
7661
- }
7662
- //#endregion
7663
8274
  //#region ../agent-cli/dist/execute.js
7664
8275
  function outcomeOf(value, render) {
7665
8276
  return {
@@ -7692,7 +8303,7 @@ function dryRunOutcome(command, plan) {
7692
8303
  ].join("\n")
7693
8304
  };
7694
8305
  }
7695
- async function execute(command, input, ctx, { dryRun, confirm, yes, page, invocation, commandLine, started }) {
8306
+ async function execute(command, input, ctx, { dryRun, confirm, yes, page, invocation, commandLine, started, files, guardOutputs }) {
7696
8307
  await checkTools(command.tools ?? [], ctx.env);
7697
8308
  if (command.rows !== void 0) return outcomeOf(await command.run({
7698
8309
  ...input,
@@ -7705,12 +8316,13 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, page, invoca
7705
8316
  }
7706
8317
  const plan = await command.plan(input, ctx);
7707
8318
  if (dryRun) return dryRunOutcome(command, plan);
8319
+ const outputs = command.outputs?.(plan);
7708
8320
  await checkOutputs({
7709
8321
  invocation,
7710
8322
  commandLine,
7711
8323
  force: ctx.force,
7712
8324
  files: ctx.files
7713
- }, command.outputs?.(plan));
8325
+ }, outputs);
7714
8326
  checkCost({
7715
8327
  invocation,
7716
8328
  commandLine,
@@ -7721,100 +8333,15 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, page, invoca
7721
8333
  commandLine,
7722
8334
  given: confirm
7723
8335
  }, command.confirm(plan));
7724
- return outcomeOf(await command.apply(plan, ctx), (data, style) => command.render(data, style));
7725
- }
7726
- //#endregion
7727
- //#region ../agent-cli/dist/files.js
7728
- const TEMP_FILE = /^\..+\.agent-cli-(\d+)\.tmp$/;
7729
- function tempPathOf(path) {
7730
- return join(dirname(path), `.${basename(path)}.agent-cli-${process.pid}.tmp`);
7731
- }
7732
- function isAlive(pid) {
7733
- try {
7734
- process.kill(pid, 0);
7735
- return true;
7736
- } catch (error) {
7737
- return errnoOf(error) === "EPERM";
7738
- }
7739
- }
7740
- async function sweep(folder) {
7741
- const dead = (await readdir(folder)).filter((name) => {
7742
- const pid = TEMP_FILE.exec(name)?.[1];
7743
- return pid !== void 0 && !isAlive(Number(pid));
7744
- });
7745
- await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
7746
- }
7747
- /** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
7748
- function realFiles() {
7749
- const swept = /* @__PURE__ */ new Set();
7750
- return {
7751
- exists: async (path) => {
7752
- try {
7753
- await access(path);
7754
- return true;
7755
- } catch (error) {
7756
- if (isMissingFile(error)) return false;
7757
- throw error;
7758
- }
7759
- },
7760
- write: async (path, data) => {
7761
- const folder = dirname(path);
7762
- await mkdir(folder, { recursive: true });
7763
- if (!swept.has(folder)) {
7764
- await sweep(folder);
7765
- swept.add(folder);
7766
- }
7767
- const temp = tempPathOf(path);
7768
- try {
7769
- await writeFile(temp, data);
7770
- await rename(temp, path);
7771
- } catch (error) {
7772
- await rm(temp, { force: true });
7773
- throw error;
7774
- }
7775
- },
7776
- read: (path) => readFile(path),
7777
- list: (folder) => readdir(folder),
7778
- remove: (path) => rm(path, {
7779
- recursive: true,
7780
- force: true
7781
- }),
7782
- mkdir: async (path, options) => {
7783
- if (options?.exclusive !== true) {
7784
- await mkdir(path, { recursive: true });
7785
- return;
7786
- }
7787
- await mkdir(dirname(path), { recursive: true });
7788
- await mkdir(path);
7789
- }
7790
- };
7791
- }
7792
- /**
7793
- * `files` resolving paths against `cwd`, its failures classified by errno; `list` drops the
7794
- * Runtime's temp files and sorts.
7795
- */
7796
- function classifiedFiles(files, { cwd, env, callerLine }) {
7797
- async function at(access, path, work) {
7798
- const target = resolve(cwd, path);
7799
- try {
7800
- return await work(target);
7801
- } catch (error) {
7802
- throw classifyErrno(error, {
7803
- path: target,
7804
- access,
7805
- callerLine,
7806
- env
7807
- });
7808
- }
8336
+ if (outputs !== void 0) {
8337
+ await probeOutputs({
8338
+ files,
8339
+ cwd: ctx.cwd,
8340
+ fileError: ctx.fileError
8341
+ }, outputs);
8342
+ if (!ctx.force) guardOutputs(outputs, ctx.commandLine({ add: [FORCE_FLAG.flag] }));
7809
8343
  }
7810
- return {
7811
- exists: (path) => at("read", path, (target) => files.exists(target)),
7812
- write: (path, data) => at("write", path, (target) => files.write(target, data)),
7813
- read: (path) => at("read", path, (target) => files.read(target)),
7814
- list: (folder) => at("read", folder, async (target) => (await files.list(target)).filter((name) => !TEMP_FILE.test(name)).sort()),
7815
- remove: (path) => at("write", path, (target) => files.remove(target)),
7816
- mkdir: (path, options) => at("write", path, (target) => files.mkdir(target, options))
7817
- };
8344
+ return outcomeOf(await command.apply(plan, ctx), (data, style) => command.render(data, style));
7818
8345
  }
7819
8346
  //#endregion
7820
8347
  //#region ../agent-cli/dist/package-root.js
@@ -7873,88 +8400,6 @@ function closestFlag(flag, flags) {
7873
8400
  return closest.length === 1 ? closest[0] : void 0;
7874
8401
  }
7875
8402
  //#endregion
7876
- //#region ../agent-cli/dist/tokenize.js
7877
- const NEGATIVE_NUMBER = /^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/;
7878
- function isOptionLike(word) {
7879
- return word !== void 0 && word.length > 1 && word[0] === "-" && !NEGATIVE_NUMBER.test(word);
7880
- }
7881
- function parseArgsOptions(known) {
7882
- return Object.fromEntries(known.map(({ flag, takes }) => [flag.slice(2), { type: takes === "value" ? "string" : "boolean" }]));
7883
- }
7884
- function flagItem(token, { flag, takes }, words) {
7885
- if (takes !== "optional value" || token.inlineValue === true) return {
7886
- kind: "flag",
7887
- flag,
7888
- value: token.value ?? true
7889
- };
7890
- const took = token.index + 1;
7891
- const next = words[took];
7892
- return next === void 0 || isOptionLike(next) ? {
7893
- kind: "flag",
7894
- flag,
7895
- value: true
7896
- } : {
7897
- kind: "flag",
7898
- flag,
7899
- value: next,
7900
- took
7901
- };
7902
- }
7903
- function itemOf(token, words, byFlag) {
7904
- const word = words[token.index] ?? "";
7905
- if (token.kind === "option-terminator") return { kind: "end" };
7906
- if (token.kind === "positional" || NEGATIVE_NUMBER.test(word)) return {
7907
- kind: "operand",
7908
- value: word
7909
- };
7910
- const flag = byFlag.get(token.rawName);
7911
- return flag === void 0 || flag.takes === "nothing" && token.inlineValue === true ? {
7912
- kind: "unknown option",
7913
- word
7914
- } : flagItem(token, flag, words);
7915
- }
7916
- /** Splits a Command's words into operands, unknown options and the values of known flags. */
7917
- function tokenize(words, known) {
7918
- const byFlag = new Map(known.map((flag) => [flag.flag, flag]));
7919
- const { tokens } = parseArgs({
7920
- args: [...words],
7921
- options: parseArgsOptions(known),
7922
- strict: false,
7923
- allowPositionals: true,
7924
- tokens: true
7925
- });
7926
- const operands = [];
7927
- const unknownOptions = [];
7928
- const values = /* @__PURE__ */ new Map();
7929
- const done = /* @__PURE__ */ new Set();
7930
- let literal = false;
7931
- let afterUnknownOption = false;
7932
- for (const token of tokens) {
7933
- if (done.has(token.index)) continue;
7934
- done.add(token.index);
7935
- const item = itemOf(token, words, byFlag);
7936
- if (item.kind === "end") literal = true;
7937
- else if (item.kind === "operand") {
7938
- operands.push({
7939
- value: item.value,
7940
- afterUnknownOption: !literal && afterUnknownOption
7941
- });
7942
- afterUnknownOption = false;
7943
- } else if (item.kind === "unknown option") {
7944
- unknownOptions.push(item.word);
7945
- afterUnknownOption = true;
7946
- } else {
7947
- values.set(item.flag, item.value);
7948
- if (item.took !== void 0) done.add(item.took);
7949
- }
7950
- }
7951
- return {
7952
- operands,
7953
- unknownOptions,
7954
- values
7955
- };
7956
- }
7957
- //#endregion
7958
8403
  //#region ../agent-cli/dist/parse-input.js
7959
8404
  const NO_INPUT = object({});
7960
8405
  function knownFlags(spec) {
@@ -7982,7 +8427,9 @@ function unknownOptionsOf(words, candidates) {
7982
8427
  };
7983
8428
  });
7984
8429
  }
7985
- function optionValue(field, raw) {
8430
+ function optionValue(field, all) {
8431
+ if (field.variadic) return variadicValue(field, (all ?? []).filter((value) => value !== true));
8432
+ const raw = all?.[all.length - 1];
7986
8433
  return field.type === "boolean" && raw === true && field.label === `--no-${field.name}` ? false : raw;
7987
8434
  }
7988
8435
  function converted(field, raw) {
@@ -7998,7 +8445,7 @@ function parse(schema, values) {
7998
8445
  const input = Object.fromEntries([...values].filter(([, raw]) => raw !== void 0).map(([field, raw]) => [field.key, converted(field, raw)]));
7999
8446
  return schema.safeParse(input);
8000
8447
  }
8001
- function issueLines(result, values, howToPass, reported) {
8448
+ function issueLines(result, values, { howToPass, arrayIssueLine, reported = [], fromStdin }) {
8002
8449
  if (result.success) return [];
8003
8450
  return result.error.issues.flatMap((issue) => {
8004
8451
  const field = [...values.keys()].find((candidate) => candidate.key === issue.path[0]);
@@ -8006,9 +8453,15 @@ function issueLines(result, values, howToPass, reported) {
8006
8453
  if (reported.includes(field)) return [];
8007
8454
  const raw = values.get(field);
8008
8455
  if (raw === void 0) return [` ${field.label}: required, ${howToPass(field)}`];
8009
- return Array.isArray(raw) ? [operandsIssueLine(field, raw, issue, [...values.keys()].indexOf(field) + 1)] : [` ${field.label} ${JSON.stringify(raw)}: ${issue.message}`];
8456
+ if (field === fromStdin) return [` ${field.label} from stdin: ${issue.message}`];
8457
+ return Array.isArray(raw) ? [arrayIssueLine(field, raw, issue)] : [` ${field.label} ${JSON.stringify(raw)}: ${issue.message}`];
8010
8458
  });
8011
8459
  }
8460
+ function flagValuesIssueLine(field, raw, issue) {
8461
+ const [, element] = issue.path;
8462
+ if (typeof element === "number") return ` ${field.label} ${JSON.stringify(raw[element])}: ${issue.message}`;
8463
+ return raw.length === 0 ? ` ${field.label}: ${issue.message}, pass ${field.usage}` : ` ${field.label} ${JSON.stringify(raw)}: ${issue.message}`;
8464
+ }
8012
8465
  function operandsIssueLine(field, raw, issue, first) {
8013
8466
  const [, element] = issue.path;
8014
8467
  if (typeof element === "number") return ` ${field.label} ${JSON.stringify(raw[element])} (argument ${first + element}): ${issue.message}`;
@@ -8030,16 +8483,36 @@ function invalidInput(commandLine, invocation, unknown, otherLines) {
8030
8483
  hint: correctable ? `did you mean \`${commandLineRenaming(invocation, renames)}\`?` : `run \`${commandLine} --help\` to see the expected input`
8031
8484
  });
8032
8485
  }
8033
- function parseInput(spec, words, invocation) {
8486
+ async function stdinTextOf(spec, operands, read) {
8487
+ const index = spec.args.findIndex((field) => field.stdin);
8488
+ const field = spec.args[index];
8489
+ if (field === void 0 || operands[index]?.value !== "-") return void 0;
8490
+ return {
8491
+ field,
8492
+ text: await read(field)
8493
+ };
8494
+ }
8495
+ async function parseInput(spec, words, invocation, readStdin) {
8034
8496
  const { command, args: argFields, options: optionFields } = spec;
8035
8497
  const pageFields = spec.list?.pageFields ?? [];
8036
8498
  const known = knownFlags(spec);
8037
- const { operands, unknownOptions, values } = tokenize(words, known);
8499
+ const { operands, unknownOptions, values, occurrences } = tokenize(words, known);
8500
+ const stdin = await stdinTextOf(spec, operands, readStdin);
8038
8501
  if (values.get(CONFIRM_FLAG.flag) === true) throw invalidInputError(spec.commandLine, `option '${CONFIRM_FLAG.usage}' argument missing`);
8039
- const missing = [...optionFields, ...pageFields].filter((field) => field.type !== "boolean" && values.get(field.label) === true);
8040
- const argValues = new Map(argFields.map((field, index) => [field, field.variadic ? variadicValue(field, operands.slice(index).filter((operand) => !operand.afterUnknownOption).map((operand) => operand.value)) : operands[index]?.value]));
8502
+ const missing = [...optionFields, ...pageFields].filter((field) => field.type !== "boolean" && (field.variadic ? occurrences.get(field.label)?.includes(true) : values.get(field.label) === true));
8503
+ const argValue = (field, index) => {
8504
+ if (field === stdin?.field) return stdin.text;
8505
+ if (!field.variadic) return operands[index]?.value;
8506
+ return variadicValue(field, operands.slice(index).filter((operand) => !operand.afterUnknownOption).map((operand) => operand.value));
8507
+ };
8508
+ const argValues = new Map(argFields.map((field, index) => [field, argValue(field, index)]));
8041
8509
  const extraOperands = argFields.some((field) => field.variadic) ? [] : operands.slice(argFields.length);
8042
- const valuesOf = (fields) => new Map(fields.map((field) => [field, missing.includes(field) ? void 0 : optionValue(field, values.get(field.label))]));
8510
+ const valuesOf = (fields) => new Map(fields.map((field) => [field, missing.includes(field) ? void 0 : optionValue(field, occurrences.get(field.label))]));
8511
+ const flagWording = {
8512
+ howToPass: (field) => `pass ${field.usage}`,
8513
+ arrayIssueLine: flagValuesIssueLine,
8514
+ reported: missing
8515
+ };
8043
8516
  const optionValues = valuesOf(optionFields);
8044
8517
  const pageValues = valuesOf(pageFields);
8045
8518
  const args = parse(command.args ?? NO_INPUT, argValues);
@@ -8049,9 +8522,13 @@ function parseInput(spec, words, invocation) {
8049
8522
  const otherLines = [
8050
8523
  ...missing.map((field) => ` ${field.label}: missing value, pass ${field.usage}`),
8051
8524
  ...extraOperands.filter((operand) => !operand.afterUnknownOption).map((operand) => ` unexpected argument ${JSON.stringify(operand.value)}`),
8052
- ...issueLines(args, argValues, (field) => `pass it as argument ${argFields.indexOf(field) + 1}`, []),
8053
- ...issueLines(options, optionValues, (field) => `pass ${field.usage}`, missing),
8054
- ...issueLines(page, pageValues, (field) => `pass ${field.usage}`, missing)
8525
+ ...issueLines(args, argValues, {
8526
+ howToPass: (field) => `pass it as argument ${argFields.indexOf(field) + 1}`,
8527
+ arrayIssueLine: (field, raw, issue) => operandsIssueLine(field, raw, issue, argFields.indexOf(field) + 1),
8528
+ fromStdin: stdin?.field
8529
+ }),
8530
+ ...issueLines(options, optionValues, flagWording),
8531
+ ...issueLines(page, pageValues, flagWording)
8055
8532
  ];
8056
8533
  if (unknown.length === 0 && otherLines.length === 0 && args.success && options.success && page.success) return {
8057
8534
  input: {
@@ -8065,9 +8542,12 @@ function parseInput(spec, words, invocation) {
8065
8542
  if (only !== void 0 && unknown.length === 1 && otherLines.length === 0) throw unknownOptionError(spec.commandLine, invocation, only.flag, only.suggestion === void 0 ? [] : [only.suggestion]);
8066
8543
  throw invalidInput(spec.commandLine, invocation, unknown, otherLines);
8067
8544
  }
8068
- /** The validated input and Standard flag values of the routed Command, or one usage error. */
8069
- function parseCommand({ spec, words, json }, invocation) {
8070
- const { input, page, values } = parseInput(spec, words, invocation);
8545
+ /**
8546
+ * The validated input and Standard flag values of the routed Command, or one usage error; the arg
8547
+ * given `-` takes its text from `readStdin`.
8548
+ */
8549
+ async function parseCommand({ spec, words, json }, invocation, readStdin) {
8550
+ const { input, page, values } = await parseInput(spec, words, invocation, readStdin);
8071
8551
  const confirm = values.get(CONFIRM_FLAG.flag);
8072
8552
  return {
8073
8553
  input,
@@ -8138,7 +8618,7 @@ function walk(node, words, json, invocation) {
8138
8618
  return unknownCommand(node, first, json);
8139
8619
  }
8140
8620
  function unknownCommand(group, name, json) {
8141
- const suggestions = closestNames(name, Object.keys(group.commands));
8621
+ const suggestions = closestNames(name, Object.keys(visibleCommands(group)));
8142
8622
  return {
8143
8623
  kind: "usage error",
8144
8624
  error: unknownCommandError(group.commandLine, name, suggestions),
@@ -8163,30 +8643,28 @@ function dotenvPath(cliName, env) {
8163
8643
  const config = env.XDG_CONFIG_HOME || join(env.HOME || homedir(), ".config");
8164
8644
  return join(config, cliName, ".env");
8165
8645
  }
8166
- function readDotenv(path, env, callerLine) {
8646
+ function readDotenv(path, fileError) {
8167
8647
  let text;
8168
8648
  try {
8169
8649
  text = readFileSync(path, "utf8");
8170
8650
  } catch (error) {
8171
8651
  if (isMissingFile(error)) return {};
8172
- throw classifyErrno(error, {
8652
+ throw fileError(error, {
8173
8653
  path,
8174
- access: "read",
8175
- callerLine,
8176
- env
8654
+ access: "read"
8177
8655
  });
8178
8656
  }
8179
8657
  return parseEnv(text);
8180
8658
  }
8181
8659
  /** Looks `name` up in the env, then in `$XDG_CONFIG_HOME/<cli>/.env`; an empty value counts as unset. */
8182
- function secretOf({ cliName, env, callerLine }, name) {
8660
+ function secretOf({ cliName, env, fileError }, name) {
8183
8661
  const fromEnv = env[name];
8184
8662
  if (fromEnv) return {
8185
8663
  value: fromEnv,
8186
8664
  origin: { kind: "env" }
8187
8665
  };
8188
8666
  const path = dotenvPath(cliName, env);
8189
- const fromFile = readDotenv(path, env, callerLine)[name];
8667
+ const fromFile = readDotenv(path, fileError)[name];
8190
8668
  return fromFile ? {
8191
8669
  value: fromFile,
8192
8670
  origin: {
@@ -8220,11 +8698,9 @@ async function prepare(folder, cutoff, target) {
8220
8698
  await mkdir(folder, { recursive: true });
8221
8699
  await pruneOld(folder, cutoff);
8222
8700
  } catch (error) {
8223
- throw classifyErrno(error, {
8701
+ throw target.fileError(error, {
8224
8702
  path: folder,
8225
- access: "write",
8226
- callerLine: target.callerLine,
8227
- env: target.env
8703
+ access: "write"
8228
8704
  });
8229
8705
  }
8230
8706
  }
@@ -8280,22 +8756,22 @@ function styleFor(env, stdoutIsTTY) {
8280
8756
  }
8281
8757
  //#endregion
8282
8758
  //#region ../agent-cli/dist/run-cli.js
8283
- async function runCommand(routed, { base, writeStdout, signal, format: baseFormat, ports, clock, files, spawn, projectFile }) {
8759
+ function stdinNeverEndedHint(callerLine) {
8760
+ return `stdin never ended: pipe the text into \`${callerLine}\`, or redirect a file into it`;
8761
+ }
8762
+ async function runCommand(routed, { base, writeStdout, signal, format: baseFormat, ports, clock, files, spawn, projectFile, stdin, platform }) {
8284
8763
  const { command, commandLine } = routed.spec;
8764
+ const invocation = {
8765
+ ...baseFormat.invocation,
8766
+ flags: knownFlags(routed.spec)
8767
+ };
8285
8768
  const format = {
8286
8769
  ...baseFormat,
8287
- json: routed.json
8770
+ json: routed.json,
8771
+ invocation
8288
8772
  };
8289
- const { invocation } = format;
8290
- let parsed;
8291
- let project;
8292
- try {
8293
- parsed = parseCommand(routed, invocation);
8294
- project = projectFile === void 0 ? void 0 : projectOf(projectFile, base.cwd);
8295
- } catch (error) {
8296
- return errorResult(error, format);
8297
- }
8298
- const { input, flags } = parsed;
8773
+ const typedLine = (change) => commandLineOf(invocation, change);
8774
+ let resumeHint;
8299
8775
  const control = new RunControl({
8300
8776
  commandLine,
8301
8777
  outer: signal,
@@ -8303,41 +8779,52 @@ async function runCommand(routed, { base, writeStdout, signal, format: baseForma
8303
8779
  progress: base.progress,
8304
8780
  runDeadline: command.deadline === void 0 ? void 0 : {
8305
8781
  ms: command.deadline.ms,
8306
- hint: command.deadline.resume(input)
8782
+ hint: () => resumeHint ?? stdinNeverEndedHint(typedLine())
8307
8783
  }
8308
8784
  });
8785
+ let parsed;
8786
+ let project;
8787
+ try {
8788
+ checkOperator(routed.spec, base.env, typedLine());
8789
+ parsed = await parseCommand(routed, invocation, (field) => {
8790
+ if (isTerminal(stdin)) base.progress(readingLine(field.name, platform));
8791
+ return readStdin(stdin, control.signal);
8792
+ });
8793
+ project = projectFile === void 0 ? void 0 : projectOf(projectFile, base.cwd);
8794
+ } catch (error) {
8795
+ control.close();
8796
+ return errorResult(error, format);
8797
+ }
8798
+ const { input, flags } = parsed;
8799
+ resumeHint = command.deadline?.resume(input, typedLine);
8309
8800
  let delivered;
8310
- const callerLine = commandLineOf(invocation);
8311
- const classified = classifiedFiles(files, {
8801
+ const { fileError } = base;
8802
+ const outputs = outputsGuard(classifiedFiles(files, {
8312
8803
  cwd: base.cwd,
8313
- env: base.env,
8314
- callerLine
8315
- });
8804
+ fileError
8805
+ }), base.cwd);
8316
8806
  const spill = spillTo({
8317
8807
  cliName: format.cliName,
8318
8808
  commandName: command.name,
8319
8809
  env: base.env,
8320
8810
  clock,
8321
- files: classified,
8322
- callerLine
8811
+ files: outputs.files,
8812
+ fileError
8323
8813
  });
8324
8814
  const portsBase = {
8325
- cwd: base.cwd,
8326
- env: base.env,
8327
- packageRoot: base.packageRoot,
8328
- secret: base.secret,
8815
+ ...base,
8329
8816
  signal: control.signal,
8330
8817
  clock,
8331
- files: classified,
8818
+ files: outputs.files,
8332
8819
  spawn: contextSpawn(spawn, {
8333
8820
  cwd: base.cwd,
8334
8821
  env: base.env,
8335
- signal: control.signal
8336
- }, callerLine)
8822
+ signal: control.signal,
8823
+ progress: base.progress
8824
+ }, fileError)
8337
8825
  };
8338
8826
  const builtPorts = once(() => ports(portsBase));
8339
8827
  const ctx = {
8340
- ...base,
8341
8828
  ...portsBase,
8342
8829
  deadline: (ms) => control.deadline(ms),
8343
8830
  runToEnd: (name, work) => control.runToEnd(name, work),
@@ -8346,7 +8833,8 @@ async function runCommand(routed, { base, writeStdout, signal, format: baseForma
8346
8833
  },
8347
8834
  project,
8348
8835
  force: flags.force,
8349
- poll: (check, { intervalMs }) => poll(clock, check, intervalMs, control.signal),
8836
+ commandLine: typedLine,
8837
+ poll: (check, { intervalMs, signal: stop }) => poll(clock, check, intervalMs, stop === void 0 ? control.signal : AbortSignal.any([control.signal, stop])),
8350
8838
  deliver: (data) => {
8351
8839
  delivered = deliveredOutcome(command, data);
8352
8840
  }
@@ -8366,7 +8854,9 @@ async function runCommand(routed, { base, writeStdout, signal, format: baseForma
8366
8854
  started: async (outcome) => {
8367
8855
  writeStdout(await startedOutput(outcome, format, spill));
8368
8856
  served = true;
8369
- }
8857
+ },
8858
+ files,
8859
+ guardOutputs: outputs.guard
8370
8860
  }))
8371
8861
  };
8372
8862
  } catch (error) {
@@ -8401,29 +8891,59 @@ function once(build) {
8401
8891
  throw outcome.error;
8402
8892
  };
8403
8893
  }
8894
+ function selfOf(name, bin) {
8895
+ return bin === void 0 ? [name] : [
8896
+ process.execPath,
8897
+ ...process.execArgv,
8898
+ bin
8899
+ ];
8900
+ }
8404
8901
  function portsOf(cli, replacement) {
8405
8902
  if (replacement !== void 0) return () => replacement;
8406
8903
  return cli.ports ?? (() => ({}));
8407
8904
  }
8408
- async function runWithProgress(cli, argv, options, write) {
8905
+ function stopLine(notice, json) {
8906
+ return json ? progressLine(notice, true) : `\n${notice}\n`;
8907
+ }
8908
+ /** Calls `onStop` if `signal` aborts while `work` runs. */
8909
+ async function whileStopping(signal, onStop, work) {
8910
+ if (signal === void 0 || onStop === void 0) return work();
8911
+ signal.addEventListener("abort", onStop, { once: true });
8912
+ try {
8913
+ return await work();
8914
+ } finally {
8915
+ signal.removeEventListener("abort", onStop);
8916
+ }
8917
+ }
8918
+ async function runWithProgress(cli, argv, options, write, stopNotice) {
8409
8919
  const format = {
8410
8920
  cliName: cli.name,
8411
8921
  env: options.env,
8412
8922
  style: styleFor(options.env, options.stdoutIsTTY === true),
8413
8923
  invocation: {
8414
8924
  cliName: cli.name,
8415
- argv
8925
+ argv,
8926
+ flags: []
8416
8927
  },
8417
8928
  json: false,
8418
8929
  errors: cli.errors ?? {},
8419
8930
  stop: options.signal
8420
8931
  };
8421
- const builtIns = cli.commands === void 0 ? {} : builtInCommands(cli, options.bin);
8932
+ const builtIns = cli.commands === void 0 ? {} : builtInCommands(cli, {
8933
+ bin: options.bin,
8934
+ node: options.node
8935
+ });
8422
8936
  const root = cli.commands === void 0 ? commandSpec(cli.name, cli.command) : groupSpec(cli.name, {
8423
8937
  ...cli.commands,
8424
8938
  ...builtIns
8425
8939
  });
8426
8940
  const routed = route(root, format.invocation);
8941
+ const platform = options.platform ?? process.platform;
8942
+ const fileError = fileErrorOf({
8943
+ callerLine: commandLineOf(format.invocation),
8944
+ env: options.env,
8945
+ platform
8946
+ });
8427
8947
  const misused = (node) => isAgent(options.env) ? {
8428
8948
  ...format,
8429
8949
  usageHelp: textHelp(node, root, cli, format.style)
@@ -8438,17 +8958,21 @@ async function runWithProgress(cli, argv, options, write) {
8438
8958
  ...misused(routed.node),
8439
8959
  json: routed.json
8440
8960
  });
8441
- case "command": return runCommand(routed, {
8961
+ case "command": return whileStopping(options.signal, stopNotice === void 0 ? void 0 : () => write.stderr(stopLine(stopNotice, routed.json)), () => runCommand(routed, {
8442
8962
  base: {
8443
8963
  cwd: options.cwd,
8444
8964
  env: options.env,
8445
8965
  packageRoot: packageRootOf(options.bin, options.cwd),
8446
- progress: (message) => write.stderr(`${message}\n`),
8966
+ progress: (message) => write.stderr(progressLine(message, routed.json)),
8447
8967
  secret: (name) => secretOf({
8448
8968
  cliName: cli.name,
8449
8969
  env: options.env,
8450
- callerLine: commandLineOf(format.invocation)
8451
- }, name)
8970
+ fileError
8971
+ }, name),
8972
+ fileError,
8973
+ version: cli.version,
8974
+ build: cli.build,
8975
+ self: selfOf(cli.name, options.bin)
8452
8976
  },
8453
8977
  writeStdout: (text) => write.stdout(text),
8454
8978
  signal: options.signal,
@@ -8457,8 +8981,10 @@ async function runWithProgress(cli, argv, options, write) {
8457
8981
  clock: options.clock ?? realClock,
8458
8982
  files: options.files ?? realFiles(),
8459
8983
  spawn: options.spawn ?? realSpawn,
8460
- projectFile: Object.values(builtIns).includes(routed.spec.command) ? void 0 : cli.project
8461
- });
8984
+ projectFile: Object.values(builtIns).includes(routed.spec.command) ? void 0 : cli.project,
8985
+ stdin: options.stdin ?? "",
8986
+ platform
8987
+ }));
8462
8988
  }
8463
8989
  }
8464
8990
  //#endregion
@@ -8470,7 +8996,6 @@ async function runProcess(cli) {
8470
8996
  const stop = new AbortController();
8471
8997
  const onSignal = (signal) => {
8472
8998
  if (stop.signal.aborted) process.exit(SIGNAL_EXIT_CODES[signal]);
8473
- process.stderr.write("\nstopping… (Ctrl-C again to quit now)\n");
8474
8999
  stop.abort(signal);
8475
9000
  };
8476
9001
  process.on("SIGINT", onSignal);
@@ -8481,11 +9006,13 @@ async function runProcess(cli) {
8481
9006
  cwd: process.cwd(),
8482
9007
  stdoutIsTTY: process.stdout.isTTY === true,
8483
9008
  signal: stop.signal,
8484
- bin: process.argv[1]
9009
+ bin: process.argv[1],
9010
+ node: process.execPath,
9011
+ stdin: process.stdin
8485
9012
  }, {
8486
9013
  stdout: (text) => process.stdout.write(text),
8487
9014
  stderr: (text) => process.stderr.write(text)
8488
- });
9015
+ }, "stopping… (Ctrl-C again to quit now)");
8489
9016
  await written(process.stdout, result.stdout);
8490
9017
  await written(process.stderr, result.stderr);
8491
9018
  process.exitCode = result.exitCode;
@@ -8498,9 +9025,9 @@ async function runProcess(cli) {
8498
9025
  //#endregion
8499
9026
  //#region src/build.ts
8500
9027
  const build = {
8501
- "sha": "a7760d0",
9028
+ "sha": "c325cae",
8502
9029
  "dirty": false,
8503
- "builtAt": "2026-10-19T10:55:24.637Z"
9030
+ "builtAt": "2026-10-19T14:33:32.022Z"
8504
9031
  };
8505
9032
  //#endregion
8506
9033
  //#region src/steps.ts
@@ -8629,7 +9156,7 @@ async function writeTemplate(root, files, values) {
8629
9156
  }
8630
9157
  //#endregion
8631
9158
  //#region src/version.ts
8632
- const version = "0.4.0";
9159
+ const version = "0.5.0";
8633
9160
  //#endregion
8634
9161
  //#region src/create.ts
8635
9162
  const { defineCommand } = agentCli();