@duyquangnvx/create-agent-cli 0.3.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,27 +5646,39 @@ 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
  }
5657
+ const variadic = current instanceof ZodArray;
5658
+ if (current instanceof ZodArray) current = current.element;
5606
5659
  return {
5607
5660
  type: typeOf(current),
5608
5661
  required,
5609
5662
  defaultValue,
5610
5663
  description,
5611
- choices: current instanceof ZodEnum ? current.options.map(String) : void 0
5664
+ choices: current instanceof ZodEnum ? current.options.map(String) : void 0,
5665
+ variadic,
5666
+ stdin
5612
5667
  };
5613
5668
  }
5669
+ function argLabel(key, { variadic, required, stdin }) {
5670
+ if (stdin) return required ? `<${key}|->` : `[${key}|-]`;
5671
+ if (!variadic) return `<${key}>`;
5672
+ return required ? `<${key}...>` : `[${key}...]`;
5673
+ }
5614
5674
  function argField(key, schema) {
5615
- const label = `<${key}>`;
5675
+ const described = describe(schema);
5676
+ const label = argLabel(key, described);
5616
5677
  return {
5617
5678
  key,
5618
5679
  name: key,
5619
5680
  schema,
5620
- ...describe(schema),
5681
+ ...described,
5621
5682
  label,
5622
5683
  usage: label
5623
5684
  };
@@ -5627,7 +5688,8 @@ function optionField(key, schema) {
5627
5688
  const name = kebab(key);
5628
5689
  const isBoolean = described.type === "boolean";
5629
5690
  const label = isBoolean && described.defaultValue === true ? `--no-${name}` : `--${name}`;
5630
- 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}`;
5631
5693
  return {
5632
5694
  key,
5633
5695
  name,
@@ -5643,9 +5705,32 @@ function fieldsOf$1(schema, toField) {
5643
5705
  function argFields(schema) {
5644
5706
  return fieldsOf$1(schema, argField);
5645
5707
  }
5708
+ function keysOf(fields) {
5709
+ return fields.map(({ key }) => `'${key}'`).join(", ");
5710
+ }
5711
+ /** Throws when an arg that takes every remaining operand is not the last arg. */
5712
+ function checkArrayArgLast(name, args) {
5713
+ const misplaced = argFields(args).slice(0, -1).filter((field) => field.variadic);
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`);
5724
+ }
5725
+ }
5646
5726
  function optionFields(schema) {
5647
5727
  return fieldsOf$1(schema, optionField);
5648
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
+ }
5649
5734
  //#endregion
5650
5735
  //#region ../agent-cli/dist/errors.js
5651
5736
  var CliError = class extends Error {
@@ -5676,6 +5761,9 @@ function errnoOf(error) {
5676
5761
  function isMissingFile(error) {
5677
5762
  return errnoOf(error) === "ENOENT";
5678
5763
  }
5764
+ function isNothingThere(error) {
5765
+ return isMissingFile(error) || errnoOf(error) === "ENOTDIR";
5766
+ }
5679
5767
  function debugVariable(cliName) {
5680
5768
  return `${cliName.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_DEBUG`;
5681
5769
  }
@@ -5740,6 +5828,89 @@ function formatRuntimeError(error, format) {
5740
5828
  }
5741
5829
  return errors.map((each) => errorLines(each, format)).join("");
5742
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
+ }
5743
5914
  const SHELL_SAFE = /^[\w@%+=:,./-]+$/;
5744
5915
  function quoted$1(token) {
5745
5916
  return SHELL_SAFE.test(token) ? token : `'${token.replaceAll("'", `'\\''`)}'`;
@@ -5747,10 +5918,6 @@ function quoted$1(token) {
5747
5918
  function pasteable(cliName, tokens) {
5748
5919
  return [cliName, ...tokens].map(quoted$1).join(" ");
5749
5920
  }
5750
- /** The caller's command line as typed, ready to paste. */
5751
- function commandLineOf({ cliName, argv }) {
5752
- return pasteable(cliName, argv);
5753
- }
5754
5921
  function split(argv) {
5755
5922
  const end = argv.indexOf("--");
5756
5923
  return end === -1 ? {
@@ -5761,19 +5928,29 @@ function split(argv) {
5761
5928
  operands: argv.slice(end)
5762
5929
  };
5763
5930
  }
5764
- function without(flag, tokens) {
5765
- 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;
5766
5943
  }
5767
5944
  /**
5768
- * The caller's command line, ready to paste, with `flag value` (or `flag` alone, for a flag that
5769
- * 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 `--`.
5770
5947
  */
5771
- function commandLineWith({ cliName, argv }, flag, value) {
5948
+ function commandLineOf({ cliName, argv, flags }, { add = [], remove = [] } = {}) {
5772
5949
  const { options, operands } = split(argv);
5773
- const added = value === void 0 ? [flag] : [flag, value];
5950
+ const takes = new Map(flags.map((known) => [known.flag, known.takes]));
5774
5951
  return pasteable(cliName, [
5775
- ...without(flag, options),
5776
- ...added,
5952
+ ...remove.reduce((tokens, flag) => without(flag, takes.get(flag) ?? "nothing", tokens), options),
5953
+ ...add,
5777
5954
  ...operands
5778
5955
  ]);
5779
5956
  }
@@ -5926,7 +6103,10 @@ function renderRows(rows, fields) {
5926
6103
  function printedPage(page, invocation) {
5927
6104
  return page.nextCursor === void 0 ? { items: page.items } : {
5928
6105
  items: page.items,
5929
- 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
+ })
5930
6110
  };
5931
6111
  }
5932
6112
  /** The line that ends a rendered page, so the caller always knows whether and how to get more. */
@@ -5935,6 +6115,12 @@ function pageFooter({ items, next }) {
5935
6115
  return `${items.length} shown; ${more}\n`;
5936
6116
  }
5937
6117
  //#endregion
6118
+ //#region ../agent-cli/dist/planned.js
6119
+ /** Whether `command` changes something through a Plan: a write or destructive Command. */
6120
+ function hasPlan(command) {
6121
+ return command.effect === "write" || command.effect === "destructive";
6122
+ }
6123
+ //#endregion
5938
6124
  //#region ../agent-cli/dist/flags.js
5939
6125
  const HELP_FLAG = "--help";
5940
6126
  const VERSION_FLAG = "--version";
@@ -5997,10 +6183,14 @@ const RESERVED = new Map([
5997
6183
  ...LIST_FLAGS
5998
6184
  ].map((flag) => [nameOf(flag), flag]));
5999
6185
  /** The Standard flags of a Command with this Effect and declarations, and of a List Command. */
6000
- function standardFlagsFor(effect, { outputs, cost }, list) {
6186
+ function standardFlagsFor(effect, { outputs, force, cost }, list) {
6187
+ const forceFlag = force === void 0 ? FORCE_FLAG : {
6188
+ ...FORCE_FLAG,
6189
+ description: force
6190
+ };
6001
6191
  return [
6002
6192
  ...EFFECT_FLAGS[effect],
6003
- ...outputs ? [FORCE_FLAG] : [],
6193
+ ...outputs || force !== void 0 ? [forceFlag] : [],
6004
6194
  ...cost ? [YES_FLAG] : [],
6005
6195
  ...list?.standardFlags ?? []
6006
6196
  ];
@@ -6019,10 +6209,21 @@ function checkNoStandardFlags(command) {
6019
6209
  });
6020
6210
  if (lines.length > 0) throw new Error([`Command '${command.name}' declares Standard flags the Runtime adds; rename them:`, ...lines].join("\n"));
6021
6211
  }
6212
+ /** Throws when `force` is declared on a Command with no Plan, or says nothing. */
6213
+ function checkForce(command) {
6214
+ const force = "force" in command ? command.force : void 0;
6215
+ if (force === void 0) return;
6216
+ if (!hasPlan(command)) throw new Error(`Command '${command.name}' declares force, which only a write or destructive Command may`);
6217
+ if (force.trim() === "") throw new Error(`Command '${command.name}' declares an empty force; say what ${FORCE_FLAG.flag} replaces`);
6218
+ }
6022
6219
  //#endregion
6023
6220
  //#region ../agent-cli/dist/define.js
6024
6221
  function checkedCommand(command) {
6025
6222
  checkNoStandardFlags(command);
6223
+ checkForce(command);
6224
+ checkArrayArgLast(command.name, command.args);
6225
+ checkStdinFields(command.name, command.args, command.options);
6226
+ checkNoBooleanArrayOption(command.name, command.options);
6026
6227
  return command;
6027
6228
  }
6028
6229
  function defineCommandFor() {
@@ -6100,22 +6301,259 @@ function lastLines(output, count) {
6100
6301
  return output.trim().split("\n").slice(-count).join("\n");
6101
6302
  }
6102
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
6103
6540
  //#region ../agent-cli/dist/npm.js
6104
6541
  const REGISTRY_UNREACHABLE = "REGISTRY_UNREACHABLE";
6105
6542
  const REGISTRY_AUTH = "REGISTRY_AUTH";
6106
6543
  const INSTALL_FAILED = "INSTALL_FAILED";
6107
6544
  const NPM_FAILED = "NPM_FAILED";
6108
6545
  const NO_LATEST = "NO_LATEST";
6109
- function npmContext(cli, install, ctx) {
6546
+ function npmContext(cli, install, ctx, node) {
6110
6547
  return {
6111
6548
  cliName: cli.name,
6112
6549
  install,
6113
6550
  env: ctx.env,
6114
- signal: ctx.signal
6551
+ signal: ctx.signal,
6552
+ node
6115
6553
  };
6116
6554
  }
6117
6555
  const NPMJS = "https://registry.npmjs.org/";
6118
- const STDERR_TAIL_LINES$1 = 10;
6556
+ const STDERR_TAIL_LINES = 10;
6119
6557
  const RM_RETRIES = 3;
6120
6558
  const NETWORK_CODES = /* @__PURE__ */ new Set([
6121
6559
  "ENOTFOUND",
@@ -6145,40 +6583,36 @@ function installCommandLine(install, version = "latest") {
6145
6583
  ...registryFlags(install)
6146
6584
  ].join(" ");
6147
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
+ }
6148
6605
  function spawnNpm(args, ctx, signal) {
6149
- const windows = process.platform === "win32";
6150
- const command = windows ? ["npm", ...args.map((arg) => `"${arg.replaceAll("\"", "\"\"")}"`)].join(" ") : "npm";
6151
- return new Promise((resolve, reject) => {
6152
- const child = spawn(command, windows ? [] : args, {
6153
- env: ctx.env,
6154
- shell: windows,
6155
- windowsHide: true,
6156
- stdio: [
6157
- "ignore",
6158
- "pipe",
6159
- "pipe"
6160
- ],
6161
- detached: signal === null && !windows,
6162
- signal: signal ?? void 0
6163
- });
6164
- let stdout = "";
6165
- let stderr = "";
6166
- child.stdout.setEncoding("utf8").on("data", (chunk) => {
6167
- stdout += chunk;
6168
- });
6169
- child.stderr.setEncoding("utf8").on("data", (chunk) => {
6170
- stderr += chunk;
6171
- });
6172
- child.on("error", reject);
6173
- child.on("close", (exitCode) => resolve({
6174
- exitCode,
6175
- stdout,
6176
- stderr
6177
- }));
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
6178
6612
  });
6179
6613
  }
6180
6614
  function tail$1(stderr) {
6181
- const lines = lastLines(stderr, STDERR_TAIL_LINES$1);
6615
+ const lines = lastLines(stderr, STDERR_TAIL_LINES);
6182
6616
  return lines === "" ? "" : `\n${lines}`;
6183
6617
  }
6184
6618
  function failureOf(call, answer, ctx) {
@@ -6326,7 +6760,10 @@ const CONFIRMATION_REQUIRED = "CONFIRMATION_REQUIRED";
6326
6760
  const CONFIRMATION_MISMATCH = "CONFIRMATION_MISMATCH";
6327
6761
  function checkConfirmation({ invocation, commandLine, given }, expected) {
6328
6762
  if (given === expected) return;
6329
- 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
+ })}\``;
6330
6767
  if (given === void 0) throw new UsageError({
6331
6768
  code: CONFIRMATION_REQUIRED,
6332
6769
  message: `\`${commandLine}\` is destructive and needs ${CONFIRM_FLAG.flag}`,
@@ -6347,51 +6784,95 @@ function checkCost({ invocation, commandLine, yes }, cost) {
6347
6784
  throw new CliError({
6348
6785
  code: NOT_CONFIRMED,
6349
6786
  message: `\`${commandLine}\` needs ${YES_FLAG.flag}: ${cost}`,
6350
- hint: `to approve the cost, run \`${commandLineWith(invocation, YES_FLAG.flag)}\``
6787
+ hint: `to approve the cost, run \`${commandLineOf(invocation, { add: [YES_FLAG.flag] })}\``
6351
6788
  });
6352
6789
  }
6353
6790
  //#endregion
6354
6791
  //#region ../agent-cli/dist/agent.js
6355
- const AGENT_VARIABLES = [
6356
- "CLAUDECODE",
6357
- "CODEX_CI",
6358
- "CODEX_SANDBOX",
6359
- "GEMINI_CLI",
6360
- "CURSOR_AGENT",
6361
- "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")
6362
6816
  ];
6363
- /** 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
+ */
6364
6821
  function isAgent(env) {
6365
- return AGENT_VARIABLES.some((name) => env[name] !== void 0);
6822
+ return MARKERS.some((marker) => marker(env));
6366
6823
  }
6367
6824
  //#endregion
6368
6825
  //#region ../agent-cli/dist/errno.js
6369
6826
  const PERMISSION_DENIED = "PERMISSION_DENIED";
6370
6827
  const DISK_FULL = "DISK_FULL";
6371
6828
  const NOT_FOUND = "NOT_FOUND";
6372
- function permissionHint({ path, callerLine, env }) {
6373
- return isAgent(env) ? `your sandbox may block it: ask your user to run \`! ${callerLine}\`, or to allow ${path} in the sandbox's write list` : `check the permissions of ${path} and its folder`;
6829
+ const ALREADY_EXISTS = "ALREADY_EXISTS";
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`;
6833
+ if (!isAgent(env)) return `check the permissions of ${path} and its folder`;
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`}`;
6835
+ }
6836
+ function fileErrorOf(scope) {
6837
+ return (error, at) => classifyErrno(error, {
6838
+ ...at,
6839
+ ...scope
6840
+ });
6374
6841
  }
6375
- /** The error a file system failure on `path` stands for; any other error comes back unchanged. */
6376
6842
  function classifyErrno(error, context) {
6377
6843
  const { path } = context;
6378
6844
  switch (errnoOf(error)) {
6379
6845
  case "EACCES":
6380
- case "EPERM": return new CliError({
6846
+ case "EPERM":
6847
+ case "EROFS":
6848
+ case "EBUSY": return new CliError({
6381
6849
  code: PERMISSION_DENIED,
6382
6850
  message: `permission denied: ${path}`,
6383
6851
  hint: permissionHint(context)
6384
6852
  }, { cause: error });
6385
- case "ENOSPC": return new CliError({
6853
+ case "ENOSPC":
6854
+ case "EDQUOT": return new CliError({
6386
6855
  code: DISK_FULL,
6387
6856
  message: `no space left on the disk holding ${path}`,
6388
6857
  hint: "free space on that disk, then run the same command again"
6389
6858
  }, { cause: error });
6390
- case "ENOENT": return new CliError({
6859
+ case "ENOENT":
6860
+ case "ENOTDIR":
6861
+ case "ENAMETOOLONG": return new CliError({
6391
6862
  code: NOT_FOUND,
6392
6863
  message: `not found: ${path}`,
6393
6864
  hint: `check that ${path} and its folder exist`
6394
6865
  }, { cause: error });
6866
+ case "EEXIST": return new CliError({
6867
+ code: ALREADY_EXISTS,
6868
+ message: `already exists: ${path}`,
6869
+ hint: `pass another path, or remove ${path}`
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 });
6395
6876
  default: return error;
6396
6877
  }
6397
6878
  }
@@ -6480,7 +6961,7 @@ var RunControl = class {
6480
6961
  else outer?.addEventListener("abort", this.#onOuterAbort, { once: true });
6481
6962
  if (runDeadline !== void 0) {
6482
6963
  const { ms, hint } = runDeadline;
6483
- 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())));
6484
6965
  }
6485
6966
  }
6486
6967
  deadline(ms) {
@@ -6512,7 +6993,7 @@ var RunControl = class {
6512
6993
  return value;
6513
6994
  } finally {
6514
6995
  await this.#stepsDone();
6515
- this.#dispose();
6996
+ this.close();
6516
6997
  }
6517
6998
  }
6518
6999
  /**
@@ -6541,13 +7022,192 @@ var RunControl = class {
6541
7022
  async #stepsDone() {
6542
7023
  while (this.#steps.size > 0) await Promise.all(this.#steps.keys());
6543
7024
  }
6544
- #dispose() {
7025
+ /** Stops the run's timers and its hold on the caller's signal, for a run that never settles. */
7026
+ close() {
6545
7027
  this.#graceCancel.abort();
6546
7028
  this.#disposed.abort();
6547
7029
  this.#outer?.removeEventListener("abort", this.#onOuterAbort);
6548
7030
  }
6549
7031
  };
6550
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
6551
7211
  //#region ../agent-cli/dist/outputs.js
6552
7212
  const OUTPUT_EXISTS = "OUTPUT_EXISTS";
6553
7213
  /** Refuses a run that would overwrite existing outputs unless the caller passed `--force`. */
@@ -6560,9 +7220,62 @@ async function checkOutputs({ invocation, commandLine, force, files }, outputs)
6560
7220
  throw new CliError({
6561
7221
  code: OUTPUT_EXISTS,
6562
7222
  message: `\`${commandLine}\` would overwrite ${existing.join(", ")}`,
6563
- hint: `to replace ${them}, run \`${commandLineWith(invocation, FORCE_FLAG.flag)}\``
7223
+ hint: `to replace ${them}, run \`${commandLineOf(invocation, { add: [FORCE_FLAG.flag] })}\``
6564
7224
  });
6565
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
+ }
6566
7279
  //#endregion
6567
7280
  //#region ../agent-cli/dist/project.js
6568
7281
  const PROJECT_INVALID = "PROJECT_INVALID";
@@ -6610,145 +7323,6 @@ function projectOf(projectFile, cwd) {
6610
7323
  return projectFrom(projectFile, resolve(cwd));
6611
7324
  }
6612
7325
  //#endregion
6613
- //#region ../agent-cli/dist/tools.js
6614
- const TOOL_MISSING = "TOOL_MISSING";
6615
- const WINDOWS_EXTENSIONS = ".COM;.EXE;.BAT;.CMD";
6616
- const NOT_EXECUTABLE = /* @__PURE__ */ new Set([
6617
- "ENOENT",
6618
- "ENOTDIR",
6619
- "EACCES"
6620
- ]);
6621
- function pathOf(env) {
6622
- return env[Object.keys(env).find((name) => name.toUpperCase() === "PATH") ?? "PATH"] ?? "";
6623
- }
6624
- function extensionsOf(env) {
6625
- return process.platform === "win32" ? (env.PATHEXT ?? WINDOWS_EXTENSIONS).split(";") : [""];
6626
- }
6627
- async function isExecutable(file) {
6628
- try {
6629
- await access(file, constants$1.X_OK);
6630
- return true;
6631
- } catch (error) {
6632
- if (NOT_EXECUTABLE.has(errnoOf(error) ?? "")) return false;
6633
- throw error;
6634
- }
6635
- }
6636
- /** Where `name` is found on `PATH` from `env`, or undefined. */
6637
- async function findTool(name, env) {
6638
- const candidates = pathOf(env).split(delimiter).filter((folder) => folder !== "").flatMap((folder) => extensionsOf(env).map((extension) => join(folder, `${name}${extension}`)));
6639
- for (const file of candidates) if (await isExecutable(file)) return file;
6640
- }
6641
- /** Fails with `TOOL_MISSING`, naming every missing tool at once, unless all are on `PATH`. */
6642
- async function checkTools(tools, env) {
6643
- const found = await Promise.all(tools.map((tool) => findTool(tool.name, env)));
6644
- const missing = tools.filter((_tool, index) => found[index] === void 0);
6645
- if (missing.length === 0) return;
6646
- const names = missing.map(({ name }) => name).join(", ");
6647
- throw new CliError({
6648
- code: TOOL_MISSING,
6649
- message: `${missing.length === 1 ? "a tool it runs is" : "tools it runs are"} not on PATH: ${names}`,
6650
- hint: `install ${missing.length === 1 ? "it" : "them"}, then run the same command again: ${missing.map(({ install }) => install).join("; ")}`
6651
- });
6652
- }
6653
- function toolsIn(node) {
6654
- return isCommand(node) ? [...node.tools ?? []] : Object.values(node).flatMap(toolsIn);
6655
- }
6656
- /** One `doctor` row per distinct tool the Commands declare, with its install command as the fix. */
6657
- function toolRows(commands) {
6658
- return [...new Map(toolsIn(commands).map((tool) => [tool.name, tool])).values()].map(({ name, install }) => ({
6659
- name,
6660
- run: async (ctx) => {
6661
- const file = await findTool(name, ctx.env);
6662
- return file === void 0 ? {
6663
- state: "problem",
6664
- message: `${name} is not on PATH`,
6665
- fix: install
6666
- } : {
6667
- state: "ok",
6668
- message: `found at ${file}`
6669
- };
6670
- }
6671
- }));
6672
- }
6673
- //#endregion
6674
- //#region ../agent-cli/dist/spawn.js
6675
- const TOOL_FAILED = "TOOL_FAILED";
6676
- const STDERR_TAIL_LINES = 20;
6677
- const SIGNAL_EXIT_BASE = 128;
6678
- const realSpawn = (command, args, { cwd, env, signal }) => new Promise((resolve, reject) => {
6679
- signal.throwIfAborted();
6680
- const child = spawn(command, args, {
6681
- cwd,
6682
- env,
6683
- stdio: [
6684
- "ignore",
6685
- "pipe",
6686
- "pipe"
6687
- ]
6688
- });
6689
- const stdout = [];
6690
- const stderr = [];
6691
- child.stdout.on("data", (chunk) => stdout.push(chunk));
6692
- child.stderr.on("data", (chunk) => stderr.push(chunk));
6693
- const kill = () => {
6694
- child.kill();
6695
- };
6696
- signal.addEventListener("abort", kill, { once: true });
6697
- child.on("error", (error) => {
6698
- signal.removeEventListener("abort", kill);
6699
- reject(error);
6700
- });
6701
- child.on("close", (code, killedBy) => {
6702
- signal.removeEventListener("abort", kill);
6703
- if (signal.aborted) {
6704
- reject(signal.reason);
6705
- return;
6706
- }
6707
- resolve({
6708
- stdout: Buffer.concat(stdout).toString(),
6709
- stderr: Buffer.concat(stderr).toString(),
6710
- exitCode: code ?? SIGNAL_EXIT_BASE + (killedBy === null ? 0 : constants.signals[killedBy])
6711
- });
6712
- });
6713
- });
6714
- function failedError(command, { exitCode, stderr }) {
6715
- const tail = lastLines(stderr, STDERR_TAIL_LINES);
6716
- return new CliError({
6717
- code: TOOL_FAILED,
6718
- message: tail === "" ? `${command} exited with ${exitCode} and wrote nothing to stderr` : `${command} exited with ${exitCode}; its stderr ends:\n${tail}`,
6719
- hint: `the output of ${command} above says why it failed`
6720
- });
6721
- }
6722
- function missingError(command, cause) {
6723
- return new CliError({
6724
- code: TOOL_MISSING,
6725
- message: `${command} could not be started: it is not on PATH`,
6726
- hint: `install ${command}, then run the same command again`
6727
- }, { cause });
6728
- }
6729
- /** `ctx.spawn`: `spawn` bound to the run, its start failures classified by errno. */
6730
- function contextSpawn(run, defaults, callerLine) {
6731
- return async (command, args = [], options = {}) => {
6732
- let result;
6733
- try {
6734
- result = await run(command, args, {
6735
- cwd: options.cwd ?? defaults.cwd,
6736
- env: options.env ?? defaults.env,
6737
- signal: defaults.signal
6738
- });
6739
- } catch (error) {
6740
- if (isMissingFile(error)) throw missingError(command, error);
6741
- throw classifyErrno(error, {
6742
- path: command,
6743
- callerLine,
6744
- env: defaults.env
6745
- });
6746
- }
6747
- if (options.check === true && result.exitCode !== 0) throw failedError(command, result);
6748
- return result;
6749
- };
6750
- }
6751
- //#endregion
6752
7326
  //#region ../agent-cli/dist/exit-classes.js
6753
7327
  const SIGNAL_EXIT_CODES = {
6754
7328
  SIGINT: 130,
@@ -6771,6 +7345,7 @@ function runtimeCodes() {
6771
7345
  [CONFIRMATION_REQUIRED]: "caller",
6772
7346
  [CONFIRMATION_MISMATCH]: "caller",
6773
7347
  [NOT_CONFIRMED]: "operator",
7348
+ [NEEDS_OPERATOR]: "operator",
6774
7349
  [OUTPUT_EXISTS]: "caller",
6775
7350
  [INTERRUPTED]: "interrupted",
6776
7351
  [DEADLINE_EXCEEDED]: "wait",
@@ -6778,6 +7353,8 @@ function runtimeCodes() {
6778
7353
  [PERMISSION_DENIED]: "operator",
6779
7354
  [DISK_FULL]: "operator",
6780
7355
  [NOT_FOUND]: "caller",
7356
+ [ALREADY_EXISTS]: "caller",
7357
+ [NOT_A_FILE]: "caller",
6781
7358
  [TOOL_FAILED]: "operator",
6782
7359
  [TOOL_MISSING]: "operator",
6783
7360
  [PROJECT_INVALID]: "operator",
@@ -6841,6 +7418,53 @@ function exitCodeOf(error, errors, stop) {
6841
7418
  return exitClass === "interrupted" ? SIGNAL_EXIT_CODES[stopSignalOf(stop?.reason)] : EXIT_CODES[exitClass];
6842
7419
  }
6843
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
6844
7468
  //#region ../agent-cli/dist/help.js
6845
7469
  function table(rows) {
6846
7470
  const width = Math.max(...rows.map(([left]) => left.length));
@@ -6854,10 +7478,11 @@ function section(title, rows, style) {
6854
7478
  ];
6855
7479
  }
6856
7480
  function summaryOf(node) {
6857
- 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;
6858
7483
  }
6859
- function commandRows({ commands }) {
6860
- 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)]);
6861
7486
  }
6862
7487
  function lines(...parts) {
6863
7488
  return `${parts.join("\n")}\n`;
@@ -6885,11 +7510,11 @@ function exitCodeRows(cli) {
6885
7510
  }
6886
7511
  function firstExample(node) {
6887
7512
  if (node.kind === "command") return node.examples.slice(0, 1);
6888
- return Object.values(node.commands).flatMap(firstExample).slice(0, 1);
7513
+ return Object.values(visibleCommands(node)).flatMap(firstExample).slice(0, 1);
6889
7514
  }
6890
7515
  function rootExamples(root) {
6891
7516
  if (root.kind === "command") return [...root.examples];
6892
- const { doctor, ...others } = root.commands;
7517
+ const { doctor, ...others } = visibleCommands(root);
6893
7518
  return [...Object.values(others), ...doctor === void 0 ? [] : [doctor]].flatMap(firstExample);
6894
7519
  }
6895
7520
  function examplesSection(examples, style) {
@@ -6908,6 +7533,7 @@ const EFFECT_NOTES = {
6908
7533
  destructive: "changes something hard to undo; show the Plan with --dry-run, run with --confirm",
6909
7534
  serve: "changes nothing; prints its data at once, then runs until Ctrl-C, which exits 0"
6910
7535
  };
7536
+ const OPERATOR_NOTE = "only a person at a terminal runs it; a Coding agent asks the operator to type it after `!`";
6911
7537
  function choicesOf(field) {
6912
7538
  return field.choices === void 0 ? [] : [`one of: ${field.choices.join(", ")}`];
6913
7539
  }
@@ -6937,8 +7563,10 @@ function commandLines(spec, style, { examples }) {
6937
7563
  ...examples ? examplesSection(spec.examples, style) : [],
6938
7564
  ...section("Arguments", spec.args.map(argRow), style),
6939
7565
  ...section("Options", spec.options.map(optionRow), style),
7566
+ ...spec.help === void 0 ? [] : ["", spec.help],
6940
7567
  "",
6941
7568
  `${style.bold("Effect:")} ${spec.effect} (${EFFECT_NOTES[spec.effect]})`,
7569
+ ...spec.operator ? [`${style.bold("Operator Command:")} ${OPERATOR_NOTE}`] : [],
6942
7570
  ...section("Standard flags", spec.standardFlags.map(flagRow), style)
6943
7571
  ];
6944
7572
  }
@@ -6960,7 +7588,9 @@ function commandData(spec) {
6960
7588
  return {
6961
7589
  name: spec.commandLine,
6962
7590
  description: spec.description,
7591
+ help: spec.help ?? null,
6963
7592
  effect: spec.effect,
7593
+ operator: spec.operator,
6964
7594
  examples: spec.examples,
6965
7595
  args: inputSchema(spec.command.args ?? object({})),
6966
7596
  options: optionsSchema(spec.options),
@@ -6978,7 +7608,7 @@ function nodeData(node) {
6978
7608
  function groupData(group) {
6979
7609
  return {
6980
7610
  name: group.commandLine,
6981
- commands: Object.values(group.commands).map(nodeData)
7611
+ commands: Object.values(visibleCommands(group)).map(nodeData)
6982
7612
  };
6983
7613
  }
6984
7614
  function printed$1(data) {
@@ -7020,6 +7650,10 @@ function printed(value, render, { json, invocation, style, fields }) {
7020
7650
  items: rows
7021
7651
  })}\n` : `${renderRows(rows, fields)}${pageFooter(page)}`;
7022
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
+ }
7023
7657
  function errorText(error, format) {
7024
7658
  const text = formatRuntimeError(error, {
7025
7659
  ...format,
@@ -7106,12 +7740,51 @@ function versionResult(cli, packageRoot) {
7106
7740
  };
7107
7741
  }
7108
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
7109
7779
  //#region ../agent-cli/dist/skills.js
7110
7780
  /** Where the skills CLI (`npx skills add`) puts a skill, under a project and under home. */
7111
7781
  const SKILL_DIRS = [[".claude", "skills"], [".agents", "skills"]];
7112
7782
  function skillUpdateCommand(cliName) {
7113
7783
  return `npx -y skills update ${cliName}`;
7114
7784
  }
7785
+ function skillAddCommand(source, cliName) {
7786
+ return `npx -y skills add ${source} --skill ${cliName} -g`;
7787
+ }
7115
7788
  const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
7116
7789
  const METADATA_VERSION = /^metadata:[ \t]*\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+version:[ \t]*["']?([^"'\s]+)["']?[ \t]*$/m;
7117
7790
  /** The version a SKILL.md's frontmatter `metadata.version` states. */
@@ -7216,14 +7889,15 @@ function projectRow(projectFile) {
7216
7889
  }
7217
7890
  };
7218
7891
  }
7219
- function installRow(cli, bin) {
7892
+ function installRow(cli, { bin, node }) {
7220
7893
  return {
7221
7894
  name: "install",
7895
+ advisory: true,
7222
7896
  run: async (ctx) => {
7223
7897
  const { install } = cli;
7224
7898
  if (install === void 0) return ok(`${cli.name} declares no install, so it has no \`update\``);
7225
7899
  try {
7226
- 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}`);
7227
7901
  } catch (error) {
7228
7902
  if (error instanceof CliError && error.code === "NOT_GLOBAL_INSTALL") return {
7229
7903
  state: "problem",
@@ -7235,13 +7909,14 @@ function installRow(cli, bin) {
7235
7909
  }
7236
7910
  };
7237
7911
  }
7238
- function registryRow(cli) {
7912
+ function registryRow(cli, node) {
7239
7913
  return {
7240
7914
  name: "registry",
7915
+ advisory: true,
7241
7916
  run: async (ctx) => {
7242
7917
  const { install } = cli;
7243
7918
  if (install === void 0) return ok(`${cli.name} declares no registry to compare this build with`);
7244
- const latest = await latestVersion(npmContext(cli, install, ctx));
7919
+ const latest = await latestVersion(npmContext(cli, install, ctx, node));
7245
7920
  const standing = standingOf(cli.version, latest);
7246
7921
  const head = `${install.registry} has ${latest} as latest: this build`;
7247
7922
  if (standing === "behind") return {
@@ -7272,7 +7947,15 @@ function skillRow(cli) {
7272
7947
  name: "skill",
7273
7948
  run: async (ctx) => {
7274
7949
  const copies = await skillCopies(cli.name, cli.version, ctx.cwd, ctx.env);
7275
- 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
+ }
7276
7959
  const fix = skillFix(cli, copies);
7277
7960
  const message = copies.map(describeCopy).join("; ");
7278
7961
  return fix === void 0 ? ok(message) : {
@@ -7284,15 +7967,15 @@ function skillRow(cli) {
7284
7967
  };
7285
7968
  }
7286
7969
  /** Every row `doctor` reports, in order: the fixed rows around the CLI's own and its tools'. */
7287
- function doctorChecks(cli, bin) {
7970
+ function doctorChecks(cli, launch) {
7288
7971
  return [
7289
7972
  buildRow(cli),
7290
7973
  nodeRow(cli),
7291
7974
  ...cli.project === void 0 ? [] : [projectRow(cli.project)],
7292
7975
  ...cli.doctor ?? [],
7293
- ...toolRows(cli.commands),
7294
- installRow(cli, bin),
7295
- registryRow(cli),
7976
+ ...toolRows(cli.tools ?? [], cli.commands),
7977
+ installRow(cli, launch),
7978
+ registryRow(cli, launch.node),
7296
7979
  skillRow(cli)
7297
7980
  ];
7298
7981
  }
@@ -7305,9 +7988,14 @@ const MIN_LABEL_WIDTH = 8;
7305
7988
  const reportSchema = object({
7306
7989
  rows: array(object({
7307
7990
  check: string(),
7308
- state: _enum(["ok", "problem"]),
7991
+ state: _enum([
7992
+ "ok",
7993
+ "problem",
7994
+ "skipped"
7995
+ ]),
7309
7996
  message: string(),
7310
- fix: string().optional()
7997
+ fix: string().optional(),
7998
+ code: string().optional()
7311
7999
  })),
7312
8000
  next: array(string())
7313
8001
  });
@@ -7318,23 +8006,25 @@ function problemOf(check, error, { signal, deadline, ms }) {
7318
8006
  };
7319
8007
  if (error instanceof CliError) return {
7320
8008
  state: "problem",
7321
- message: `${error.message}; ${error.hint}`
8009
+ message: `${error.message}; ${error.hint}`,
8010
+ code: error.code
7322
8011
  };
7323
8012
  return {
7324
8013
  state: "problem",
7325
8014
  message: `the ${check.name} row hit a bug: ${messageOf(error)}`
7326
8015
  };
7327
8016
  }
8017
+ function withSignal(ctx, signal) {
8018
+ return Object.create(ctx, { signal: { value: signal } });
8019
+ }
7328
8020
  async function rowOf(check, ctx) {
7329
8021
  const ms = check.deadlineMs ?? DEFAULT_DEADLINE_MS;
7330
8022
  const signal = ctx.deadline(ms);
7331
8023
  let finding;
7332
8024
  try {
7333
- finding = await Promise.race([Promise.resolve().then(() => check.run({
7334
- ...ctx,
7335
- signal
7336
- })), untilAborted(signal)]);
8025
+ finding = await Promise.race([Promise.resolve().then(() => check.run(withSignal(ctx, signal))), untilAborted(signal)]);
7337
8026
  } catch (error) {
8027
+ if (ctx.signal.aborted) return void 0;
7338
8028
  finding = problemOf(check, error, {
7339
8029
  signal: ctx.signal,
7340
8030
  deadline: signal,
@@ -7360,15 +8050,16 @@ function render({ rows, next }, style) {
7360
8050
  return `${check.padEnd(width)}${label}${" ".repeat(stateWidth - state.length)}${message}`;
7361
8051
  }), ...next.map((fix) => `${"next".padEnd(width)}${fix}`)].join("\n");
7362
8052
  }
7363
- function failure(cliName, problems, report) {
7364
- return new CliError({
7365
- code: DOCTOR_PROBLEMS,
7366
- message: `${cliName} doctor found ${problems.length === 1 ? "a problem" : `${problems.length} problems`}: ${problems.join(", ")}`,
7367
- hint: report.next.length === 0 ? "each problem row says what is wrong" : "run the commands under next, then run doctor again"
7368
- });
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);
7369
8060
  }
7370
8061
  /** The built-in `doctor`: the fixed rows around the CLI's own, each under its own deadline. */
7371
- function doctorCommand(cli, bin) {
8062
+ function doctorCommand(cli, launch) {
7372
8063
  return defineCommand$1({
7373
8064
  name: "doctor",
7374
8065
  description: "check the setup and say what fixes each problem",
@@ -7382,17 +8073,20 @@ function doctorCommand(cli, bin) {
7382
8073
  effect: "read",
7383
8074
  output: reportSchema,
7384
8075
  run: async (_input, ctx) => {
7385
- const checked = await Promise.all(doctorChecks(cli, bin).map((check) => rowOf(check, ctx)));
7386
- ctx.signal.throwIfAborted();
8076
+ const checked = (await Promise.all(doctorChecks(cli, launch).map((check) => rowOf(check, ctx)))).filter((row) => row !== void 0);
7387
8077
  const rows = checked.map(({ row }) => row);
7388
8078
  const report = {
7389
8079
  rows,
7390
8080
  next: nextOf(rows)
7391
8081
  };
7392
- 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);
7393
8087
  if (problems.length > 0) {
7394
8088
  ctx.deliver(report);
7395
- throw failure(cli.name, problems, report);
8089
+ throw failure(cli.name, problems);
7396
8090
  }
7397
8091
  return report;
7398
8092
  },
@@ -7471,7 +8165,7 @@ function interruptedAfterInstall(plan) {
7471
8165
  });
7472
8166
  }
7473
8167
  /** The built-in `update`, for a CLI installed globally with npm. */
7474
- function updateCommand(cli, install, bin) {
8168
+ function updateCommand(cli, install, { bin, node }) {
7475
8169
  return defineCommand$1({
7476
8170
  name: "update",
7477
8171
  description: "install the latest version of this CLI from its registry",
@@ -7485,7 +8179,7 @@ function updateCommand(cli, install, bin) {
7485
8179
  effect: "write",
7486
8180
  output: updateSchema,
7487
8181
  plan: async (_input, ctx) => {
7488
- const npm = npmContext(cli, install, ctx);
8182
+ const npm = npmContext(cli, install, ctx, node);
7489
8183
  const copy = await globalInstallOf(bin, npm);
7490
8184
  return {
7491
8185
  package: install.package,
@@ -7506,7 +8200,7 @@ function updateCommand(cli, install, bin) {
7506
8200
  };
7507
8201
  ctx.signal.throwIfAborted();
7508
8202
  ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
7509
- 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)));
7510
8204
  const update = {
7511
8205
  from,
7512
8206
  latest,
@@ -7530,10 +8224,10 @@ function updateCommand(cli, install, bin) {
7530
8224
  //#endregion
7531
8225
  //#region ../agent-cli/dist/built-ins.js
7532
8226
  /** The Commands the Runtime adds to a multi-Command CLI: `update` only when it declares an install. */
7533
- function builtInCommands(cli, bin) {
8227
+ function builtInCommands(cli, launch) {
7534
8228
  return {
7535
- doctor: doctorCommand(cli, bin),
7536
- ...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) }
7537
8231
  };
7538
8232
  }
7539
8233
  //#endregion
@@ -7546,7 +8240,10 @@ function defineCli$1(cli) {
7546
8240
  checkOwnCodes(cli);
7547
8241
  if (cli.commands === void 0) return cli;
7548
8242
  const { commands } = cli;
7549
- 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);
7550
8247
  if (taken.length > 0) throw new Error(`${cli.name} defines ${taken.join(", ")}, a built-in Command it cannot replace`);
7551
8248
  return cli;
7552
8249
  }
@@ -7574,41 +8271,6 @@ function cliError(_table, code, init, options) {
7574
8271
  }, options);
7575
8272
  }
7576
8273
  //#endregion
7577
- //#region ../agent-cli/dist/command-spec.js
7578
- function declaredOf(command) {
7579
- const planned = command.effect === "write" || command.effect === "destructive";
7580
- return {
7581
- outputs: planned && command.outputs !== void 0,
7582
- cost: planned && command.cost !== void 0
7583
- };
7584
- }
7585
- function commandSpec(commandLine, command) {
7586
- const list = command.rows === void 0 ? void 0 : listSpec(command.rows);
7587
- return {
7588
- kind: "command",
7589
- commandLine,
7590
- command,
7591
- description: command.description,
7592
- effect: command.effect,
7593
- examples: command.examples,
7594
- args: argFields(command.args),
7595
- options: optionFields(command.options),
7596
- standardFlags: standardFlagsFor(command.effect, declaredOf(command), list),
7597
- list,
7598
- output: list?.output ?? command.output
7599
- };
7600
- }
7601
- function groupSpec(commandLine, commands) {
7602
- return {
7603
- kind: "group",
7604
- commandLine,
7605
- commands: Object.fromEntries(Object.entries(commands).map(([key, node]) => [key, specOf(`${commandLine} ${key}`, node)]))
7606
- };
7607
- }
7608
- function specOf(commandLine, node) {
7609
- return isCommand(node) ? commandSpec(commandLine, node) : groupSpec(commandLine, node);
7610
- }
7611
- //#endregion
7612
8274
  //#region ../agent-cli/dist/execute.js
7613
8275
  function outcomeOf(value, render) {
7614
8276
  return {
@@ -7641,7 +8303,7 @@ function dryRunOutcome(command, plan) {
7641
8303
  ].join("\n")
7642
8304
  };
7643
8305
  }
7644
- async function execute(command, input, ctx, { dryRun, confirm, yes, force, page, invocation, commandLine, started }) {
8306
+ async function execute(command, input, ctx, { dryRun, confirm, yes, page, invocation, commandLine, started, files, guardOutputs }) {
7645
8307
  await checkTools(command.tools ?? [], ctx.env);
7646
8308
  if (command.rows !== void 0) return outcomeOf(await command.run({
7647
8309
  ...input,
@@ -7654,12 +8316,13 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, force, page,
7654
8316
  }
7655
8317
  const plan = await command.plan(input, ctx);
7656
8318
  if (dryRun) return dryRunOutcome(command, plan);
8319
+ const outputs = command.outputs?.(plan);
7657
8320
  await checkOutputs({
7658
8321
  invocation,
7659
8322
  commandLine,
7660
- force,
8323
+ force: ctx.force,
7661
8324
  files: ctx.files
7662
- }, command.outputs?.(plan));
8325
+ }, outputs);
7663
8326
  checkCost({
7664
8327
  invocation,
7665
8328
  commandLine,
@@ -7670,58 +8333,15 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, force, page,
7670
8333
  commandLine,
7671
8334
  given: confirm
7672
8335
  }, command.confirm(plan));
7673
- return outcomeOf(await command.apply(plan, ctx), (data, style) => command.render(data, style));
7674
- }
7675
- //#endregion
7676
- //#region ../agent-cli/dist/files.js
7677
- const TEMP_FILE = /^\..+\.agent-cli-(\d+)\.tmp$/;
7678
- function tempPathOf(path) {
7679
- return join(dirname(path), `.${basename(path)}.agent-cli-${process.pid}.tmp`);
7680
- }
7681
- function isAlive(pid) {
7682
- try {
7683
- process.kill(pid, 0);
7684
- return true;
7685
- } catch (error) {
7686
- return errnoOf(error) === "EPERM";
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] }));
7687
8343
  }
7688
- }
7689
- async function sweep(folder) {
7690
- const dead = (await readdir(folder)).filter((name) => {
7691
- const pid = TEMP_FILE.exec(name)?.[1];
7692
- return pid !== void 0 && !isAlive(Number(pid));
7693
- });
7694
- await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
7695
- }
7696
- /** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
7697
- function realFiles() {
7698
- const swept = /* @__PURE__ */ new Set();
7699
- return {
7700
- exists: async (path) => {
7701
- try {
7702
- await access(path);
7703
- return true;
7704
- } catch (error) {
7705
- if (isMissingFile(error)) return false;
7706
- throw error;
7707
- }
7708
- },
7709
- write: async (path, data) => {
7710
- const folder = dirname(path);
7711
- if (!swept.has(folder)) {
7712
- await sweep(folder);
7713
- swept.add(folder);
7714
- }
7715
- const temp = tempPathOf(path);
7716
- try {
7717
- await writeFile(temp, data);
7718
- await rename(temp, path);
7719
- } catch (error) {
7720
- await rm(temp, { force: true });
7721
- throw error;
7722
- }
7723
- }
7724
- };
8344
+ return outcomeOf(await command.apply(plan, ctx), (data, style) => command.render(data, style));
7725
8345
  }
7726
8346
  //#endregion
7727
8347
  //#region ../agent-cli/dist/package-root.js
@@ -7780,88 +8400,6 @@ function closestFlag(flag, flags) {
7780
8400
  return closest.length === 1 ? closest[0] : void 0;
7781
8401
  }
7782
8402
  //#endregion
7783
- //#region ../agent-cli/dist/tokenize.js
7784
- const NEGATIVE_NUMBER = /^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/;
7785
- function isOptionLike(word) {
7786
- return word !== void 0 && word.length > 1 && word[0] === "-" && !NEGATIVE_NUMBER.test(word);
7787
- }
7788
- function parseArgsOptions(known) {
7789
- return Object.fromEntries(known.map(({ flag, takes }) => [flag.slice(2), { type: takes === "value" ? "string" : "boolean" }]));
7790
- }
7791
- function flagItem(token, { flag, takes }, words) {
7792
- if (takes !== "optional value" || token.inlineValue === true) return {
7793
- kind: "flag",
7794
- flag,
7795
- value: token.value ?? true
7796
- };
7797
- const took = token.index + 1;
7798
- const next = words[took];
7799
- return next === void 0 || isOptionLike(next) ? {
7800
- kind: "flag",
7801
- flag,
7802
- value: true
7803
- } : {
7804
- kind: "flag",
7805
- flag,
7806
- value: next,
7807
- took
7808
- };
7809
- }
7810
- function itemOf(token, words, byFlag) {
7811
- const word = words[token.index] ?? "";
7812
- if (token.kind === "option-terminator") return { kind: "end" };
7813
- if (token.kind === "positional" || NEGATIVE_NUMBER.test(word)) return {
7814
- kind: "operand",
7815
- value: word
7816
- };
7817
- const flag = byFlag.get(token.rawName);
7818
- return flag === void 0 || flag.takes === "nothing" && token.inlineValue === true ? {
7819
- kind: "unknown option",
7820
- word
7821
- } : flagItem(token, flag, words);
7822
- }
7823
- /** Splits a Command's words into operands, unknown options and the values of known flags. */
7824
- function tokenize(words, known) {
7825
- const byFlag = new Map(known.map((flag) => [flag.flag, flag]));
7826
- const { tokens } = parseArgs({
7827
- args: [...words],
7828
- options: parseArgsOptions(known),
7829
- strict: false,
7830
- allowPositionals: true,
7831
- tokens: true
7832
- });
7833
- const operands = [];
7834
- const unknownOptions = [];
7835
- const values = /* @__PURE__ */ new Map();
7836
- const done = /* @__PURE__ */ new Set();
7837
- let literal = false;
7838
- let afterUnknownOption = false;
7839
- for (const token of tokens) {
7840
- if (done.has(token.index)) continue;
7841
- done.add(token.index);
7842
- const item = itemOf(token, words, byFlag);
7843
- if (item.kind === "end") literal = true;
7844
- else if (item.kind === "operand") {
7845
- operands.push({
7846
- value: item.value,
7847
- afterUnknownOption: !literal && afterUnknownOption
7848
- });
7849
- afterUnknownOption = false;
7850
- } else if (item.kind === "unknown option") {
7851
- unknownOptions.push(item.word);
7852
- afterUnknownOption = true;
7853
- } else {
7854
- values.set(item.flag, item.value);
7855
- if (item.took !== void 0) done.add(item.took);
7856
- }
7857
- }
7858
- return {
7859
- operands,
7860
- unknownOptions,
7861
- values
7862
- };
7863
- }
7864
- //#endregion
7865
8403
  //#region ../agent-cli/dist/parse-input.js
7866
8404
  const NO_INPUT = object({});
7867
8405
  function knownFlags(spec) {
@@ -7889,28 +8427,46 @@ function unknownOptionsOf(words, candidates) {
7889
8427
  };
7890
8428
  });
7891
8429
  }
7892
- 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];
7893
8433
  return field.type === "boolean" && raw === true && field.label === `--no-${field.name}` ? false : raw;
7894
8434
  }
7895
8435
  function converted(field, raw) {
8436
+ if (Array.isArray(raw)) return raw.map((element) => converted(field, element));
7896
8437
  if (field.type !== "number" || typeof raw !== "string" || raw.trim() === "") return raw;
7897
8438
  const number = Number(raw);
7898
8439
  return Number.isNaN(number) ? raw : number;
7899
8440
  }
8441
+ function variadicValue(field, values) {
8442
+ return values.length === 0 && !field.required ? void 0 : values;
8443
+ }
7900
8444
  function parse(schema, values) {
7901
8445
  const input = Object.fromEntries([...values].filter(([, raw]) => raw !== void 0).map(([field, raw]) => [field.key, converted(field, raw)]));
7902
8446
  return schema.safeParse(input);
7903
8447
  }
7904
- function issueLines(result, values, howToPass, reported) {
8448
+ function issueLines(result, values, { howToPass, arrayIssueLine, reported = [], fromStdin }) {
7905
8449
  if (result.success) return [];
7906
8450
  return result.error.issues.flatMap((issue) => {
7907
8451
  const field = [...values.keys()].find((candidate) => candidate.key === issue.path[0]);
7908
8452
  if (field === void 0) return [` ${issue.message}`];
7909
8453
  if (reported.includes(field)) return [];
7910
8454
  const raw = values.get(field);
7911
- return raw === void 0 ? [` ${field.label}: required, ${howToPass(field)}`] : [` ${field.label} ${JSON.stringify(raw)}: ${issue.message}`];
8455
+ if (raw === void 0) return [` ${field.label}: required, ${howToPass(field)}`];
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}`];
7912
8458
  });
7913
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
+ }
8465
+ function operandsIssueLine(field, raw, issue, first) {
8466
+ const [, element] = issue.path;
8467
+ if (typeof element === "number") return ` ${field.label} ${JSON.stringify(raw[element])} (argument ${first + element}): ${issue.message}`;
8468
+ return raw.length === 0 ? ` ${field.label}: ${issue.message}, pass them as arguments from ${first}` : ` ${field.label} ${JSON.stringify(raw)}: ${issue.message}`;
8469
+ }
7914
8470
  function unknownLine({ flag, suggestion }) {
7915
8471
  return ` unknown option '${flag}'${suggestion === void 0 ? "" : ` (did you mean ${suggestion}?)`}`;
7916
8472
  }
@@ -7927,15 +8483,36 @@ function invalidInput(commandLine, invocation, unknown, otherLines) {
7927
8483
  hint: correctable ? `did you mean \`${commandLineRenaming(invocation, renames)}\`?` : `run \`${commandLine} --help\` to see the expected input`
7928
8484
  });
7929
8485
  }
7930
- 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) {
7931
8496
  const { command, args: argFields, options: optionFields } = spec;
7932
8497
  const pageFields = spec.list?.pageFields ?? [];
7933
8498
  const known = knownFlags(spec);
7934
- const { operands, unknownOptions, values } = tokenize(words, known);
8499
+ const { operands, unknownOptions, values, occurrences } = tokenize(words, known);
8500
+ const stdin = await stdinTextOf(spec, operands, readStdin);
7935
8501
  if (values.get(CONFIRM_FLAG.flag) === true) throw invalidInputError(spec.commandLine, `option '${CONFIRM_FLAG.usage}' argument missing`);
7936
- const missing = [...optionFields, ...pageFields].filter((field) => field.type !== "boolean" && values.get(field.label) === true);
7937
- const argValues = new Map(argFields.map((field, index) => [field, operands[index]?.value]));
7938
- const valuesOf = (fields) => new Map(fields.map((field) => [field, missing.includes(field) ? void 0 : optionValue(field, values.get(field.label))]));
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)]));
8509
+ const extraOperands = argFields.some((field) => field.variadic) ? [] : operands.slice(argFields.length);
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
+ };
7939
8516
  const optionValues = valuesOf(optionFields);
7940
8517
  const pageValues = valuesOf(pageFields);
7941
8518
  const args = parse(command.args ?? NO_INPUT, argValues);
@@ -7944,10 +8521,14 @@ function parseInput(spec, words, invocation) {
7944
8521
  const unknown = unknownOptionsOf(unknownOptions, candidateFlags(known));
7945
8522
  const otherLines = [
7946
8523
  ...missing.map((field) => ` ${field.label}: missing value, pass ${field.usage}`),
7947
- ...operands.slice(argFields.length).filter((operand) => !operand.afterUnknownOption).map((operand) => ` unexpected argument ${JSON.stringify(operand.value)}`),
7948
- ...issueLines(args, argValues, (field) => `pass it as argument ${argFields.indexOf(field) + 1}`, []),
7949
- ...issueLines(options, optionValues, (field) => `pass ${field.usage}`, missing),
7950
- ...issueLines(page, pageValues, (field) => `pass ${field.usage}`, missing)
8524
+ ...extraOperands.filter((operand) => !operand.afterUnknownOption).map((operand) => ` unexpected argument ${JSON.stringify(operand.value)}`),
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)
7951
8532
  ];
7952
8533
  if (unknown.length === 0 && otherLines.length === 0 && args.success && options.success && page.success) return {
7953
8534
  input: {
@@ -7961,9 +8542,12 @@ function parseInput(spec, words, invocation) {
7961
8542
  if (only !== void 0 && unknown.length === 1 && otherLines.length === 0) throw unknownOptionError(spec.commandLine, invocation, only.flag, only.suggestion === void 0 ? [] : [only.suggestion]);
7962
8543
  throw invalidInput(spec.commandLine, invocation, unknown, otherLines);
7963
8544
  }
7964
- /** The validated input and Standard flag values of the routed Command, or one usage error. */
7965
- function parseCommand({ spec, words, json }, invocation) {
7966
- 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);
7967
8551
  const confirm = values.get(CONFIRM_FLAG.flag);
7968
8552
  return {
7969
8553
  input,
@@ -8034,7 +8618,7 @@ function walk(node, words, json, invocation) {
8034
8618
  return unknownCommand(node, first, json);
8035
8619
  }
8036
8620
  function unknownCommand(group, name, json) {
8037
- const suggestions = closestNames(name, Object.keys(group.commands));
8621
+ const suggestions = closestNames(name, Object.keys(visibleCommands(group)));
8038
8622
  return {
8039
8623
  kind: "usage error",
8040
8624
  error: unknownCommandError(group.commandLine, name, suggestions),
@@ -8059,23 +8643,28 @@ function dotenvPath(cliName, env) {
8059
8643
  const config = env.XDG_CONFIG_HOME || join(env.HOME || homedir(), ".config");
8060
8644
  return join(config, cliName, ".env");
8061
8645
  }
8062
- function readDotenv(path) {
8646
+ function readDotenv(path, fileError) {
8647
+ let text;
8063
8648
  try {
8064
- return parseEnv(readFileSync(path, "utf8"));
8649
+ text = readFileSync(path, "utf8");
8065
8650
  } catch (error) {
8066
8651
  if (isMissingFile(error)) return {};
8067
- throw error;
8652
+ throw fileError(error, {
8653
+ path,
8654
+ access: "read"
8655
+ });
8068
8656
  }
8657
+ return parseEnv(text);
8069
8658
  }
8070
8659
  /** Looks `name` up in the env, then in `$XDG_CONFIG_HOME/<cli>/.env`; an empty value counts as unset. */
8071
- function secretOf(cliName, env, name) {
8660
+ function secretOf({ cliName, env, fileError }, name) {
8072
8661
  const fromEnv = env[name];
8073
8662
  if (fromEnv) return {
8074
8663
  value: fromEnv,
8075
8664
  origin: { kind: "env" }
8076
8665
  };
8077
8666
  const path = dotenvPath(cliName, env);
8078
- const fromFile = readDotenv(path)[name];
8667
+ const fromFile = readDotenv(path, fileError)[name];
8079
8668
  return fromFile ? {
8080
8669
  value: fromFile,
8081
8670
  origin: {
@@ -8109,10 +8698,9 @@ async function prepare(folder, cutoff, target) {
8109
8698
  await mkdir(folder, { recursive: true });
8110
8699
  await pruneOld(folder, cutoff);
8111
8700
  } catch (error) {
8112
- throw classifyErrno(error, {
8701
+ throw target.fileError(error, {
8113
8702
  path: folder,
8114
- callerLine: target.callerLine,
8115
- env: target.env
8703
+ access: "write"
8116
8704
  });
8117
8705
  }
8118
8706
  }
@@ -8168,51 +8756,22 @@ function styleFor(env, stdoutIsTTY) {
8168
8756
  }
8169
8757
  //#endregion
8170
8758
  //#region ../agent-cli/dist/run-cli.js
8171
- /** `files` resolving paths against `cwd`, its failures classified by errno. */
8172
- function classifiedFiles(files, base, callerLine) {
8173
- return {
8174
- exists: async (path) => {
8175
- const target = resolve(base.cwd, path);
8176
- try {
8177
- return await files.exists(target);
8178
- } catch (error) {
8179
- throw classifyErrno(error, {
8180
- path: target,
8181
- callerLine,
8182
- env: base.env
8183
- });
8184
- }
8185
- },
8186
- write: async (path, data) => {
8187
- const target = resolve(base.cwd, path);
8188
- try {
8189
- await files.write(target, data);
8190
- } catch (error) {
8191
- throw classifyErrno(error, {
8192
- path: target,
8193
- callerLine,
8194
- env: base.env
8195
- });
8196
- }
8197
- }
8198
- };
8759
+ function stdinNeverEndedHint(callerLine) {
8760
+ return `stdin never ended: pipe the text into \`${callerLine}\`, or redirect a file into it`;
8199
8761
  }
8200
- async function runCommand(routed, { base, writeStdout, signal, format: baseFormat, ports, clock, files, spawn, projectFile }) {
8762
+ async function runCommand(routed, { base, writeStdout, signal, format: baseFormat, ports, clock, files, spawn, projectFile, stdin, platform }) {
8201
8763
  const { command, commandLine } = routed.spec;
8764
+ const invocation = {
8765
+ ...baseFormat.invocation,
8766
+ flags: knownFlags(routed.spec)
8767
+ };
8202
8768
  const format = {
8203
8769
  ...baseFormat,
8204
- json: routed.json
8770
+ json: routed.json,
8771
+ invocation
8205
8772
  };
8206
- const { invocation } = format;
8207
- let parsed;
8208
- let project;
8209
- try {
8210
- parsed = parseCommand(routed, invocation);
8211
- project = projectFile === void 0 ? void 0 : projectOf(projectFile, base.cwd);
8212
- } catch (error) {
8213
- return errorResult(error, format);
8214
- }
8215
- const { input, flags } = parsed;
8773
+ const typedLine = (change) => commandLineOf(invocation, change);
8774
+ let resumeHint;
8216
8775
  const control = new RunControl({
8217
8776
  commandLine,
8218
8777
  outer: signal,
@@ -8220,35 +8779,62 @@ async function runCommand(routed, { base, writeStdout, signal, format: baseForma
8220
8779
  progress: base.progress,
8221
8780
  runDeadline: command.deadline === void 0 ? void 0 : {
8222
8781
  ms: command.deadline.ms,
8223
- hint: command.deadline.resume(input)
8782
+ hint: () => resumeHint ?? stdinNeverEndedHint(typedLine())
8224
8783
  }
8225
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);
8226
8800
  let delivered;
8227
- const callerLine = commandLineOf(invocation);
8228
- const classified = classifiedFiles(files, base, callerLine);
8801
+ const { fileError } = base;
8802
+ const outputs = outputsGuard(classifiedFiles(files, {
8803
+ cwd: base.cwd,
8804
+ fileError
8805
+ }), base.cwd);
8229
8806
  const spill = spillTo({
8230
8807
  cliName: format.cliName,
8231
8808
  commandName: command.name,
8232
8809
  env: base.env,
8233
8810
  clock,
8234
- files: classified,
8235
- callerLine
8811
+ files: outputs.files,
8812
+ fileError
8236
8813
  });
8237
- const ctx = {
8814
+ const portsBase = {
8238
8815
  ...base,
8239
8816
  signal: control.signal,
8240
- deadline: (ms) => control.deadline(ms),
8241
- runToEnd: (name, work) => control.runToEnd(name, work),
8242
- ports: ports(),
8243
8817
  clock,
8244
- files: classified,
8818
+ files: outputs.files,
8245
8819
  spawn: contextSpawn(spawn, {
8246
8820
  cwd: base.cwd,
8247
8821
  env: base.env,
8248
- signal: control.signal
8249
- }, callerLine),
8822
+ signal: control.signal,
8823
+ progress: base.progress
8824
+ }, fileError)
8825
+ };
8826
+ const builtPorts = once(() => ports(portsBase));
8827
+ const ctx = {
8828
+ ...portsBase,
8829
+ deadline: (ms) => control.deadline(ms),
8830
+ runToEnd: (name, work) => control.runToEnd(name, work),
8831
+ get ports() {
8832
+ return builtPorts();
8833
+ },
8250
8834
  project,
8251
- poll: (check, { intervalMs }) => poll(clock, check, intervalMs, control.signal),
8835
+ force: flags.force,
8836
+ commandLine: typedLine,
8837
+ poll: (check, { intervalMs, signal: stop }) => poll(clock, check, intervalMs, stop === void 0 ? control.signal : AbortSignal.any([control.signal, stop])),
8252
8838
  deliver: (data) => {
8253
8839
  delivered = deliveredOutcome(command, data);
8254
8840
  }
@@ -8262,14 +8848,15 @@ async function runCommand(routed, { base, writeStdout, signal, format: baseForma
8262
8848
  dryRun: flags.dryRun,
8263
8849
  confirm: flags.confirm,
8264
8850
  yes: flags.yes,
8265
- force: flags.force,
8266
8851
  page: flags.page,
8267
8852
  invocation,
8268
8853
  commandLine,
8269
8854
  started: async (outcome) => {
8270
8855
  writeStdout(await startedOutput(outcome, format, spill));
8271
8856
  served = true;
8272
- }
8857
+ },
8858
+ files,
8859
+ guardOutputs: outputs.guard
8273
8860
  }))
8274
8861
  };
8275
8862
  } catch (error) {
@@ -8285,29 +8872,78 @@ async function runCommand(routed, { base, writeStdout, signal, format: baseForma
8285
8872
  fields: flags.fields
8286
8873
  }, spill);
8287
8874
  }
8875
+ /** `build` runs on the first call; later calls give its value or throw its error again. */
8876
+ function once(build) {
8877
+ let outcome;
8878
+ return () => {
8879
+ if (outcome === void 0) try {
8880
+ outcome = {
8881
+ ok: true,
8882
+ value: build()
8883
+ };
8884
+ } catch (error) {
8885
+ outcome = {
8886
+ ok: false,
8887
+ error
8888
+ };
8889
+ }
8890
+ if (outcome.ok) return outcome.value;
8891
+ throw outcome.error;
8892
+ };
8893
+ }
8894
+ function selfOf(name, bin) {
8895
+ return bin === void 0 ? [name] : [
8896
+ process.execPath,
8897
+ ...process.execArgv,
8898
+ bin
8899
+ ];
8900
+ }
8288
8901
  function portsOf(cli, replacement) {
8289
8902
  if (replacement !== void 0) return () => replacement;
8290
8903
  return cli.ports ?? (() => ({}));
8291
8904
  }
8292
- 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) {
8293
8919
  const format = {
8294
8920
  cliName: cli.name,
8295
8921
  env: options.env,
8296
8922
  style: styleFor(options.env, options.stdoutIsTTY === true),
8297
8923
  invocation: {
8298
8924
  cliName: cli.name,
8299
- argv
8925
+ argv,
8926
+ flags: []
8300
8927
  },
8301
8928
  json: false,
8302
8929
  errors: cli.errors ?? {},
8303
8930
  stop: options.signal
8304
8931
  };
8305
- 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
+ });
8306
8936
  const root = cli.commands === void 0 ? commandSpec(cli.name, cli.command) : groupSpec(cli.name, {
8307
8937
  ...cli.commands,
8308
8938
  ...builtIns
8309
8939
  });
8310
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
+ });
8311
8947
  const misused = (node) => isAgent(options.env) ? {
8312
8948
  ...format,
8313
8949
  usageHelp: textHelp(node, root, cli, format.style)
@@ -8322,13 +8958,21 @@ async function runWithProgress(cli, argv, options, write) {
8322
8958
  ...misused(routed.node),
8323
8959
  json: routed.json
8324
8960
  });
8325
- 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, {
8326
8962
  base: {
8327
8963
  cwd: options.cwd,
8328
8964
  env: options.env,
8329
8965
  packageRoot: packageRootOf(options.bin, options.cwd),
8330
- progress: (message) => write.stderr(`${message}\n`),
8331
- secret: (name) => secretOf(cli.name, options.env, name)
8966
+ progress: (message) => write.stderr(progressLine(message, routed.json)),
8967
+ secret: (name) => secretOf({
8968
+ cliName: cli.name,
8969
+ env: options.env,
8970
+ fileError
8971
+ }, name),
8972
+ fileError,
8973
+ version: cli.version,
8974
+ build: cli.build,
8975
+ self: selfOf(cli.name, options.bin)
8332
8976
  },
8333
8977
  writeStdout: (text) => write.stdout(text),
8334
8978
  signal: options.signal,
@@ -8337,8 +8981,10 @@ async function runWithProgress(cli, argv, options, write) {
8337
8981
  clock: options.clock ?? realClock,
8338
8982
  files: options.files ?? realFiles(),
8339
8983
  spawn: options.spawn ?? realSpawn,
8340
- projectFile: Object.values(builtIns).includes(routed.spec.command) ? void 0 : cli.project
8341
- });
8984
+ projectFile: Object.values(builtIns).includes(routed.spec.command) ? void 0 : cli.project,
8985
+ stdin: options.stdin ?? "",
8986
+ platform
8987
+ }));
8342
8988
  }
8343
8989
  }
8344
8990
  //#endregion
@@ -8350,7 +8996,6 @@ async function runProcess(cli) {
8350
8996
  const stop = new AbortController();
8351
8997
  const onSignal = (signal) => {
8352
8998
  if (stop.signal.aborted) process.exit(SIGNAL_EXIT_CODES[signal]);
8353
- process.stderr.write("\nstopping… (Ctrl-C again to quit now)\n");
8354
8999
  stop.abort(signal);
8355
9000
  };
8356
9001
  process.on("SIGINT", onSignal);
@@ -8361,11 +9006,13 @@ async function runProcess(cli) {
8361
9006
  cwd: process.cwd(),
8362
9007
  stdoutIsTTY: process.stdout.isTTY === true,
8363
9008
  signal: stop.signal,
8364
- bin: process.argv[1]
9009
+ bin: process.argv[1],
9010
+ node: process.execPath,
9011
+ stdin: process.stdin
8365
9012
  }, {
8366
9013
  stdout: (text) => process.stdout.write(text),
8367
9014
  stderr: (text) => process.stderr.write(text)
8368
- });
9015
+ }, "stopping… (Ctrl-C again to quit now)");
8369
9016
  await written(process.stdout, result.stdout);
8370
9017
  await written(process.stderr, result.stderr);
8371
9018
  process.exitCode = result.exitCode;
@@ -8378,9 +9025,9 @@ async function runProcess(cli) {
8378
9025
  //#endregion
8379
9026
  //#region src/build.ts
8380
9027
  const build = {
8381
- "sha": "411548a",
9028
+ "sha": "c325cae",
8382
9029
  "dirty": false,
8383
- "builtAt": "2026-10-19T08:42:00.085Z"
9030
+ "builtAt": "2026-10-19T14:33:32.022Z"
8384
9031
  };
8385
9032
  //#endregion
8386
9033
  //#region src/steps.ts
@@ -8509,7 +9156,7 @@ async function writeTemplate(root, files, values) {
8509
9156
  }
8510
9157
  //#endregion
8511
9158
  //#region src/version.ts
8512
- const version = "0.3.0";
9159
+ const version = "0.5.0";
8513
9160
  //#endregion
8514
9161
  //#region src/create.ts
8515
9162
  const { defineCommand } = agentCli();