@duyquangnvx/create-agent-cli 0.20.0 → 0.22.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,10 @@
1
1
  #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { constants, homedir, tmpdir } from "node:os";
2
4
  import { parseArgs, parseEnv } from "node:util";
3
- import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
- import { constants, existsSync, readFileSync, realpathSync } from "node:fs";
5
- import { constants as constants$1, homedir, tmpdir } from "node:os";
5
+ import { constants as constants$1, existsSync, readFileSync, realpathSync } from "node:fs";
6
6
  import { access, chmod, link, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
7
- import { spawn } from "node:child_process";
7
+ import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
8
8
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
9
9
  import { fileURLToPath } from "node:url";
10
10
  //#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/util.js
@@ -5740,40 +5740,6 @@ function untilAborted(signal) {
5740
5740
  });
5741
5741
  }
5742
5742
  //#endregion
5743
- //#region ../agent-cli/dist/clock.js
5744
- const realClock = {
5745
- now: () => Date.now(),
5746
- sleep: (ms, signal) => new Promise((resolve, reject) => {
5747
- if (signal?.aborted === true) {
5748
- reject(signal.reason);
5749
- return;
5750
- }
5751
- const onAbort = () => {
5752
- clearTimeout(timer);
5753
- reject(signal?.reason);
5754
- };
5755
- const timer = setTimeout(() => {
5756
- signal?.removeEventListener("abort", onAbort);
5757
- resolve();
5758
- }, ms);
5759
- signal?.addEventListener("abort", onAbort, { once: true });
5760
- })
5761
- };
5762
- /** Runs `then` once `ms` have passed on `clock`; aborting `cancel` drops it. */
5763
- function after(clock, ms, cancel, then) {
5764
- clock.sleep(ms, cancel).then(then, (error) => {
5765
- if (!cancel.aborted) throw error;
5766
- });
5767
- }
5768
- async function poll(clock, check, intervalMs, signal) {
5769
- for (;;) {
5770
- signal.throwIfAborted();
5771
- const value = await check();
5772
- if (value !== void 0) return value;
5773
- await clock.sleep(intervalMs, signal);
5774
- }
5775
- }
5776
- //#endregion
5777
5743
  //#region ../agent-cli/dist/command-line/tokenize.js
5778
5744
  const NEGATIVE_NUMBER = /^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/;
5779
5745
  function isOptionLike(word) {
@@ -5926,6 +5892,9 @@ function wholeLine(line) {
5926
5892
  function isWords(step) {
5927
5893
  return Array.isArray(step);
5928
5894
  }
5895
+ function isRerun(step) {
5896
+ return !isWords(step) && step.kind === "rerun";
5897
+ }
5929
5898
  /** The words after the Run-as, or `undefined` for a line given whole. */
5930
5899
  function wordsOf(step) {
5931
5900
  if (isWords(step)) return step;
@@ -6081,1006 +6050,1081 @@ function formatRuntimeError(error, format) {
6081
6050
  return errors.map((each) => errorLines(each, format)).join("");
6082
6051
  }
6083
6052
  //#endregion
6084
- //#region ../agent-cli/dist/context/stdin.js
6085
- const STDIN_META = "stdin";
6086
- function isStdinSchema(schema) {
6087
- return globalRegistry$1.get(schema)?.[STDIN_META] === true;
6088
- }
6089
- function endOfInputKeys(platform) {
6090
- return platform === "win32" ? "Ctrl-Z then Enter" : "Ctrl-D";
6091
- }
6092
- /** The progress line shown before reading a terminal, so a person isn't left facing silence. */
6093
- function readingLine(name, platform) {
6094
- return `reading <${name}> from stdin; end it with ${endOfInputKeys(platform)}`;
6095
- }
6096
- function isTerminal(source) {
6097
- return typeof source !== "string" && "isTTY" in source && source.isTTY === true;
6053
+ //#region ../agent-cli/dist/tail.js
6054
+ /** The last `count` lines of a child's output, trimmed; empty when it wrote nothing. */
6055
+ function lastLines(output, count) {
6056
+ return output.trim().split("\n").slice(-count).join("\n");
6098
6057
  }
6099
- const STDIN_STALLED = "STDIN_STALLED";
6100
- /** How long a stdin that is not a terminal may send nothing, from the start of the read and from each chunk. */
6101
- const STDIN_IDLE_MS = 3e4;
6102
- function stdinStalled(callerLine) {
6103
- return new CliError({
6104
- code: STDIN_STALLED,
6105
- message: `stdin sent nothing for ${STDIN_IDLE_MS} ms`,
6106
- hint: `stdin never ended: close the pipe or heredoc that feeds \`${callerLine}\`, pass the text as an argument instead, or write a slow producer's output to a file and redirect it in`
6107
- });
6058
+ //#endregion
6059
+ //#region ../agent-cli/dist/define.js
6060
+ function isCommand(node) {
6061
+ return typeof node.effect === "string";
6108
6062
  }
6063
+ //#endregion
6064
+ //#region ../agent-cli/dist/context/declared.js
6109
6065
  /**
6110
- * The whole of `source` as UTF-8; rejects with the signal's reason once it aborts, and with
6111
- * `idle.stalled()` once a stream that is not a terminal sends nothing for `STDIN_IDLE_MS`.
6066
+ * What the CLI declares itself (`own`) and every Command under `commands` declares, each name once,
6067
+ * for a `doctor` row: a Command requires only its own, but `doctor` checks them all.
6112
6068
  */
6113
- function readStdin(source, signal, idle) {
6114
- if (typeof source === "string") return signal.aborted ? Promise.reject(signal.reason) : Promise.resolve(source);
6115
- return new Promise((resolve, reject) => {
6116
- const chunks = [];
6117
- let idleWindow = new AbortController();
6118
- const watchIdle = () => {
6119
- idleWindow.abort();
6120
- idleWindow = new AbortController();
6121
- if (isTerminal(source)) return;
6122
- after(idle.clock, STDIN_IDLE_MS, idleWindow.signal, () => {
6123
- stop();
6124
- reject(idle.stalled());
6125
- });
6126
- };
6127
- const onData = (chunk) => {
6128
- chunks.push(chunk instanceof Uint8Array ? chunk : Buffer.from(String(chunk)));
6129
- watchIdle();
6130
- };
6131
- const onEnd = () => {
6132
- stop();
6133
- resolve(Buffer.concat(chunks).toString("utf8"));
6134
- };
6135
- const onError = (error) => {
6136
- stop();
6137
- reject(error);
6138
- };
6139
- const onAbort = () => {
6140
- stop();
6141
- reject(signal.reason);
6142
- };
6143
- const stop = () => {
6144
- source.off("data", onData).off("end", onEnd).off("error", onError).pause();
6145
- signal.removeEventListener("abort", onAbort);
6146
- idleWindow.abort();
6147
- };
6148
- if (signal.aborted) {
6149
- reject(signal.reason);
6150
- return;
6151
- }
6152
- signal.addEventListener("abort", onAbort, { once: true });
6153
- watchIdle();
6154
- source.on("data", onData).once("end", onEnd).once("error", onError);
6155
- });
6069
+ function declaredIn(own, commands, needsOf) {
6070
+ const walk = (node) => isCommand(node) ? [...needsOf(node) ?? []] : Object.values(node).flatMap(walk);
6071
+ return [...new Map([...own, ...walk(commands)].map((need) => [need.name, need])).values()];
6156
6072
  }
6157
6073
  //#endregion
6158
- //#region ../agent-cli/dist/command-line/input.js
6159
- function isWrapper(schema) {
6160
- return schema instanceof ZodOptional || schema instanceof ZodNullable || schema instanceof ZodDefault || schema instanceof ZodPrefault;
6161
- }
6162
- function kebab(key) {
6163
- return key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
6074
+ //#region ../agent-cli/dist/context/tools.js
6075
+ const TOOL_MISSING = "TOOL_MISSING";
6076
+ const WINDOWS_EXTENSIONS = ".COM;.EXE;.BAT;.CMD";
6077
+ function pathOf(env) {
6078
+ return env[Object.keys(env).find((name) => name.toUpperCase() === "PATH") ?? "PATH"] ?? "";
6164
6079
  }
6165
- function typeOf(schema) {
6166
- if (schema instanceof ZodNumber) return "number";
6167
- return schema instanceof ZodBoolean ? "boolean" : "string";
6080
+ function extensionsOf(env) {
6081
+ return process.platform === "win32" ? (env.PATHEXT ?? WINDOWS_EXTENSIONS).split(";") : [""];
6168
6082
  }
6169
- function describe(schema) {
6170
- let current = schema;
6171
- let description = current.description;
6172
- let required = true;
6173
- let defaultValue;
6174
- let stdin = isStdinSchema(current);
6175
- while (isWrapper(current)) {
6176
- if (current instanceof ZodDefault || current instanceof ZodPrefault) defaultValue = current.def.defaultValue;
6177
- required = false;
6178
- current = current.unwrap();
6179
- description ??= current.description;
6180
- stdin ||= isStdinSchema(current);
6083
+ async function isExecutable(file) {
6084
+ try {
6085
+ await access(file, constants$1.X_OK);
6086
+ return true;
6087
+ } catch (error) {
6088
+ if (isNothingThere(error) || errnoOf(error) === "EACCES") return false;
6089
+ throw error;
6181
6090
  }
6182
- const variadic = current instanceof ZodArray;
6183
- if (current instanceof ZodArray) current = current.element;
6184
- return {
6185
- type: typeOf(current),
6186
- required,
6187
- defaultValue,
6188
- description,
6189
- choices: current instanceof ZodEnum ? current.options.map(String) : void 0,
6190
- variadic,
6191
- stdin
6192
- };
6193
6091
  }
6194
- function argLabel(key, { variadic, required, stdin }) {
6195
- if (stdin) return required ? `<${key}|->` : `[${key}|-]`;
6196
- if (!variadic) return `<${key}>`;
6197
- return required ? `<${key}...>` : `[${key}...]`;
6092
+ /** The file names `name` may have on `PATH`: itself when it already ends in one of the extensions. */
6093
+ function fileNamesOf(name, env) {
6094
+ const extensions = extensionsOf(env);
6095
+ const own = extname(name).toUpperCase();
6096
+ return own !== "" && extensions.some((extension) => extension.toUpperCase() === own) ? [name] : extensions.map((extension) => `${name}${extension}`);
6198
6097
  }
6199
- function argField(key, schema) {
6200
- const described = describe(schema);
6201
- const label = argLabel(key, described);
6202
- return {
6203
- key,
6204
- name: key,
6205
- schema,
6206
- ...described,
6207
- label,
6208
- usage: label
6209
- };
6098
+ /** Where `name` is found on `PATH` from `env`, or undefined. */
6099
+ async function findTool(name, env) {
6100
+ const candidates = pathOf(env).split(delimiter).filter((folder) => folder !== "").flatMap((folder) => fileNamesOf(name, env).map((file) => join(folder, file)));
6101
+ for (const file of candidates) if (await isExecutable(file)) return file;
6210
6102
  }
6211
- function optionField(key, schema) {
6212
- const described = describe(schema);
6213
- const name = kebab(key);
6214
- const isBoolean = described.type === "boolean";
6215
- const label = isBoolean && described.defaultValue === true ? `--no-${name}` : `--${name}`;
6216
- const value = `<${described.choices?.join("|") ?? "value"}>${described.variadic ? "..." : ""}`;
6217
- const usage = isBoolean ? label : `${label} ${value}`;
6218
- return {
6219
- key,
6220
- name,
6221
- schema,
6222
- ...described,
6223
- label,
6224
- usage
6225
- };
6103
+ /** Fails with `TOOL_MISSING`, naming every missing tool at once, unless all are on `PATH`. */
6104
+ async function checkTools(tools, env, rerun) {
6105
+ const found = await Promise.all(tools.map((tool) => findTool(tool.name, env)));
6106
+ const missing = tools.filter((_tool, index) => found[index] === void 0);
6107
+ if (missing.length === 0) return;
6108
+ const names = missing.map(({ name }) => name).join(", ");
6109
+ throw runtimeError({
6110
+ code: TOOL_MISSING,
6111
+ message: `${missing.length === 1 ? "a tool it runs is" : "tools it runs are"} not on PATH: ${names}`,
6112
+ hint: "run the Next steps in order: each install, then the command again",
6113
+ next: [...missing.map(({ install }) => wholeLine(install)), rerun]
6114
+ });
6226
6115
  }
6227
- function fieldsOf(schema, toField) {
6228
- return schema === void 0 ? [] : Object.entries(schema.shape).map(([key, value]) => toField(key, value));
6116
+ /** One `doctor` row per distinct tool the CLI and its Commands declare, with its install command as the fix. */
6117
+ function toolRows(tools, commands) {
6118
+ return declaredIn(tools, commands, (command) => command.tools).map(({ name, install }) => ({
6119
+ name,
6120
+ run: async (ctx) => {
6121
+ const file = await findTool(name, ctx.env);
6122
+ return file === void 0 ? {
6123
+ state: "problem",
6124
+ message: `${name} is not on PATH`,
6125
+ fix: install
6126
+ } : {
6127
+ state: "ok",
6128
+ message: `found at ${file}`
6129
+ };
6130
+ }
6131
+ }));
6229
6132
  }
6230
- function argFields(schema) {
6231
- return fieldsOf(schema, argField);
6133
+ //#endregion
6134
+ //#region ../agent-cli/dist/context/spawn.js
6135
+ const TOOL_FAILED = "TOOL_FAILED";
6136
+ const STDERR_TAIL_LINES$1 = 20;
6137
+ const SIGNAL_EXIT_BASE = 128;
6138
+ const BATCH_FILE = /\.(?:cmd|bat)$/i;
6139
+ const CMD_SHIM = /\.cmd$/i;
6140
+ const CMD_META = /([()\][%!^"`<>&|;, *?])/g;
6141
+ const PATH_SEPARATOR = /[\\/]/;
6142
+ const TRAILING_CR = /\r$/;
6143
+ const LINE_END = /\r\n|\r(?!$)|\n/;
6144
+ function isWhole(input) {
6145
+ return typeof input === "string" || input instanceof Uint8Array;
6232
6146
  }
6233
- function keysOf(fields) {
6234
- return fields.map(({ key }) => `'${key}'`).join(", ");
6147
+ function escapedForCmd(text) {
6148
+ return text.replace(CMD_META, "^$1");
6235
6149
  }
6236
- /** Throws when an arg that takes every remaining operand is not the last arg. */
6237
- function checkArrayArgLast(name, args) {
6238
- const misplaced = argFields(args).slice(0, -1).filter((field) => field.variadic);
6239
- 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`);
6150
+ function batchArgument(arg, shim) {
6151
+ const escaped = escapedForCmd(`"${arg.replace(/(\\*)"/g, "$1$1\\\"").replace(/(\\*)$/, "$1$1")}"`);
6152
+ return shim ? escapedForCmd(escaped) : escaped;
6240
6153
  }
6241
- /** Throws unless at most one arg, and no option, is marked with `orStdin`. */
6242
- function checkStdinFields(name, args, options) {
6243
- const stdinArgs = argFields(args).filter((field) => field.stdin);
6244
- if (stdinArgs.length > 1) throw new Error(`Command '${name}' marks args ${keysOf(stdinArgs)} with orStdin; only one arg may read stdin`);
6245
- const stdinOptions = optionFields(options).filter((field) => field.stdin);
6246
- if (stdinOptions.length > 0) {
6247
- const noun = stdinOptions.length === 1 ? "option" : "options";
6248
- throw new Error(`Command '${name}' marks ${noun} ${keysOf(stdinOptions)} with orStdin; only an arg may read stdin`);
6154
+ function batchCommandLine(command, file, args) {
6155
+ const shim = CMD_SHIM.test(file);
6156
+ return [escapedForCmd(command), ...args.map((arg) => batchArgument(arg, shim))].join(" ");
6157
+ }
6158
+ async function windowsLaunch(command, args, env) {
6159
+ const file = PATH_SEPARATOR.test(command) ? command : await findTool(command, env);
6160
+ if (file === void 0 || !BATCH_FILE.test(file)) return {
6161
+ command,
6162
+ args,
6163
+ shell: false
6164
+ };
6165
+ return {
6166
+ command: batchCommandLine(command, file, args),
6167
+ args: [],
6168
+ shell: true
6169
+ };
6170
+ }
6171
+ function stop(child) {
6172
+ if (process.platform !== "win32" || child.pid === void 0) {
6173
+ child.kill();
6174
+ return Promise.resolve();
6249
6175
  }
6176
+ const pid = String(child.pid);
6177
+ return new Promise((resolve) => {
6178
+ const taskkill = spawn("taskkill", [
6179
+ "/pid",
6180
+ pid,
6181
+ "/t",
6182
+ "/f"
6183
+ ], {
6184
+ stdio: "ignore",
6185
+ windowsHide: true
6186
+ });
6187
+ taskkill.once("error", () => {
6188
+ child.kill();
6189
+ resolve();
6190
+ });
6191
+ taskkill.once("exit", () => resolve());
6192
+ });
6250
6193
  }
6251
- function optionFields(schema) {
6252
- return fieldsOf(schema, optionField);
6194
+ /** Calls `onLine` with each complete line pushed, and with the unterminated rest at the end. */
6195
+ function lineSplitter(onLine) {
6196
+ let rest = "";
6197
+ return {
6198
+ push(text) {
6199
+ if (onLine === void 0) return;
6200
+ const lines = (rest + text).split(LINE_END);
6201
+ rest = lines.pop() ?? "";
6202
+ for (const line of lines) onLine(line);
6203
+ },
6204
+ end() {
6205
+ if (rest !== "") onLine?.(rest.replace(TRAILING_CR, ""));
6206
+ rest = "";
6207
+ }
6208
+ };
6253
6209
  }
6254
- /** Throws when an option is an array of booleans, which a switch cannot express. */
6255
- function checkNoBooleanArrayOption(name, options) {
6256
- const booleans = optionFields(options).filter((field) => field.variadic && field.type === "boolean");
6257
- if (booleans.length > 0) throw new Error(`Command '${name}' declares boolean array option ${keysOf(booleans)}; a switch cannot repeat, use a boolean`);
6210
+ /** `onLine`, passing what it throws to `fail` and called no more once `failed()`. */
6211
+ function guarded(onLine, failed, fail) {
6212
+ if (onLine === void 0) return void 0;
6213
+ return (line) => {
6214
+ if (failed()) return;
6215
+ try {
6216
+ onLine(line);
6217
+ } catch (error) {
6218
+ fail(error);
6219
+ }
6220
+ };
6258
6221
  }
6259
- //#endregion
6260
- //#region ../agent-cli/dist/command-line/usage.js
6261
- const INVALID_INPUT = "INVALID_INPUT";
6262
- const UNKNOWN_OPTION = "UNKNOWN_OPTION";
6263
- const UNKNOWN_COMMAND = "UNKNOWN_COMMAND";
6264
- function unknownError({ code, problem, commandLine, corrections, listed }) {
6265
- return new UsageError({
6266
- code,
6267
- message: `${problem} for \`${commandLine}\``,
6268
- hint: corrections.length === 0 ? `run \`${commandLine} --help\` to see the ${listed}` : `did you mean ${corrections.map((correction) => `\`${correction}\``).join(" or ")}?`
6222
+ /** Settles once the child has taken what was written, or its stdin has closed. */
6223
+ function drained(stdin) {
6224
+ return new Promise((resolve) => {
6225
+ const done = () => {
6226
+ stdin.off("drain", done);
6227
+ stdin.off("close", done);
6228
+ resolve();
6229
+ };
6230
+ stdin.on("drain", done);
6231
+ stdin.on("close", done);
6269
6232
  });
6270
6233
  }
6271
- function unknownOptionError(commandLine, invocation, flag, suggestions) {
6272
- return unknownError({
6273
- code: UNKNOWN_OPTION,
6274
- problem: `unknown option '${flag}'`,
6275
- commandLine,
6276
- corrections: suggestions.map((name) => commandLineRenaming(invocation, /* @__PURE__ */ new Map([[flag, name]]))),
6277
- listed: "options"
6234
+ /**
6235
+ * Writes `input` to the child's stdin and ends it, stopping once the child is gone or the run
6236
+ * aborts.
6237
+ */
6238
+ async function feed(stdin, input, signal) {
6239
+ stdin.on("error", () => void 0);
6240
+ const chunks = isWhole(input) ? [input] : input;
6241
+ for await (const chunk of chunks) {
6242
+ if (signal.aborted || stdin.destroyed) return;
6243
+ if (!stdin.write(chunk)) await drained(stdin);
6244
+ }
6245
+ stdin.end();
6246
+ }
6247
+ /**
6248
+ * Runs a child process to its end; it is killed on abort, and the promise settles only once it has
6249
+ * exited. A `detached` child leaves the terminal's process group on POSIX, and so its Ctrl-C.
6250
+ */
6251
+ async function spawnChild(command, args, { cwd, env, signal, onLine, stdin, detached = false }) {
6252
+ signal.throwIfAborted();
6253
+ const launch = await launchOf(command, args, env);
6254
+ signal.throwIfAborted();
6255
+ return new Promise((resolve, reject) => {
6256
+ const child = spawn(launch.command, launch.args, {
6257
+ cwd,
6258
+ env,
6259
+ shell: launch.shell,
6260
+ windowsHide: true,
6261
+ stdio: [
6262
+ stdin === void 0 ? "ignore" : "pipe",
6263
+ "pipe",
6264
+ "pipe"
6265
+ ],
6266
+ detached: detached && process.platform !== "win32"
6267
+ });
6268
+ let stopped = Promise.resolve();
6269
+ const kill = () => {
6270
+ stopped = stop(child);
6271
+ };
6272
+ signal.addEventListener("abort", kill, { once: true });
6273
+ let callerFailure;
6274
+ const fail = (error) => {
6275
+ if (callerFailure !== void 0) return;
6276
+ callerFailure = { error };
6277
+ kill();
6278
+ };
6279
+ let stdout = "";
6280
+ let stderr = "";
6281
+ const lines = guarded(onLine, () => callerFailure !== void 0, fail);
6282
+ const stdoutLines = lineSplitter(lines);
6283
+ const stderrLines = lineSplitter(lines);
6284
+ child.stdout?.setEncoding("utf8").on("data", (chunk) => {
6285
+ stdout += chunk;
6286
+ stdoutLines.push(chunk);
6287
+ });
6288
+ child.stderr?.setEncoding("utf8").on("data", (chunk) => {
6289
+ stderr += chunk;
6290
+ stderrLines.push(chunk);
6291
+ });
6292
+ if (stdin !== void 0 && child.stdin !== null) feed(child.stdin, stdin, signal).catch(fail);
6293
+ child.on("error", (error) => {
6294
+ signal.removeEventListener("abort", kill);
6295
+ reject(error);
6296
+ });
6297
+ child.on("close", (code, killedBy) => {
6298
+ signal.removeEventListener("abort", kill);
6299
+ if (signal.aborted) {
6300
+ stopped.then(() => reject(signal.reason));
6301
+ return;
6302
+ }
6303
+ stdoutLines.end();
6304
+ stderrLines.end();
6305
+ if (callerFailure !== void 0) {
6306
+ const { error } = callerFailure;
6307
+ stopped.then(() => reject(error));
6308
+ return;
6309
+ }
6310
+ resolve({
6311
+ stdout,
6312
+ stderr,
6313
+ exitCode: code ?? SIGNAL_EXIT_BASE + (killedBy === null ? 0 : constants.signals[killedBy])
6314
+ });
6315
+ });
6278
6316
  });
6279
6317
  }
6280
- /** Text opening with `-` was read as an option, where the Command could have read it from stdin. */
6281
- function stdinTextError(commandLine, invocation, word) {
6282
- const withStdin = commandLineOf(invocation, {
6283
- remove: [word],
6284
- add: ["-"]
6318
+ async function launchOf(command, args, env) {
6319
+ return process.platform === "win32" ? windowsLaunch(command, args, env) : {
6320
+ command,
6321
+ args,
6322
+ shell: false
6323
+ };
6324
+ }
6325
+ /**
6326
+ * Starts a child in its own process group (its own console on Windows) with no stdio, and settles
6327
+ * once it has started; the parent neither waits for it nor keeps it from exiting.
6328
+ */
6329
+ const realSpawnDetached = async (command, args, { cwd, env }) => {
6330
+ const launch = await launchOf(command, args, env);
6331
+ return new Promise((resolve, reject) => {
6332
+ const child = spawn(launch.command, launch.args, {
6333
+ cwd,
6334
+ env,
6335
+ shell: launch.shell,
6336
+ windowsHide: true,
6337
+ stdio: "ignore",
6338
+ detached: true
6339
+ });
6340
+ child.unref();
6341
+ child.once("error", reject);
6342
+ child.once("spawn", () => {
6343
+ child.off("error", reject);
6344
+ const { pid } = child;
6345
+ if (pid === void 0) reject(/* @__PURE__ */ new Error(`${command} started without a pid`));
6346
+ else resolve({ pid });
6347
+ });
6285
6348
  });
6286
- return new UsageError({
6287
- code: UNKNOWN_OPTION,
6288
- message: `unknown option '${word}' for \`${commandLine}\``,
6289
- hint: `to pass text opening with \`-\`, pass \`-\` and send the text on stdin through a heredoc: \`${withStdin} <<'EOF'\`, then the text, then \`EOF\``
6349
+ };
6350
+ const realSpawn = (command, args, options) => spawnChild(command, args, options);
6351
+ function failedError(command, { exitCode, stderr }) {
6352
+ const tail = lastLines(stderr, STDERR_TAIL_LINES$1);
6353
+ return new CliError({
6354
+ code: TOOL_FAILED,
6355
+ message: tail === "" ? `${command} exited with ${exitCode} and wrote nothing to stderr` : `${command} exited with ${exitCode}; its stderr ends:\n${tail}`,
6356
+ hint: `the output of ${command} above says why it failed`
6290
6357
  });
6291
6358
  }
6292
- function unknownCommandError(commandLine, name, suggestions) {
6293
- return unknownError({
6294
- code: UNKNOWN_COMMAND,
6295
- problem: `unknown command '${name}'`,
6296
- commandLine,
6297
- corrections: suggestions.map((suggestion) => `${commandLine} ${suggestion}`),
6298
- listed: "commands"
6359
+ var MissingToolError = class extends CliError {
6360
+ tool;
6361
+ constructor(tool, rerun, cause) {
6362
+ super({
6363
+ code: TOOL_MISSING,
6364
+ message: `${tool} could not be started: it is not on PATH`,
6365
+ hint: `install ${tool}, then run the Next step`,
6366
+ next: [rerun]
6367
+ }, { cause });
6368
+ this.tool = tool;
6369
+ }
6370
+ };
6371
+ function missingToolOf(error) {
6372
+ return error instanceof MissingToolError ? error.tool : void 0;
6373
+ }
6374
+ function startError(command, error, { fileError, rerun }) {
6375
+ if (isMissingFile(error)) return new MissingToolError(command, rerun, error);
6376
+ return fileError(error, {
6377
+ path: command,
6378
+ access: "read"
6299
6379
  });
6300
6380
  }
6301
- function invalidInputError(commandLine, problem) {
6302
- return new UsageError({
6303
- code: INVALID_INPUT,
6304
- message: problem,
6305
- hint: `run \`${commandLine} --help\` to see the expected input`
6306
- });
6307
- }
6308
- //#endregion
6309
- //#region ../agent-cli/dist/list.js
6310
- const UNKNOWN_FIELD = "UNKNOWN_FIELD";
6311
- const DEFAULT_PAGE_LIMIT = 20;
6312
- const FIELDS_FLAG = "--fields";
6313
- const SEPARATOR = ",";
6314
- const COLUMN_SEPARATOR = " ";
6315
- const UNSAFE_IN_COLUMN = /[\t\n\r]/;
6316
- const PAGE_INPUT = object({
6317
- limit: number().int().positive().default(DEFAULT_PAGE_LIMIT),
6318
- cursor: string().min(1).optional()
6319
- });
6320
- var Page = class {
6321
- items;
6322
- nextCursor;
6323
- constructor(items, nextCursor) {
6324
- this.items = items;
6325
- this.nextCursor = nextCursor;
6381
+ async function* reported(stream, onError) {
6382
+ try {
6383
+ yield* stream;
6384
+ } catch (error) {
6385
+ onError(error);
6386
+ throw error;
6326
6387
  }
6327
- };
6328
- const PAGE_FLAG_DESCRIPTIONS = {
6329
- limit: `how many rows to show, a positive whole number (default: ${DEFAULT_PAGE_LIMIT})`,
6330
- cursor: "where to start, as given by the previous page's next"
6331
- };
6332
- function pageFlag(key) {
6333
- const field = optionField(key, PAGE_INPUT.shape[key]);
6334
- return {
6335
- flag: field.label,
6336
- usage: field.usage,
6337
- description: PAGE_FLAG_DESCRIPTIONS[key],
6338
- takes: "optional value",
6339
- schema: field.schema
6340
- };
6341
6388
  }
6342
- function fieldsFlag(rowFields) {
6343
- return {
6344
- flag: FIELDS_FLAG,
6345
- usage: `${FIELDS_FLAG} <a,b>`,
6346
- description: `print only these comma-separated fields of each row (any of: ${rowFields.join(", ")})`,
6347
- takes: "optional value",
6348
- schema: string()
6389
+ /** `input`, a stream passing each error it throws to `onError` before the reader sees it. */
6390
+ function reporting(input, onError) {
6391
+ return input === void 0 || isWhole(input) ? input : reported(input, onError);
6392
+ }
6393
+ /** `onLine`, passing each error it throws to `onError` before its caller sees it. */
6394
+ function reportingLines(onLine, onError) {
6395
+ if (onLine === void 0) return void 0;
6396
+ return (line) => {
6397
+ try {
6398
+ onLine(line);
6399
+ } catch (error) {
6400
+ onError(error);
6401
+ throw error;
6402
+ }
6349
6403
  };
6350
6404
  }
6351
- const PAGE_FIELDS = optionFields(PAGE_INPUT);
6352
- const PAGE_FLAGS = {
6353
- limit: pageFlag("limit"),
6354
- cursor: pageFlag("cursor")
6355
- };
6356
- /** The Standard flags every List Command gets, as the caller types them. */
6357
- const LIST_FLAGS = [...Object.values(PAGE_FLAGS).map(({ flag }) => flag), FIELDS_FLAG];
6358
- function listSpec(rows) {
6359
- const rowFields = Object.keys(rows.shape);
6360
- return {
6361
- rowFields,
6362
- pageFields: PAGE_FIELDS,
6363
- standardFlags: [...Object.values(PAGE_FLAGS), fieldsFlag(rowFields)],
6364
- output: object({
6365
- items: array(rows),
6366
- next: string().optional()
6367
- })
6405
+ function contextSpawn(run, defaults, scope) {
6406
+ return async (command, args = [], options = {}) => {
6407
+ let callerFailure;
6408
+ const caller = (error) => {
6409
+ callerFailure = { error };
6410
+ };
6411
+ let result;
6412
+ try {
6413
+ result = await run(command, args, {
6414
+ cwd: options.cwd ?? defaults.cwd,
6415
+ env: options.env ?? defaults.env,
6416
+ signal: defaults.signal,
6417
+ onLine: reportingLines(options.onLine, caller),
6418
+ stdin: reporting(options.stdin, caller)
6419
+ });
6420
+ } catch (error) {
6421
+ throw callerFailure?.error === error ? error : startError(command, error, scope);
6422
+ }
6423
+ if (options.check === true && result.exitCode !== 0) throw failedError(command, result);
6424
+ return result;
6368
6425
  };
6369
6426
  }
6370
- function quoted(names) {
6371
- return names.map((name) => `'${name}'`).join(", ");
6427
+ /** `ctx.spawnDetached`: `run` bound to the run's cwd and env, its start failures classified by errno. */
6428
+ function contextSpawnDetached(run, defaults, scope) {
6429
+ return async (command, args = [], options = {}) => {
6430
+ defaults.signal.throwIfAborted();
6431
+ try {
6432
+ return await run(command, args, {
6433
+ cwd: options.cwd ?? defaults.cwd,
6434
+ env: options.env ?? defaults.env
6435
+ });
6436
+ } catch (error) {
6437
+ throw startError(command, error, scope);
6438
+ }
6439
+ };
6372
6440
  }
6373
- /** The fields the caller asked for, in their order, or undefined when they gave no `--fields`. */
6374
- function selectedFields(values, rowFields, commandLine) {
6375
- const raw = values.get(FIELDS_FLAG);
6376
- if (raw === void 0) return;
6377
- const hint = `pass ${FIELDS_FLAG} with any of: ${rowFields.join(", ")}`;
6378
- const names = typeof raw === "string" ? raw.split(SEPARATOR).filter((name) => name !== "") : [];
6379
- if (names.length === 0) throw new UsageError({
6380
- code: INVALID_INPUT,
6381
- message: `${FIELDS_FLAG}: missing value for \`${commandLine}\``,
6382
- hint
6383
- });
6384
- const unknown = names.filter((name) => !rowFields.includes(name));
6385
- if (unknown.length > 0) throw new UsageError({
6386
- code: UNKNOWN_FIELD,
6387
- message: `unknown field ${quoted(unknown)} for \`${commandLine}\``,
6388
- hint
6441
+ //#endregion
6442
+ //#region ../agent-cli/dist/clock.js
6443
+ const realClock = {
6444
+ now: () => Date.now(),
6445
+ sleep: (ms, signal) => new Promise((resolve, reject) => {
6446
+ if (signal?.aborted === true) {
6447
+ reject(signal.reason);
6448
+ return;
6449
+ }
6450
+ const onAbort = () => {
6451
+ clearTimeout(timer);
6452
+ reject(signal?.reason);
6453
+ };
6454
+ const timer = setTimeout(() => {
6455
+ signal?.removeEventListener("abort", onAbort);
6456
+ resolve();
6457
+ }, ms);
6458
+ signal?.addEventListener("abort", onAbort, { once: true });
6459
+ })
6460
+ };
6461
+ /** Runs `then` once `ms` have passed on `clock`; aborting `cancel` drops it. */
6462
+ function after(clock, ms, cancel, then) {
6463
+ clock.sleep(ms, cancel).then(then, (error) => {
6464
+ if (!cancel.aborted) throw error;
6389
6465
  });
6390
- return names;
6391
- }
6392
- function fieldOf(row, field) {
6393
- return typeof row === "object" && row !== null ? row[field] : void 0;
6394
- }
6395
- function projected(rows, fields) {
6396
- return rows.map((row) => Object.fromEntries(fields.map((field) => [field, fieldOf(row, field)])));
6397
- }
6398
- function cell(value) {
6399
- if (value === void 0 || value === null) return "";
6400
- return typeof value === "string" && !UNSAFE_IN_COLUMN.test(value) ? value : JSON.stringify(value);
6401
- }
6402
- /** Selected rows as text: a header line of the field names, then one tab-separated line per row. */
6403
- function renderRows(rows, fields) {
6404
- return `${[fields, ...rows.map((row) => fields.map((field) => cell(row[field])))].map((line) => line.join(COLUMN_SEPARATOR)).join("\n")}\n`;
6405
6466
  }
6406
- function cursorWords(cursor) {
6407
- const { flag } = PAGE_FLAGS.cursor;
6408
- return cursor.startsWith("-") ? [`${flag}=${cursor}`] : [flag, cursor];
6467
+ async function poll(clock, check, intervalMs, signal) {
6468
+ for (;;) {
6469
+ signal.throwIfAborted();
6470
+ const value = await check();
6471
+ if (value !== void 0) return value;
6472
+ await clock.sleep(intervalMs, signal);
6473
+ }
6409
6474
  }
6410
- function printedPage(page, invocation) {
6411
- return page.nextCursor === void 0 ? { items: page.items } : {
6412
- items: page.items,
6413
- next: lineOf({
6414
- kind: "rerun",
6415
- words: changedArgv(invocation, {
6416
- remove: [PAGE_FLAGS.cursor.flag],
6417
- add: cursorWords(page.nextCursor)
6418
- }),
6419
- stdinArg: invocation.stdinArg
6420
- }, invocation.runAs)
6421
- };
6475
+ //#endregion
6476
+ //#region ../agent-cli/dist/context/stdin.js
6477
+ const STDIN_META = "stdin";
6478
+ function isStdinSchema(schema) {
6479
+ return globalRegistry$1.get(schema)?.[STDIN_META] === true;
6422
6480
  }
6423
- /** The lines that end a rendered page, so the caller always knows whether and how to get more. */
6424
- function pageFooter({ items, next }, style) {
6425
- return next === void 0 ? `${items.length} shown; no more pages\n` : `${items.length} shown; run the Next step for the next page\n${style.cyan("run:")} ${next}\n`;
6481
+ function endOfInputKeys(platform) {
6482
+ return platform === "win32" ? "Ctrl-Z then Enter" : "Ctrl-D";
6426
6483
  }
6427
- //#endregion
6428
- //#region ../agent-cli/dist/planned.js
6429
- /** Whether `command` changes something through a Plan: a write or destructive Command. */
6430
- function hasPlan(command) {
6431
- return command.effect === "write" || command.effect === "destructive";
6484
+ /** The progress line shown before reading a terminal, so a person isn't left facing silence. */
6485
+ function readingLine(name, platform) {
6486
+ return `reading <${name}> from stdin; end it with ${endOfInputKeys(platform)}`;
6432
6487
  }
6433
- //#endregion
6434
- //#region ../agent-cli/dist/command-line/flags.js
6435
- const HELP_FLAG = "--help";
6436
- const VERSION_FLAG = "--version";
6437
- const ROUTING_FLAGS = [
6438
- HELP_FLAG,
6439
- "-h",
6440
- VERSION_FLAG
6441
- ];
6442
- function nameOf$1(flag) {
6443
- return flag.replace(/^--?/, "");
6488
+ function isTerminal(source) {
6489
+ return typeof source !== "string" && "isTTY" in source && source.isTTY === true;
6444
6490
  }
6445
- function standardFlag(flag, { placeholder, ...rest }) {
6446
- return {
6447
- flag,
6448
- usage: placeholder === void 0 ? flag : `${flag} ${placeholder}`,
6449
- ...rest
6450
- };
6491
+ const STDIN_STALLED = "STDIN_STALLED";
6492
+ /** How long a stdin that is not a terminal may send nothing, from the start of the read and from each chunk. */
6493
+ const STDIN_IDLE_MS = 3e4;
6494
+ function stdinStalled(callerLine) {
6495
+ return new CliError({
6496
+ code: STDIN_STALLED,
6497
+ message: `stdin sent nothing for ${STDIN_IDLE_MS} ms`,
6498
+ hint: `stdin never ended: close the pipe or heredoc that feeds \`${callerLine}\`, pass the text as an argument instead, or write a slow producer's output to a file and redirect it in`
6499
+ });
6451
6500
  }
6452
- const JSON_FLAG = standardFlag("--json", {
6453
- description: "print the result as JSON",
6454
- takes: "nothing",
6455
- schema: boolean()
6456
- });
6457
- const DRY_RUN_FLAG = standardFlag("--dry-run", {
6458
- description: "print the Plan without changing anything",
6459
- takes: "nothing",
6460
- schema: boolean()
6461
- });
6462
- const CONFIRM_FLAG = standardFlag("--confirm", {
6463
- placeholder: "<target>",
6464
- description: "name the target to confirm the change; an error hint gives the exact command",
6465
- takes: "value",
6466
- schema: string()
6467
- });
6468
- const YES_FLAG = standardFlag("--yes", {
6469
- description: "approve the cost the run states; --dry-run shows it",
6470
- takes: "nothing",
6471
- schema: boolean()
6472
- });
6473
- const FORCE_FLAG = standardFlag("--force", {
6474
- description: "replace a file the run writes that already exists; --dry-run lists every file it writes",
6475
- takes: "nothing",
6476
- schema: boolean()
6477
- });
6478
- const EFFECT_FLAGS = {
6479
- read: [JSON_FLAG],
6480
- act: [JSON_FLAG],
6481
- write: [JSON_FLAG, DRY_RUN_FLAG],
6482
- destructive: [
6483
- JSON_FLAG,
6484
- DRY_RUN_FLAG,
6485
- CONFIRM_FLAG
6486
- ],
6487
- serve: [JSON_FLAG]
6488
- };
6489
- const RESERVED = new Map([
6490
- ...ROUTING_FLAGS,
6491
- ...EFFECT_FLAGS.destructive.map(({ flag }) => flag),
6492
- YES_FLAG.flag,
6493
- FORCE_FLAG.flag,
6494
- ...LIST_FLAGS
6495
- ].map((flag) => [nameOf$1(flag), flag]));
6496
- /** The Standard flags of a Command with this Effect and declarations, and of a List Command. */
6497
- function standardFlagsFor(effect, { outputs, force, cost }, list) {
6498
- const forceFlag = force === void 0 ? FORCE_FLAG : {
6499
- ...FORCE_FLAG,
6500
- description: force
6501
- };
6502
- return [
6503
- ...EFFECT_FLAGS[effect],
6504
- ...outputs || force !== void 0 ? [forceFlag] : [],
6505
- ...cost ? [YES_FLAG] : [],
6506
- ...list?.standardFlags ?? []
6507
- ];
6508
- }
6509
- /** Throws when the Command declares an arg or option under a Standard flag's name. */
6510
- function checkNoStandardFlags(command, label) {
6511
- const lines = [...argFields(command.args).map(({ key }) => ({
6512
- kind: "arg",
6513
- key
6514
- })), ...optionFields(command.options).map(({ key }) => ({
6515
- kind: "option",
6516
- key
6517
- }))].flatMap(({ kind, key }) => {
6518
- const flag = RESERVED.get(kebab(key));
6519
- return flag === void 0 ? [] : [` ${kind} '${key}' is the Standard flag ${flag}`];
6501
+ /**
6502
+ * The whole of `source` as UTF-8; rejects with the signal's reason once it aborts, and with
6503
+ * `idle.stalled()` once a stream that is not a terminal sends nothing for `STDIN_IDLE_MS`.
6504
+ */
6505
+ function readStdin(source, signal, idle) {
6506
+ if (typeof source === "string") return signal.aborted ? Promise.reject(signal.reason) : Promise.resolve(source);
6507
+ return new Promise((resolve, reject) => {
6508
+ const chunks = [];
6509
+ let idleWindow = new AbortController();
6510
+ const watchIdle = () => {
6511
+ idleWindow.abort();
6512
+ idleWindow = new AbortController();
6513
+ if (isTerminal(source)) return;
6514
+ after(idle.clock, STDIN_IDLE_MS, idleWindow.signal, () => {
6515
+ stop();
6516
+ reject(idle.stalled());
6517
+ });
6518
+ };
6519
+ const onData = (chunk) => {
6520
+ chunks.push(chunk instanceof Uint8Array ? chunk : Buffer.from(String(chunk)));
6521
+ watchIdle();
6522
+ };
6523
+ const onEnd = () => {
6524
+ stop();
6525
+ resolve(Buffer.concat(chunks).toString("utf8"));
6526
+ };
6527
+ const onError = (error) => {
6528
+ stop();
6529
+ reject(error);
6530
+ };
6531
+ const onAbort = () => {
6532
+ stop();
6533
+ reject(signal.reason);
6534
+ };
6535
+ const stop = () => {
6536
+ source.off("data", onData).off("end", onEnd).off("error", onError).pause();
6537
+ signal.removeEventListener("abort", onAbort);
6538
+ idleWindow.abort();
6539
+ };
6540
+ if (signal.aborted) {
6541
+ reject(signal.reason);
6542
+ return;
6543
+ }
6544
+ signal.addEventListener("abort", onAbort, { once: true });
6545
+ watchIdle();
6546
+ source.on("data", onData).once("end", onEnd).once("error", onError);
6520
6547
  });
6521
- if (lines.length > 0) throw new Error([`Command '${label}' declares Standard flags the Runtime adds; rename them:`, ...lines].join("\n"));
6522
- }
6523
- /** Throws when `force` is declared on a Command with no Plan, or says nothing. */
6524
- function checkForce(command, label) {
6525
- const force = "force" in command ? command.force : void 0;
6526
- if (force === void 0) return;
6527
- if (!hasPlan(command)) throw new Error(`Command '${label}' declares force, which only a write or destructive Command may`);
6528
- if (force.trim() === "") throw new Error(`Command '${label}' declares an empty force; say what ${FORCE_FLAG.flag} replaces`);
6529
6548
  }
6530
6549
  //#endregion
6531
- //#region ../agent-cli/dist/define-command.js
6532
- /** Throws when the Command's declaration breaks a rule its type cannot express; `label` names it. */
6533
- function checkCommand(command, label, table) {
6534
- checkNoStandardFlags(command, label);
6535
- checkForce(command, label);
6536
- checkArrayArgLast(label, command.args);
6537
- checkStdinFields(label, command.args, command.options);
6538
- checkNoBooleanArrayOption(label, command.options);
6539
- checkErrors(command, label, table);
6540
- }
6541
- function checkErrors(command, label, table) {
6542
- const unknown = (command.errors ?? []).filter((code) => !Object.hasOwn(table, code));
6543
- if (unknown.length > 0) throw new Error(`${label} declares ${unknown.join(", ")} in errors, a code the Error table lacks`);
6544
- }
6545
- function defineCommandFor() {
6546
- return declared;
6550
+ //#region ../agent-cli/dist/command-line/input.js
6551
+ function isWrapper(schema) {
6552
+ return schema instanceof ZodOptional || schema instanceof ZodNullable || schema instanceof ZodDefault || schema instanceof ZodPrefault;
6547
6553
  }
6548
- function declared(command) {
6549
- return command;
6554
+ function kebab(key) {
6555
+ return key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
6550
6556
  }
6551
- const defineCommand$1 = defineCommandFor();
6552
- //#endregion
6553
- //#region ../agent-cli/dist/context/project.js
6554
- const PROJECT_INVALID = "PROJECT_INVALID";
6555
- function invalid(path, problem, rerun, options) {
6556
- return new CliError({
6557
- code: PROJECT_INVALID,
6558
- message: `the Project file ${path} is invalid: ${problem}`,
6559
- hint: `fix ${path}, then run the Next step`,
6560
- next: [rerun]
6561
- }, options);
6557
+ function typeOf(schema) {
6558
+ if (schema instanceof ZodNumber) return "number";
6559
+ return schema instanceof ZodBoolean ? "boolean" : "string";
6562
6560
  }
6563
- function readText(path, fileError) {
6564
- try {
6565
- return readFileSync(path, "utf8");
6566
- } catch (error) {
6567
- if (isMissingFile(error)) return void 0;
6568
- throw fileError(error, {
6569
- path,
6570
- access: "read"
6571
- });
6561
+ function describe(schema) {
6562
+ let current = schema;
6563
+ let description = current.description;
6564
+ let required = true;
6565
+ let defaultValue;
6566
+ let stdin = isStdinSchema(current);
6567
+ while (isWrapper(current)) {
6568
+ if (current instanceof ZodDefault || current instanceof ZodPrefault) defaultValue = current.def.defaultValue;
6569
+ required = false;
6570
+ current = current.unwrap();
6571
+ description ??= current.description;
6572
+ stdin ||= isStdinSchema(current);
6572
6573
  }
6574
+ const variadic = current instanceof ZodArray;
6575
+ if (current instanceof ZodArray) current = current.element;
6576
+ return {
6577
+ type: typeOf(current),
6578
+ required,
6579
+ defaultValue,
6580
+ description,
6581
+ choices: current instanceof ZodEnum ? current.options.map(String) : void 0,
6582
+ variadic,
6583
+ stdin
6584
+ };
6573
6585
  }
6574
- function parsed(path, text, schema, rerun) {
6575
- let json;
6576
- try {
6577
- json = JSON.parse(text);
6578
- } catch (error) {
6579
- throw invalid(path, messageOf(error), rerun, { cause: error });
6580
- }
6581
- const result = schema.safeParse(json);
6582
- if (!result.success) throw invalid(path, prettifyError(result.error), rerun);
6583
- return result.data;
6586
+ function argLabel(key, { variadic, required, stdin }) {
6587
+ if (stdin) return required ? `<${key}|->` : `[${key}|-]`;
6588
+ if (!variadic) return `<${key}>`;
6589
+ return required ? `<${key}...>` : `[${key}...]`;
6584
6590
  }
6585
- function projectFrom(projectFile, folder, scope) {
6586
- const path = join(folder, projectFile.file);
6587
- const text = readText(path, scope.fileError);
6588
- if (text !== void 0) return {
6589
- root: folder,
6590
- settings: parsed(path, text, projectFile.schema, scope.rerun)
6591
+ function argField(key, schema) {
6592
+ const described = describe(schema);
6593
+ const label = argLabel(key, described);
6594
+ return {
6595
+ key,
6596
+ name: key,
6597
+ schema,
6598
+ ...described,
6599
+ label,
6600
+ usage: label
6591
6601
  };
6592
- const parent = dirname(folder);
6593
- return parent === folder ? void 0 : projectFrom(projectFile, parent, scope);
6594
6602
  }
6595
- /** The nearest Project file at or above `cwd`, parsed by its schema; undefined when there is none. */
6596
- function projectOf(projectFile, cwd, scope) {
6597
- return projectFrom(projectFile, resolve(cwd), scope);
6603
+ function optionField(key, schema) {
6604
+ const described = describe(schema);
6605
+ const name = kebab(key);
6606
+ const isBoolean = described.type === "boolean";
6607
+ const label = isBoolean && described.defaultValue === true ? `--no-${name}` : `--${name}`;
6608
+ const value = `<${described.choices?.join("|") ?? "value"}>${described.variadic ? "..." : ""}`;
6609
+ const usage = isBoolean ? label : `${label} ${value}`;
6610
+ return {
6611
+ key,
6612
+ name,
6613
+ schema,
6614
+ ...described,
6615
+ label,
6616
+ usage
6617
+ };
6598
6618
  }
6599
- //#endregion
6600
- //#region ../agent-cli/dist/define.js
6601
- function isCommand(node) {
6602
- return typeof node.effect === "string";
6619
+ function fieldsOf(schema, toField) {
6620
+ return schema === void 0 ? [] : Object.entries(schema.shape).map(([key, value]) => toField(key, value));
6603
6621
  }
6604
- //#endregion
6605
- //#region ../agent-cli/dist/context/declared.js
6606
- /**
6607
- * What the CLI declares itself (`own`) and every Command under `commands` declares, each name once,
6608
- * for a `doctor` row: a Command requires only its own, but `doctor` checks them all.
6609
- */
6610
- function declaredIn(own, commands, needsOf) {
6611
- const walk = (node) => isCommand(node) ? [...needsOf(node) ?? []] : Object.values(node).flatMap(walk);
6612
- return [...new Map([...own, ...walk(commands)].map((need) => [need.name, need])).values()];
6622
+ function argFields(schema) {
6623
+ return fieldsOf(schema, argField);
6613
6624
  }
6614
- //#endregion
6615
- //#region ../agent-cli/dist/context/secrets.js
6616
- const SECRET_MISSING = "SECRET_MISSING";
6617
- /** `$XDG_CONFIG_HOME/<cli>/.env`, with `~/.config` when the variable is unset or empty. */
6618
- function envFileOf(cliName, env) {
6619
- const config = env.XDG_CONFIG_HOME || join(env.HOME || homedir(), ".config");
6620
- return join(config, cliName, ".env");
6625
+ function keysOf(fields) {
6626
+ return fields.map(({ key }) => `'${key}'`).join(", ");
6621
6627
  }
6622
- function readDotenv(path, fileError) {
6623
- let text;
6624
- try {
6625
- text = readFileSync(path, "utf8");
6626
- } catch (error) {
6627
- if (isMissingFile(error)) return {};
6628
- throw fileError(error, {
6629
- path,
6630
- access: "read"
6631
- });
6628
+ /** Throws when an arg that takes every remaining operand is not the last arg. */
6629
+ function checkArrayArgLast(name, args) {
6630
+ const misplaced = argFields(args).slice(0, -1).filter((field) => field.variadic);
6631
+ 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`);
6632
+ }
6633
+ /** Throws unless at most one arg, and no option, is marked with `orStdin`. */
6634
+ function checkStdinFields(name, args, options) {
6635
+ const stdinArgs = argFields(args).filter((field) => field.stdin);
6636
+ if (stdinArgs.length > 1) throw new Error(`Command '${name}' marks args ${keysOf(stdinArgs)} with orStdin; only one arg may read stdin`);
6637
+ const stdinOptions = optionFields(options).filter((field) => field.stdin);
6638
+ if (stdinOptions.length > 0) {
6639
+ const noun = stdinOptions.length === 1 ? "option" : "options";
6640
+ throw new Error(`Command '${name}' marks ${noun} ${keysOf(stdinOptions)} with orStdin; only an arg may read stdin`);
6632
6641
  }
6633
- return parseEnv(text);
6634
6642
  }
6635
- /** Looks `name` up in the env, then in `envFile`; an empty value counts as unset. */
6636
- function secretOf({ envFile, env, fileError }, name) {
6637
- const fromEnv = env[name];
6638
- if (fromEnv) return {
6639
- value: fromEnv,
6640
- origin: { kind: "env" }
6641
- };
6642
- const fromFile = readDotenv(envFile, fileError)[name];
6643
- return fromFile ? {
6644
- value: fromFile,
6645
- origin: {
6646
- kind: "file",
6647
- path: envFile
6648
- }
6649
- } : void 0;
6643
+ function optionFields(schema) {
6644
+ return fieldsOf(schema, optionField);
6650
6645
  }
6651
- function missingIn(scope, secrets) {
6652
- return secrets.filter(({ name }) => secretOf(scope, name) === void 0);
6646
+ /** Throws when an option is an array of booleans, which a switch cannot express. */
6647
+ function checkNoBooleanArrayOption(name, options) {
6648
+ const booleans = optionFields(options).filter((field) => field.variadic && field.type === "boolean");
6649
+ if (booleans.length > 0) throw new Error(`Command '${name}' declares boolean array option ${keysOf(booleans)}; a switch cannot repeat, use a boolean`);
6653
6650
  }
6654
- function getItFrom({ get }) {
6655
- return `(get it from ${get})`;
6651
+ //#endregion
6652
+ //#region ../agent-cli/dist/command-line/usage.js
6653
+ const INVALID_INPUT = "INVALID_INPUT";
6654
+ const UNKNOWN_OPTION = "UNKNOWN_OPTION";
6655
+ const UNKNOWN_COMMAND = "UNKNOWN_COMMAND";
6656
+ function unknownError({ code, problem, commandLine, corrections, listed }) {
6657
+ return new UsageError({
6658
+ code,
6659
+ message: `${problem} for \`${commandLine}\``,
6660
+ hint: corrections.length === 0 ? `run \`${commandLine} --help\` to see the ${listed}` : `did you mean ${corrections.map((correction) => `\`${correction}\``).join(" or ")}?`
6661
+ });
6656
6662
  }
6657
- function whereToGet(secrets) {
6658
- return secrets.map((secret) => `${secret.name} ${getItFrom(secret)}`).join(", ");
6663
+ function unknownOptionError(commandLine, invocation, flag, suggestions) {
6664
+ return unknownError({
6665
+ code: UNKNOWN_OPTION,
6666
+ problem: `unknown option '${flag}'`,
6667
+ commandLine,
6668
+ corrections: suggestions.map((name) => commandLineRenaming(invocation, /* @__PURE__ */ new Map([[flag, name]]))),
6669
+ listed: "options"
6670
+ });
6659
6671
  }
6660
- /** Fails with `SECRET_MISSING`, naming every missing Secret at once, unless all are set. */
6661
- function checkSecrets(secrets, scope, rerun) {
6662
- const missing = missingIn(scope, secrets);
6663
- if (missing.length === 0) return;
6664
- const names = missing.map(({ name }) => name).join(", ");
6665
- throw new CliError({
6666
- code: SECRET_MISSING,
6667
- message: `${missing.length === 1 ? "a Secret it needs is" : "Secrets it needs are"} not set: ${names}`,
6668
- hint: `set ${whereToGet(missing)} in the environment or in ${scope.envFile}, then run the Next step`,
6669
- next: [rerun]
6672
+ /** Text opening with `-` was read as an option, where the Command could have read it from stdin. */
6673
+ function stdinTextError(commandLine, invocation, word) {
6674
+ const withStdin = commandLineOf(invocation, {
6675
+ remove: [word],
6676
+ add: ["-"]
6677
+ });
6678
+ return new UsageError({
6679
+ code: UNKNOWN_OPTION,
6680
+ message: `unknown option '${word}' for \`${commandLine}\``,
6681
+ hint: `to pass text opening with \`-\`, pass \`-\` and send the text on stdin through a heredoc: \`${withStdin} <<'EOF'\`, then the text, then \`EOF\``
6670
6682
  });
6671
6683
  }
6672
- function originOf({ origin }) {
6673
- return origin.kind === "env" ? "the environment" : origin.path;
6684
+ function unknownCommandError(commandLine, name, suggestions) {
6685
+ return unknownError({
6686
+ code: UNKNOWN_COMMAND,
6687
+ problem: `unknown command '${name}'`,
6688
+ commandLine,
6689
+ corrections: suggestions.map((suggestion) => `${commandLine} ${suggestion}`),
6690
+ listed: "commands"
6691
+ });
6674
6692
  }
6675
- /** The `doctor` row over every distinct Secret the CLI and its Commands declare; none without any. */
6676
- function secretRows(secrets, commands) {
6677
- const declared = declaredIn(secrets, commands, (command) => command.secrets);
6678
- if (declared.length === 0) return [];
6679
- return [{
6680
- name: "secrets",
6681
- run: ({ env, envFile, fileError }) => {
6682
- const read = declared.map((declaration) => ({
6683
- declaration,
6684
- secret: secretOf({
6685
- env,
6686
- envFile,
6687
- fileError
6688
- }, declaration.name)
6689
- }));
6690
- const missing = read.filter(({ secret }) => secret === void 0);
6691
- if (missing.length > 0) return {
6692
- state: "problem",
6693
- message: `${missing.map(({ declaration }) => `${declaration.name} is not set ${getItFrom(declaration)}`).join(", ")}; add ${missing.length === 1 ? "it" : "them"} to ${envFile} or the environment`
6694
- };
6695
- return {
6696
- state: "ok",
6697
- message: read.flatMap(({ declaration, secret }) => secret === void 0 ? [] : [`${declaration.name} from ${originOf(secret)}`]).join(", ")
6698
- };
6699
- }
6700
- }];
6693
+ function invalidInputError(commandLine, problem) {
6694
+ return new UsageError({
6695
+ code: INVALID_INPUT,
6696
+ message: problem,
6697
+ hint: `run \`${commandLine} --help\` to see the expected input`
6698
+ });
6701
6699
  }
6702
6700
  //#endregion
6703
- //#region ../agent-cli/dist/context/tools.js
6704
- const TOOL_MISSING = "TOOL_MISSING";
6705
- const WINDOWS_EXTENSIONS = ".COM;.EXE;.BAT;.CMD";
6706
- function pathOf(env) {
6707
- return env[Object.keys(env).find((name) => name.toUpperCase() === "PATH") ?? "PATH"] ?? "";
6708
- }
6709
- function extensionsOf(env) {
6710
- return process.platform === "win32" ? (env.PATHEXT ?? WINDOWS_EXTENSIONS).split(";") : [""];
6711
- }
6712
- async function isExecutable(file) {
6713
- try {
6714
- await access(file, constants.X_OK);
6715
- return true;
6716
- } catch (error) {
6717
- if (isNothingThere(error) || errnoOf(error) === "EACCES") return false;
6718
- throw error;
6701
+ //#region ../agent-cli/dist/list.js
6702
+ const UNKNOWN_FIELD = "UNKNOWN_FIELD";
6703
+ const DEFAULT_PAGE_LIMIT = 20;
6704
+ const FIELDS_FLAG = "--fields";
6705
+ const SEPARATOR = ",";
6706
+ const COLUMN_SEPARATOR = " ";
6707
+ const UNSAFE_IN_COLUMN = /[\t\n\r]/;
6708
+ const PAGE_INPUT = object({
6709
+ limit: number().int().positive().default(DEFAULT_PAGE_LIMIT),
6710
+ cursor: string().min(1).optional()
6711
+ });
6712
+ var Page = class {
6713
+ items;
6714
+ nextCursor;
6715
+ constructor(items, nextCursor) {
6716
+ this.items = items;
6717
+ this.nextCursor = nextCursor;
6719
6718
  }
6719
+ };
6720
+ const PAGE_FLAG_DESCRIPTIONS = {
6721
+ limit: `how many rows to show, a positive whole number (default: ${DEFAULT_PAGE_LIMIT})`,
6722
+ cursor: "where to start, as given by the previous page's next"
6723
+ };
6724
+ function pageFlag(key) {
6725
+ const field = optionField(key, PAGE_INPUT.shape[key]);
6726
+ return {
6727
+ flag: field.label,
6728
+ usage: field.usage,
6729
+ description: PAGE_FLAG_DESCRIPTIONS[key],
6730
+ takes: "optional value",
6731
+ schema: field.schema
6732
+ };
6720
6733
  }
6721
- /** The file names `name` may have on `PATH`: itself when it already ends in one of the extensions. */
6722
- function fileNamesOf(name, env) {
6723
- const extensions = extensionsOf(env);
6724
- const own = extname(name).toUpperCase();
6725
- return own !== "" && extensions.some((extension) => extension.toUpperCase() === own) ? [name] : extensions.map((extension) => `${name}${extension}`);
6726
- }
6727
- /** Where `name` is found on `PATH` from `env`, or undefined. */
6728
- async function findTool(name, env) {
6729
- const candidates = pathOf(env).split(delimiter).filter((folder) => folder !== "").flatMap((folder) => fileNamesOf(name, env).map((file) => join(folder, file)));
6730
- for (const file of candidates) if (await isExecutable(file)) return file;
6731
- }
6732
- /** Fails with `TOOL_MISSING`, naming every missing tool at once, unless all are on `PATH`. */
6733
- async function checkTools(tools, env, rerun) {
6734
- const found = await Promise.all(tools.map((tool) => findTool(tool.name, env)));
6735
- const missing = tools.filter((_tool, index) => found[index] === void 0);
6736
- if (missing.length === 0) return;
6737
- const names = missing.map(({ name }) => name).join(", ");
6738
- throw runtimeError({
6739
- code: TOOL_MISSING,
6740
- message: `${missing.length === 1 ? "a tool it runs is" : "tools it runs are"} not on PATH: ${names}`,
6741
- hint: "run the Next steps in order: each install, then the command again",
6742
- next: [...missing.map(({ install }) => wholeLine(install)), rerun]
6743
- });
6744
- }
6745
- /** One `doctor` row per distinct tool the CLI and its Commands declare, with its install command as the fix. */
6746
- function toolRows(tools, commands) {
6747
- return declaredIn(tools, commands, (command) => command.tools).map(({ name, install }) => ({
6748
- name,
6749
- run: async (ctx) => {
6750
- const file = await findTool(name, ctx.env);
6751
- return file === void 0 ? {
6752
- state: "problem",
6753
- message: `${name} is not on PATH`,
6754
- fix: install
6755
- } : {
6756
- state: "ok",
6757
- message: `found at ${file}`
6758
- };
6759
- }
6760
- }));
6761
- }
6762
- //#endregion
6763
- //#region ../agent-cli/dist/version.js
6764
- /** The bundler's stamp, absent when the CLI runs unbundled. Read per call, so a test can define it. */
6765
- function buildStamp() {
6734
+ function fieldsFlag(rowFields) {
6766
6735
  return {
6767
- "sha": "330940a",
6768
- "dirty": false,
6769
- "builtAt": "2026-10-28T15:25:01.000Z"
6736
+ flag: FIELDS_FLAG,
6737
+ usage: `${FIELDS_FLAG} <a,b>`,
6738
+ description: `print only these comma-separated fields of each row (any of: ${rowFields.join(", ")})`,
6739
+ takes: "optional value",
6740
+ schema: string()
6770
6741
  };
6771
6742
  }
6772
- /** The first line of `--version`: the version, and the commit and time of a stamped build. */
6773
- function versionLine({ version }) {
6774
- const build = buildStamp();
6775
- return build === void 0 ? version : `${version} (${build.sha}${build.dirty ? "+dirty" : ""}) built ${build.builtAt}`;
6743
+ const PAGE_FIELDS = optionFields(PAGE_INPUT);
6744
+ const PAGE_FLAGS = {
6745
+ limit: pageFlag("limit"),
6746
+ cursor: pageFlag("cursor")
6747
+ };
6748
+ /** The Standard flags every List Command gets, as the caller types them. */
6749
+ const LIST_FLAGS = [...Object.values(PAGE_FLAGS).map(({ flag }) => flag), FIELDS_FLAG];
6750
+ function listSpec(rows) {
6751
+ const rowFields = Object.keys(rows.shape);
6752
+ return {
6753
+ rowFields,
6754
+ pageFields: PAGE_FIELDS,
6755
+ standardFlags: [...Object.values(PAGE_FLAGS), fieldsFlag(rowFields)],
6756
+ output: object({
6757
+ items: array(rows),
6758
+ next: string().optional()
6759
+ })
6760
+ };
6776
6761
  }
6777
- //#endregion
6778
- //#region ../agent-cli/dist/tail.js
6779
- /** The last `count` lines of a child's output, trimmed; empty when it wrote nothing. */
6780
- function lastLines(output, count) {
6781
- return output.trim().split("\n").slice(-count).join("\n");
6762
+ function quoted(names) {
6763
+ return names.map((name) => `'${name}'`).join(", ");
6782
6764
  }
6783
- //#endregion
6784
- //#region ../agent-cli/dist/context/spawn.js
6785
- const TOOL_FAILED = "TOOL_FAILED";
6786
- const STDERR_TAIL_LINES$1 = 20;
6787
- const SIGNAL_EXIT_BASE = 128;
6788
- const BATCH_FILE = /\.(?:cmd|bat)$/i;
6789
- const CMD_SHIM = /\.cmd$/i;
6790
- const CMD_META = /([()\][%!^"`<>&|;, *?])/g;
6791
- const PATH_SEPARATOR = /[\\/]/;
6792
- const TRAILING_CR = /\r$/;
6793
- const LINE_END = /\r\n|\r(?!$)|\n/;
6794
- function isWhole(input) {
6795
- return typeof input === "string" || input instanceof Uint8Array;
6765
+ /** The fields the caller asked for, in their order, or undefined when they gave no `--fields`. */
6766
+ function selectedFields(values, rowFields, commandLine) {
6767
+ const raw = values.get(FIELDS_FLAG);
6768
+ if (raw === void 0) return;
6769
+ const hint = `pass ${FIELDS_FLAG} with any of: ${rowFields.join(", ")}`;
6770
+ const names = typeof raw === "string" ? raw.split(SEPARATOR).filter((name) => name !== "") : [];
6771
+ if (names.length === 0) throw new UsageError({
6772
+ code: INVALID_INPUT,
6773
+ message: `${FIELDS_FLAG}: missing value for \`${commandLine}\``,
6774
+ hint
6775
+ });
6776
+ const unknown = names.filter((name) => !rowFields.includes(name));
6777
+ if (unknown.length > 0) throw new UsageError({
6778
+ code: UNKNOWN_FIELD,
6779
+ message: `unknown field ${quoted(unknown)} for \`${commandLine}\``,
6780
+ hint
6781
+ });
6782
+ return names;
6796
6783
  }
6797
- function escapedForCmd(text) {
6798
- return text.replace(CMD_META, "^$1");
6784
+ function fieldOf(row, field) {
6785
+ return typeof row === "object" && row !== null ? row[field] : void 0;
6799
6786
  }
6800
- function batchArgument(arg, shim) {
6801
- const escaped = escapedForCmd(`"${arg.replace(/(\\*)"/g, "$1$1\\\"").replace(/(\\*)$/, "$1$1")}"`);
6802
- return shim ? escapedForCmd(escaped) : escaped;
6787
+ function projected(rows, fields) {
6788
+ return rows.map((row) => Object.fromEntries(fields.map((field) => [field, fieldOf(row, field)])));
6803
6789
  }
6804
- function batchCommandLine(command, file, args) {
6805
- const shim = CMD_SHIM.test(file);
6806
- return [escapedForCmd(command), ...args.map((arg) => batchArgument(arg, shim))].join(" ");
6790
+ function cell(value) {
6791
+ if (value === void 0 || value === null) return "";
6792
+ return typeof value === "string" && !UNSAFE_IN_COLUMN.test(value) ? value : JSON.stringify(value);
6807
6793
  }
6808
- async function windowsLaunch(command, args, env) {
6809
- const file = PATH_SEPARATOR.test(command) ? command : await findTool(command, env);
6810
- if (file === void 0 || !BATCH_FILE.test(file)) return {
6811
- command,
6812
- args,
6813
- shell: false
6814
- };
6815
- return {
6816
- command: batchCommandLine(command, file, args),
6817
- args: [],
6818
- shell: true
6794
+ /** Selected rows as text: a header line of the field names, then one tab-separated line per row. */
6795
+ function renderRows(rows, fields) {
6796
+ return `${[fields, ...rows.map((row) => fields.map((field) => cell(row[field])))].map((line) => line.join(COLUMN_SEPARATOR)).join("\n")}\n`;
6797
+ }
6798
+ function cursorWords(cursor) {
6799
+ const { flag } = PAGE_FLAGS.cursor;
6800
+ return cursor.startsWith("-") ? [`${flag}=${cursor}`] : [flag, cursor];
6801
+ }
6802
+ function printedPage(page, invocation) {
6803
+ return page.nextCursor === void 0 ? { items: page.items } : {
6804
+ items: page.items,
6805
+ next: lineOf({
6806
+ kind: "rerun",
6807
+ words: changedArgv(invocation, {
6808
+ remove: [PAGE_FLAGS.cursor.flag],
6809
+ add: cursorWords(page.nextCursor)
6810
+ }),
6811
+ stdinArg: invocation.stdinArg
6812
+ }, invocation.runAs)
6819
6813
  };
6820
6814
  }
6821
- function stop(child) {
6822
- if (process.platform !== "win32" || child.pid === void 0) {
6823
- child.kill();
6824
- return Promise.resolve();
6825
- }
6826
- const pid = String(child.pid);
6827
- return new Promise((resolve) => {
6828
- const taskkill = spawn("taskkill", [
6829
- "/pid",
6830
- pid,
6831
- "/t",
6832
- "/f"
6833
- ], {
6834
- stdio: "ignore",
6835
- windowsHide: true
6836
- });
6837
- taskkill.once("error", () => {
6838
- child.kill();
6839
- resolve();
6840
- });
6841
- taskkill.once("exit", () => resolve());
6842
- });
6815
+ /** The lines that end a rendered page, so the caller always knows whether and how to get more. */
6816
+ function pageFooter({ items, next }, style) {
6817
+ return next === void 0 ? `${items.length} shown; no more pages\n` : `${items.length} shown; run the Next step for the next page\n${style.cyan("run:")} ${next}\n`;
6843
6818
  }
6844
- /** Calls `onLine` with each complete line pushed, and with the unterminated rest at the end. */
6845
- function lineSplitter(onLine) {
6846
- let rest = "";
6819
+ //#endregion
6820
+ //#region ../agent-cli/dist/planned.js
6821
+ /** Whether `command` changes something through a Plan: a write or destructive Command. */
6822
+ function hasPlan(command) {
6823
+ return command.effect === "write" || command.effect === "destructive";
6824
+ }
6825
+ //#endregion
6826
+ //#region ../agent-cli/dist/command-line/flags.js
6827
+ const HELP_FLAG = "--help";
6828
+ const VERSION_FLAG = "--version";
6829
+ const ROUTING_FLAGS = [
6830
+ HELP_FLAG,
6831
+ "-h",
6832
+ VERSION_FLAG
6833
+ ];
6834
+ function nameOf$1(flag) {
6835
+ return flag.replace(/^--?/, "");
6836
+ }
6837
+ function standardFlag(flag, { placeholder, ...rest }) {
6847
6838
  return {
6848
- push(text) {
6849
- if (onLine === void 0) return;
6850
- const lines = (rest + text).split(LINE_END);
6851
- rest = lines.pop() ?? "";
6852
- for (const line of lines) onLine(line);
6853
- },
6854
- end() {
6855
- if (rest !== "") onLine?.(rest.replace(TRAILING_CR, ""));
6856
- rest = "";
6857
- }
6839
+ flag,
6840
+ usage: placeholder === void 0 ? flag : `${flag} ${placeholder}`,
6841
+ ...rest
6858
6842
  };
6859
6843
  }
6860
- /** `onLine`, passing what it throws to `fail` and called no more once `failed()`. */
6861
- function guarded(onLine, failed, fail) {
6862
- if (onLine === void 0) return void 0;
6863
- return (line) => {
6864
- if (failed()) return;
6865
- try {
6866
- onLine(line);
6867
- } catch (error) {
6868
- fail(error);
6869
- }
6844
+ const JSON_FLAG = standardFlag("--json", {
6845
+ description: "print the result as JSON",
6846
+ takes: "nothing",
6847
+ schema: boolean()
6848
+ });
6849
+ const DRY_RUN_FLAG = standardFlag("--dry-run", {
6850
+ description: "print the Plan without changing anything",
6851
+ takes: "nothing",
6852
+ schema: boolean()
6853
+ });
6854
+ const CONFIRM_FLAG = standardFlag("--confirm", {
6855
+ placeholder: "<target>",
6856
+ description: "name the target to confirm the change; an error hint gives the exact command",
6857
+ takes: "value",
6858
+ schema: string()
6859
+ });
6860
+ const YES_FLAG = standardFlag("--yes", {
6861
+ description: "approve the cost the run states; --dry-run shows it",
6862
+ takes: "nothing",
6863
+ schema: boolean()
6864
+ });
6865
+ const FORCE_FLAG = standardFlag("--force", {
6866
+ description: "replace a file the run writes that already exists; --dry-run lists every file it writes",
6867
+ takes: "nothing",
6868
+ schema: boolean()
6869
+ });
6870
+ const EFFECT_FLAGS = {
6871
+ read: [JSON_FLAG],
6872
+ act: [JSON_FLAG],
6873
+ write: [JSON_FLAG, DRY_RUN_FLAG],
6874
+ destructive: [
6875
+ JSON_FLAG,
6876
+ DRY_RUN_FLAG,
6877
+ CONFIRM_FLAG
6878
+ ],
6879
+ serve: [JSON_FLAG]
6880
+ };
6881
+ const RESERVED = new Map([
6882
+ ...ROUTING_FLAGS,
6883
+ ...EFFECT_FLAGS.destructive.map(({ flag }) => flag),
6884
+ YES_FLAG.flag,
6885
+ FORCE_FLAG.flag,
6886
+ ...LIST_FLAGS
6887
+ ].map((flag) => [nameOf$1(flag), flag]));
6888
+ /** The Standard flags of a Command with this Effect and declarations, and of a List Command. */
6889
+ function standardFlagsFor(effect, { outputs, force, cost }, list) {
6890
+ const forceFlag = force === void 0 ? FORCE_FLAG : {
6891
+ ...FORCE_FLAG,
6892
+ description: force
6870
6893
  };
6894
+ return [
6895
+ ...EFFECT_FLAGS[effect],
6896
+ ...outputs || force !== void 0 ? [forceFlag] : [],
6897
+ ...cost ? [YES_FLAG] : [],
6898
+ ...list?.standardFlags ?? []
6899
+ ];
6871
6900
  }
6872
- /** Settles once the child has taken what was written, or its stdin has closed. */
6873
- function drained(stdin) {
6874
- return new Promise((resolve) => {
6875
- const done = () => {
6876
- stdin.off("drain", done);
6877
- stdin.off("close", done);
6878
- resolve();
6879
- };
6880
- stdin.on("drain", done);
6881
- stdin.on("close", done);
6901
+ /** Throws when the Command declares an arg or option under a Standard flag's name. */
6902
+ function checkNoStandardFlags(command, label) {
6903
+ const lines = [...argFields(command.args).map(({ key }) => ({
6904
+ kind: "arg",
6905
+ key
6906
+ })), ...optionFields(command.options).map(({ key }) => ({
6907
+ kind: "option",
6908
+ key
6909
+ }))].flatMap(({ kind, key }) => {
6910
+ const flag = RESERVED.get(kebab(key));
6911
+ return flag === void 0 ? [] : [` ${kind} '${key}' is the Standard flag ${flag}`];
6882
6912
  });
6913
+ if (lines.length > 0) throw new Error([`Command '${label}' declares Standard flags the Runtime adds; rename them:`, ...lines].join("\n"));
6883
6914
  }
6884
- /**
6885
- * Writes `input` to the child's stdin and ends it, stopping once the child is gone or the run
6886
- * aborts.
6887
- */
6888
- async function feed(stdin, input, signal) {
6889
- stdin.on("error", () => void 0);
6890
- const chunks = isWhole(input) ? [input] : input;
6891
- for await (const chunk of chunks) {
6892
- if (signal.aborted || stdin.destroyed) return;
6893
- if (!stdin.write(chunk)) await drained(stdin);
6894
- }
6895
- stdin.end();
6915
+ /** Throws when `force` is declared on a Command with no Plan, or says nothing. */
6916
+ function checkForce(command, label) {
6917
+ const force = "force" in command ? command.force : void 0;
6918
+ if (force === void 0) return;
6919
+ if (!hasPlan(command)) throw new Error(`Command '${label}' declares force, which only a write or destructive Command may`);
6920
+ if (force.trim() === "") throw new Error(`Command '${label}' declares an empty force; say what ${FORCE_FLAG.flag} replaces`);
6896
6921
  }
6897
- /**
6898
- * Runs a child process to its end; it is killed on abort, and the promise settles only once it has
6899
- * exited. A `detached` child leaves the terminal's process group on POSIX, and so its Ctrl-C.
6900
- */
6901
- async function spawnChild(command, args, { cwd, env, signal, onLine, stdin, detached = false }) {
6902
- signal.throwIfAborted();
6903
- const launch = await launchOf(command, args, env);
6904
- signal.throwIfAborted();
6905
- return new Promise((resolve, reject) => {
6906
- const child = spawn(launch.command, launch.args, {
6907
- cwd,
6908
- env,
6909
- shell: launch.shell,
6910
- windowsHide: true,
6911
- stdio: [
6912
- stdin === void 0 ? "ignore" : "pipe",
6913
- "pipe",
6914
- "pipe"
6915
- ],
6916
- detached: detached && process.platform !== "win32"
6917
- });
6918
- let stopped = Promise.resolve();
6919
- const kill = () => {
6920
- stopped = stop(child);
6921
- };
6922
- signal.addEventListener("abort", kill, { once: true });
6923
- let callerFailure;
6924
- const fail = (error) => {
6925
- if (callerFailure !== void 0) return;
6926
- callerFailure = { error };
6927
- kill();
6928
- };
6929
- let stdout = "";
6930
- let stderr = "";
6931
- const lines = guarded(onLine, () => callerFailure !== void 0, fail);
6932
- const stdoutLines = lineSplitter(lines);
6933
- const stderrLines = lineSplitter(lines);
6934
- child.stdout?.setEncoding("utf8").on("data", (chunk) => {
6935
- stdout += chunk;
6936
- stdoutLines.push(chunk);
6937
- });
6938
- child.stderr?.setEncoding("utf8").on("data", (chunk) => {
6939
- stderr += chunk;
6940
- stderrLines.push(chunk);
6941
- });
6942
- if (stdin !== void 0 && child.stdin !== null) feed(child.stdin, stdin, signal).catch(fail);
6943
- child.on("error", (error) => {
6944
- signal.removeEventListener("abort", kill);
6945
- reject(error);
6946
- });
6947
- child.on("close", (code, killedBy) => {
6948
- signal.removeEventListener("abort", kill);
6949
- if (signal.aborted) {
6950
- stopped.then(() => reject(signal.reason));
6951
- return;
6952
- }
6953
- stdoutLines.end();
6954
- stderrLines.end();
6955
- if (callerFailure !== void 0) {
6956
- const { error } = callerFailure;
6957
- stopped.then(() => reject(error));
6958
- return;
6959
- }
6960
- resolve({
6961
- stdout,
6962
- stderr,
6963
- exitCode: code ?? SIGNAL_EXIT_BASE + (killedBy === null ? 0 : constants$1.signals[killedBy])
6964
- });
6965
- });
6966
- });
6922
+ //#endregion
6923
+ //#region ../agent-cli/dist/define-command.js
6924
+ /** Throws when the Command's declaration breaks a rule its type cannot express; `label` names it. */
6925
+ function checkCommand(command, label, table) {
6926
+ checkNoStandardFlags(command, label);
6927
+ checkForce(command, label);
6928
+ checkArrayArgLast(label, command.args);
6929
+ checkStdinFields(label, command.args, command.options);
6930
+ checkNoBooleanArrayOption(label, command.options);
6931
+ checkErrors(command, label, table);
6967
6932
  }
6968
- async function launchOf(command, args, env) {
6969
- return process.platform === "win32" ? windowsLaunch(command, args, env) : {
6970
- command,
6971
- args,
6972
- shell: false
6973
- };
6933
+ function checkErrors(command, label, table) {
6934
+ const unknown = (command.errors ?? []).filter((code) => !Object.hasOwn(table, code));
6935
+ if (unknown.length > 0) throw new Error(`${label} declares ${unknown.join(", ")} in errors, a code the Error table lacks`);
6974
6936
  }
6975
- /**
6976
- * Starts a child in its own process group (its own console on Windows) with no stdio, and settles
6977
- * once it has started; the parent neither waits for it nor keeps it from exiting.
6978
- */
6979
- const realSpawnDetached = async (command, args, { cwd, env }) => {
6980
- const launch = await launchOf(command, args, env);
6981
- return new Promise((resolve, reject) => {
6982
- const child = spawn(launch.command, launch.args, {
6983
- cwd,
6984
- env,
6985
- shell: launch.shell,
6986
- windowsHide: true,
6987
- stdio: "ignore",
6988
- detached: true
6989
- });
6990
- child.unref();
6991
- child.once("error", reject);
6992
- child.once("spawn", () => {
6993
- child.off("error", reject);
6994
- const { pid } = child;
6995
- if (pid === void 0) reject(/* @__PURE__ */ new Error(`${command} started without a pid`));
6996
- else resolve({ pid });
6997
- });
6998
- });
6999
- };
7000
- const realSpawn = (command, args, options) => spawnChild(command, args, options);
7001
- function failedError(command, { exitCode, stderr }) {
7002
- const tail = lastLines(stderr, STDERR_TAIL_LINES$1);
7003
- return new CliError({
7004
- code: TOOL_FAILED,
7005
- message: tail === "" ? `${command} exited with ${exitCode} and wrote nothing to stderr` : `${command} exited with ${exitCode}; its stderr ends:\n${tail}`,
7006
- hint: `the output of ${command} above says why it failed`
7007
- });
6937
+ function defineCommandFor() {
6938
+ return declared;
6939
+ }
6940
+ function declared(command) {
6941
+ return command;
7008
6942
  }
7009
- function missingError(command, rerun, cause) {
6943
+ const defineCommand$1 = defineCommandFor();
6944
+ //#endregion
6945
+ //#region ../agent-cli/dist/context/project.js
6946
+ const PROJECT_INVALID = "PROJECT_INVALID";
6947
+ function invalid(path, problem, rerun, options) {
7010
6948
  return new CliError({
7011
- code: TOOL_MISSING,
7012
- message: `${command} could not be started: it is not on PATH`,
7013
- hint: `install ${command}, then run the Next step`,
6949
+ code: PROJECT_INVALID,
6950
+ message: `the Project file ${path} is invalid: ${problem}`,
6951
+ hint: `fix ${path}, then run the Next step`,
7014
6952
  next: [rerun]
7015
- }, { cause });
6953
+ }, options);
7016
6954
  }
7017
- function startError(command, error, { fileError, rerun }) {
7018
- if (isMissingFile(error)) return missingError(command, rerun, error);
7019
- return fileError(error, {
7020
- path: command,
7021
- access: "read"
7022
- });
6955
+ function readText(path, fileError) {
6956
+ try {
6957
+ return readFileSync(path, "utf8");
6958
+ } catch (error) {
6959
+ if (isMissingFile(error)) return void 0;
6960
+ throw fileError(error, {
6961
+ path,
6962
+ access: "read"
6963
+ });
6964
+ }
6965
+ }
6966
+ function parsed(path, text, schema, rerun) {
6967
+ let json;
6968
+ try {
6969
+ json = JSON.parse(text);
6970
+ } catch (error) {
6971
+ throw invalid(path, messageOf(error), rerun, { cause: error });
6972
+ }
6973
+ const result = schema.safeParse(json);
6974
+ if (!result.success) throw invalid(path, prettifyError(result.error), rerun);
6975
+ return result.data;
6976
+ }
6977
+ function projectFrom(projectFile, folder, scope) {
6978
+ const path = join(folder, projectFile.file);
6979
+ const text = readText(path, scope.fileError);
6980
+ if (text !== void 0) return {
6981
+ root: folder,
6982
+ settings: parsed(path, text, projectFile.schema, scope.rerun)
6983
+ };
6984
+ const parent = dirname(folder);
6985
+ return parent === folder ? void 0 : projectFrom(projectFile, parent, scope);
6986
+ }
6987
+ /** The nearest Project file at or above `cwd`, parsed by its schema; undefined when there is none. */
6988
+ function projectOf(projectFile, cwd, scope) {
6989
+ return projectFrom(projectFile, resolve(cwd), scope);
7023
6990
  }
7024
- async function* reported(stream, onError) {
6991
+ //#endregion
6992
+ //#region ../agent-cli/dist/context/secrets.js
6993
+ const SECRET_MISSING = "SECRET_MISSING";
6994
+ /** `$XDG_CONFIG_HOME/<cli>/.env`, with `~/.config` when the variable is unset or empty. */
6995
+ function envFileOf(cliName, env) {
6996
+ const config = env.XDG_CONFIG_HOME || join(env.HOME || homedir(), ".config");
6997
+ return join(config, cliName, ".env");
6998
+ }
6999
+ function readDotenv(path, fileError) {
7000
+ let text;
7025
7001
  try {
7026
- yield* stream;
7002
+ text = readFileSync(path, "utf8");
7027
7003
  } catch (error) {
7028
- onError(error);
7029
- throw error;
7004
+ if (isMissingFile(error)) return {};
7005
+ throw fileError(error, {
7006
+ path,
7007
+ access: "read"
7008
+ });
7030
7009
  }
7010
+ return parseEnv(text);
7031
7011
  }
7032
- /** `input`, a stream passing each error it throws to `onError` before the reader sees it. */
7033
- function reporting(input, onError) {
7034
- return input === void 0 || isWhole(input) ? input : reported(input, onError);
7035
- }
7036
- /** `onLine`, passing each error it throws to `onError` before its caller sees it. */
7037
- function reportingLines(onLine, onError) {
7038
- if (onLine === void 0) return void 0;
7039
- return (line) => {
7040
- try {
7041
- onLine(line);
7042
- } catch (error) {
7043
- onError(error);
7044
- throw error;
7045
- }
7012
+ /** Looks `name` up in the env, then in `envFile`; an empty value counts as unset. */
7013
+ function secretOf({ envFile, env, fileError }, name) {
7014
+ const fromEnv = env[name];
7015
+ if (fromEnv) return {
7016
+ value: fromEnv,
7017
+ origin: { kind: "env" }
7046
7018
  };
7047
- }
7048
- function contextSpawn(run, defaults, scope) {
7049
- return async (command, args = [], options = {}) => {
7050
- let callerFailure;
7051
- const caller = (error) => {
7052
- callerFailure = { error };
7053
- };
7054
- let result;
7055
- try {
7056
- result = await run(command, args, {
7057
- cwd: options.cwd ?? defaults.cwd,
7058
- env: options.env ?? defaults.env,
7059
- signal: defaults.signal,
7060
- onLine: reportingLines(options.onLine, caller),
7061
- stdin: reporting(options.stdin, caller)
7062
- });
7063
- } catch (error) {
7064
- throw callerFailure?.error === error ? error : startError(command, error, scope);
7019
+ const fromFile = readDotenv(envFile, fileError)[name];
7020
+ return fromFile ? {
7021
+ value: fromFile,
7022
+ origin: {
7023
+ kind: "file",
7024
+ path: envFile
7065
7025
  }
7066
- if (options.check === true && result.exitCode !== 0) throw failedError(command, result);
7067
- return result;
7068
- };
7026
+ } : void 0;
7069
7027
  }
7070
- /** `ctx.spawnDetached`: `run` bound to the run's cwd and env, its start failures classified by errno. */
7071
- function contextSpawnDetached(run, defaults, scope) {
7072
- return async (command, args = [], options = {}) => {
7073
- defaults.signal.throwIfAborted();
7074
- try {
7075
- return await run(command, args, {
7076
- cwd: options.cwd ?? defaults.cwd,
7077
- env: options.env ?? defaults.env
7078
- });
7079
- } catch (error) {
7080
- throw startError(command, error, scope);
7028
+ function missingIn(scope, secrets) {
7029
+ return secrets.filter(({ name }) => secretOf(scope, name) === void 0);
7030
+ }
7031
+ function getItFrom({ get }) {
7032
+ return `(get it from ${get})`;
7033
+ }
7034
+ function whereToGet(secrets) {
7035
+ return secrets.map((secret) => `${secret.name} ${getItFrom(secret)}`).join(", ");
7036
+ }
7037
+ /** Fails with `SECRET_MISSING`, naming every missing Secret at once, unless all are set. */
7038
+ function checkSecrets(secrets, scope, rerun) {
7039
+ const missing = missingIn(scope, secrets);
7040
+ if (missing.length === 0) return;
7041
+ const names = missing.map(({ name }) => name).join(", ");
7042
+ throw new CliError({
7043
+ code: SECRET_MISSING,
7044
+ message: `${missing.length === 1 ? "a Secret it needs is" : "Secrets it needs are"} not set: ${names}`,
7045
+ hint: `set ${whereToGet(missing)} in the environment or in ${scope.envFile}, then run the Next step`,
7046
+ next: [rerun]
7047
+ });
7048
+ }
7049
+ function originOf({ origin }) {
7050
+ return origin.kind === "env" ? "the environment" : origin.path;
7051
+ }
7052
+ /** The `doctor` row over every distinct Secret the CLI and its Commands declare; none without any. */
7053
+ function secretRows(secrets, commands) {
7054
+ const declared = declaredIn(secrets, commands, (command) => command.secrets);
7055
+ if (declared.length === 0) return [];
7056
+ return [{
7057
+ name: "secrets",
7058
+ run: ({ env, envFile, fileError }) => {
7059
+ const read = declared.map((declaration) => ({
7060
+ declaration,
7061
+ secret: secretOf({
7062
+ env,
7063
+ envFile,
7064
+ fileError
7065
+ }, declaration.name)
7066
+ }));
7067
+ const missing = read.filter(({ secret }) => secret === void 0);
7068
+ if (missing.length > 0) return {
7069
+ state: "problem",
7070
+ message: `${missing.map(({ declaration }) => `${declaration.name} is not set ${getItFrom(declaration)}`).join(", ")}; add ${missing.length === 1 ? "it" : "them"} to ${envFile} or the environment`
7071
+ };
7072
+ return {
7073
+ state: "ok",
7074
+ message: read.flatMap(({ declaration, secret }) => secret === void 0 ? [] : [`${declaration.name} from ${originOf(secret)}`]).join(", ")
7075
+ };
7081
7076
  }
7077
+ }];
7078
+ }
7079
+ //#endregion
7080
+ //#region ../agent-cli/dist/version.js
7081
+ /** The bundler's stamp, absent when the CLI runs unbundled. Read per call, so a test can define it. */
7082
+ function buildStamp() {
7083
+ return {
7084
+ "sha": "4664298",
7085
+ "dirty": false,
7086
+ "builtAt": "2026-11-02T04:03:08.000Z"
7082
7087
  };
7083
7088
  }
7089
+ /** The first line of `--version`: the version, and the commit and time of a stamped build. */
7090
+ function versionLine({ version }) {
7091
+ const build = buildStamp();
7092
+ return build === void 0 ? version : `${version} (${build.sha}${build.dirty ? "+dirty" : ""}) built ${build.builtAt}`;
7093
+ }
7094
+ //#endregion
7095
+ //#region ../agent-cli/dist/context/agent.js
7096
+ const AGENT_NAME = /^[a-zA-Z0-9_-]+$/u;
7097
+ const isSet = (name) => (env) => (env[name] ?? "") !== "";
7098
+ const equals = (name, value) => (env) => env[name] === value;
7099
+ const MARKERS = [
7100
+ (env) => AGENT_NAME.test(env.AI_AGENT ?? ""),
7101
+ equals("AGENT", "amp"),
7102
+ isSet("CODEX_SANDBOX"),
7103
+ isSet("CODEX_CI"),
7104
+ isSet("CODEX_THREAD_ID"),
7105
+ isSet("GEMINI_CLI"),
7106
+ isSet("COPILOT_CLI"),
7107
+ isSet("OPENCODE"),
7108
+ isSet("ANTIGRAVITY_AGENT"),
7109
+ isSet("AUGMENT_AGENT"),
7110
+ isSet("REPL_ID"),
7111
+ isSet("CLAUDE_CODE_IS_COWORK"),
7112
+ isSet("CLAUDECODE"),
7113
+ isSet("CLAUDE_CODE"),
7114
+ isSet("CURSOR_TRACE_ID"),
7115
+ isSet("CURSOR_AGENT"),
7116
+ equals("CURSOR_EXTENSION_HOST_ROLE", "agent-exec"),
7117
+ equals("TERM_PROGRAM", "kiro"),
7118
+ (env) => ["/.pi/agent", "\\.pi\\agent"].some((dir) => (env.PATH ?? "").includes(dir)),
7119
+ isSet("GOOSE_PROVIDER")
7120
+ ];
7121
+ /**
7122
+ * Whether a Coding agent, not a person, is running the Agent CLI. Some Coding agents run commands
7123
+ * in a PTY, so a TTY alone doesn't mean a person is watching.
7124
+ */
7125
+ function isAgent(env) {
7126
+ return MARKERS.some((marker) => marker(env));
7127
+ }
7084
7128
  //#endregion
7085
7129
  //#region ../agent-cli/dist/built-ins/semver.js
7086
7130
  const VERSION = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
@@ -7221,8 +7265,14 @@ function tail$1(stderr) {
7221
7265
  const lines = lastLines(stderr, STDERR_TAIL_LINES);
7222
7266
  return lines === "" ? "" : `\n${lines}`;
7223
7267
  }
7268
+ function authHint(code, ctx) {
7269
+ const { label: registry, loginHint } = ctx.registry;
7270
+ const login = `${loginHint}, then ${RUN_AGAIN}`;
7271
+ if (code !== "E403" || !isAgent(ctx.env)) return login;
7272
+ return `a Coding agent's sandbox may refuse ${registry} with a 403: ask your user to type ! and ${RUN_AGAIN}, or to allow ${registry} in the sandbox's network list; if it fails outside the sandbox too, ${login}`;
7273
+ }
7224
7274
  function failureOf(call, answer, ctx) {
7225
- const { label: registry, package: name, loginHint, unreachableHint } = ctx.registry;
7275
+ const { label: registry, package: name, unreachableHint } = ctx.registry;
7226
7276
  const code = NPM_CODE.exec(answer.stderr)?.[1];
7227
7277
  const output = tail$1(answer.stderr);
7228
7278
  const next = [ctx.rerun];
@@ -7235,7 +7285,7 @@ function failureOf(call, answer, ctx) {
7235
7285
  if (code !== void 0 && AUTH_CODES.has(code)) return new CliError({
7236
7286
  code: REGISTRY_AUTH,
7237
7287
  message: `${registry} refused npm's credentials (${code})${output}`,
7238
- hint: `${loginHint}, then ${RUN_AGAIN}`,
7288
+ hint: authHint(code, ctx),
7239
7289
  next
7240
7290
  });
7241
7291
  if (call === "view" && code === "E404") return noLatest(ctx, `npm found no ${name}@latest on ${registry}: no version of it is published there`);
@@ -7475,7 +7525,7 @@ function packagesOf(install) {
7475
7525
  function isUnchecked(standing) {
7476
7526
  return standing.kind === "source" || standing.kind === "linked";
7477
7527
  }
7478
- function skippedFor(cli, install, standing) {
7528
+ function skippedFor$1(cli, install, standing) {
7479
7529
  return {
7480
7530
  state: "skipped",
7481
7531
  message: standing.kind === "source" ? `not checked: ${cli.name} runs from source, not from an install` : `not checked: ${install.package} links to ${standing.copy.realDir}, so the workspace decides its version`
@@ -7500,7 +7550,7 @@ function installFinding(cli, runAs, install, { cwd, root, standing }) {
7500
7550
  const lines = root === void 0 ? void 0 : LINES[root.manager];
7501
7551
  switch (standing.kind) {
7502
7552
  case "source":
7503
- case "linked": return skippedFor(cli, install, standing);
7553
+ case "linked": return skippedFor$1(cli, install, standing);
7504
7554
  case "missing": return problem(`no package ${searchedFrom(cwd, root)} depends on ${install.package}`, lines?.add(packagesOf(install)));
7505
7555
  case "listed": return problem(`${standing.manifest} lists ${install.package}, but no copy of it is installed at or above ${cwd}`, lines?.install);
7506
7556
  case "unreadable": return problem(`${standing.manifest} is not a package.json with a version: ${standing.reason}`, lines?.install);
@@ -7546,7 +7596,7 @@ function checkOf$1(cli, install, runAs, { bin, node }) {
7546
7596
  install: async (ctx) => installFinding(cli, runAs, install, await surveyed(ctx.cwd)),
7547
7597
  release: async (ctx) => {
7548
7598
  const { cwd, root, standing } = await surveyed(ctx.cwd);
7549
- if (isUnchecked(standing)) return { skipped: skippedFor(cli, install, standing) };
7599
+ if (isUnchecked(standing)) return { skipped: skippedFor$1(cli, install, standing) };
7550
7600
  return {
7551
7601
  npm: npmContext(cli, registryOf(install, root?.dir), ctx, node, root?.dir ?? cwd),
7552
7602
  advise: (latest) => adviceFor(install, root, latest)
@@ -7659,6 +7709,7 @@ function hubSkill(cli, from) {
7659
7709
  run: async (ctx) => findingOf(await skillCopies(cli.name, cli.version, ctx))
7660
7710
  }],
7661
7711
  notice: async () => {},
7712
+ differing: async (place) => (await skillCopies(cli.name, cli.version, place)).filter(({ state }) => state !== "current" && state !== "unreadable").map(describeCopy),
7662
7713
  nextAt: async (version, ctx) => {
7663
7714
  const copies = await skillCopies(cli.name, version, ctx);
7664
7715
  const fix = fixFor(cli.name, copies, from);
@@ -7741,40 +7792,6 @@ function agentsRow(agents, install) {
7741
7792
  };
7742
7793
  }
7743
7794
  //#endregion
7744
- //#region ../agent-cli/dist/context/agent.js
7745
- const AGENT_NAME = /^[a-zA-Z0-9_-]+$/u;
7746
- const isSet = (name) => (env) => (env[name] ?? "") !== "";
7747
- const equals = (name, value) => (env) => env[name] === value;
7748
- const MARKERS = [
7749
- (env) => AGENT_NAME.test(env.AI_AGENT ?? ""),
7750
- equals("AGENT", "amp"),
7751
- isSet("CODEX_SANDBOX"),
7752
- isSet("CODEX_CI"),
7753
- isSet("CODEX_THREAD_ID"),
7754
- isSet("GEMINI_CLI"),
7755
- isSet("COPILOT_CLI"),
7756
- isSet("OPENCODE"),
7757
- isSet("ANTIGRAVITY_AGENT"),
7758
- isSet("AUGMENT_AGENT"),
7759
- isSet("REPL_ID"),
7760
- isSet("CLAUDE_CODE_IS_COWORK"),
7761
- isSet("CLAUDECODE"),
7762
- isSet("CLAUDE_CODE"),
7763
- isSet("CURSOR_TRACE_ID"),
7764
- isSet("CURSOR_AGENT"),
7765
- equals("CURSOR_EXTENSION_HOST_ROLE", "agent-exec"),
7766
- equals("TERM_PROGRAM", "kiro"),
7767
- (env) => ["/.pi/agent", "\\.pi\\agent"].some((dir) => (env.PATH ?? "").includes(dir)),
7768
- isSet("GOOSE_PROVIDER")
7769
- ];
7770
- /**
7771
- * Whether a Coding agent, not a person, is running the Agent CLI. Some Coding agents run commands
7772
- * in a PTY, so a TTY alone doesn't mean a person is watching.
7773
- */
7774
- function isAgent(env) {
7775
- return MARKERS.some((marker) => marker(env));
7776
- }
7777
- //#endregion
7778
7795
  //#region ../agent-cli/dist/context/errno.js
7779
7796
  const PERMISSION_DENIED = "PERMISSION_DENIED";
7780
7797
  const DISK_FULL = "DISK_FULL";
@@ -8295,6 +8312,15 @@ function packagedSkill(cli, { packaged: folder, agents: text }, install) {
8295
8312
  run: (ctx) => findingOf(cli, folder, install, ctx)
8296
8313
  }, ...agents === void 0 ? [] : [agentsRow(agents, install)]],
8297
8314
  notice: (launch, ctx) => notice(cli, folder, install, launch, ctx),
8315
+ differing: async ({ cwd, packageRoot }) => {
8316
+ const { copy } = await skillCopyOf(cli, cwd);
8317
+ const { copyVersion, packageVersion, same } = await skillVersionsOf(join(packageRoot, folder), copy);
8318
+ return copyVersion === void 0 || same ? [] : [compared(cli.name, {
8319
+ copy,
8320
+ copyVersion,
8321
+ packageVersion
8322
+ }, false)];
8323
+ },
8298
8324
  nextAt: async (_version, ctx) => {
8299
8325
  const { fix } = await findingOf(cli, folder, install, ctx);
8300
8326
  return fix === void 0 ? [] : [fix];
@@ -8759,12 +8785,21 @@ function nodeRow(cli) {
8759
8785
  };
8760
8786
  }
8761
8787
  /** A row's thrown error as its problem: a `CliError` with its hint and code, anything else a bug. */
8762
- function problemOfError(name, error) {
8763
- if (error instanceof CliError) return {
8764
- state: "problem",
8765
- message: `${error.message}; ${error.hint}`,
8766
- code: error.code
8767
- };
8788
+ function problemOfError(name, error, runAs) {
8789
+ if (error instanceof CliError) {
8790
+ const message = `${error.message}; ${error.hint}`;
8791
+ const fix = error.next.find((step) => !isRerun(step));
8792
+ return fix === void 0 ? {
8793
+ state: "problem",
8794
+ message,
8795
+ code: error.code
8796
+ } : {
8797
+ state: "problem",
8798
+ message,
8799
+ fix: lineOf(fix, runAs),
8800
+ code: error.code
8801
+ };
8802
+ }
8768
8803
  return {
8769
8804
  state: "problem",
8770
8805
  message: `the ${name} row hit a bug: ${messageOf(error)}`
@@ -8790,11 +8825,11 @@ function readProject(projectFile, ctx) {
8790
8825
  };
8791
8826
  }
8792
8827
  }
8793
- function projectRow({ file }, read) {
8828
+ function projectRow({ file }, { read, runAs }) {
8794
8829
  return {
8795
8830
  name: "project",
8796
8831
  run: (ctx) => {
8797
- if (!read.ok) return problemOfError("project", read.error);
8832
+ if (!read.ok) return problemOfError("project", read.error, runAs);
8798
8833
  return ok(read.project === void 0 ? `none found: no ${file} at or above ${ctx.cwd}` : join(read.project.root, file));
8799
8834
  }
8800
8835
  };
@@ -8834,20 +8869,24 @@ function registryRow(cli, check) {
8834
8869
  };
8835
8870
  }
8836
8871
  /** Every row `doctor` reports, in order: the fixed rows around the CLI's own and its tools'. */
8837
- function doctorChecks(cli, launch, read) {
8872
+ function doctorChecks(cli, launch, scope) {
8838
8873
  const install = installOf(cli);
8839
8874
  const check = install.check(launch);
8840
- return [
8841
- buildRow(cli),
8842
- nodeRow(cli),
8843
- ...cli.project === void 0 ? [] : [projectRow(cli.project, read)],
8844
- ...cli.doctor ?? [],
8845
- ...toolRows(cli.tools ?? [], cli.commands),
8846
- ...secretRows(cli.secrets ?? [], cli.commands),
8847
- installRow(check),
8848
- registryRow(cli, check),
8849
- ...skillOf$1(cli, install).rows
8850
- ];
8875
+ const tools = toolRows(cli.tools ?? [], cli.commands);
8876
+ return {
8877
+ checks: [
8878
+ buildRow(cli),
8879
+ nodeRow(cli),
8880
+ ...cli.project === void 0 ? [] : [projectRow(cli.project, scope)],
8881
+ ...cli.doctor ?? [],
8882
+ ...tools,
8883
+ ...secretRows(cli.secrets ?? [], cli.commands),
8884
+ installRow(check),
8885
+ registryRow(cli, check),
8886
+ ...skillOf$1(cli, install).rows
8887
+ ],
8888
+ tools: new Set(tools.map(({ name }) => name))
8889
+ };
8851
8890
  }
8852
8891
  const DEFAULT_DEADLINE_MS = 1e4;
8853
8892
  const MS_PER_SECOND = 1e3;
@@ -8867,14 +8906,14 @@ const reportSchema = object({
8867
8906
  })),
8868
8907
  next: array(string())
8869
8908
  });
8870
- function problemOf(check, error, { signal, deadline, ms }) {
8909
+ function problemOf(check, error, { signal, deadline, ms, runAs }) {
8871
8910
  if (!signal.aborted && deadline.aborted && error === deadline.reason) return {
8872
8911
  state: "problem",
8873
8912
  message: `${check.name} did not answer within ${ms / MS_PER_SECOND} s`
8874
8913
  };
8875
- return problemOfError(check.name, error);
8914
+ return problemOfError(check.name, error, runAs);
8876
8915
  }
8877
- function rowContext(ctx, signal, read) {
8916
+ function rowContext(ctx, signal, { read }) {
8878
8917
  return Object.create(ctx, {
8879
8918
  signal: { value: signal },
8880
8919
  project: { get: () => {
@@ -8883,21 +8922,36 @@ function rowContext(ctx, signal, read) {
8883
8922
  } }
8884
8923
  });
8885
8924
  }
8886
- async function rowOf(check, ctx, read) {
8925
+ function skippedFor(error, { read, tools }) {
8926
+ if (!read.ok && error === read.error) return {
8927
+ state: "skipped",
8928
+ message: "the Project file is unusable; the project row says why"
8929
+ };
8930
+ const tool = missingToolOf(error);
8931
+ if (tool !== void 0 && tools.has(tool)) return {
8932
+ state: "skipped",
8933
+ message: `${tool} is not on PATH; the ${tool} row says why`
8934
+ };
8935
+ }
8936
+ async function rowOf(check, ctx, { read, runAs, tools }) {
8887
8937
  const ms = check.deadlineMs ?? DEFAULT_DEADLINE_MS;
8888
8938
  const signal = ctx.deadline(ms);
8889
8939
  let finding;
8890
8940
  try {
8891
- finding = await Promise.race([Promise.resolve().then(() => check.run(rowContext(ctx, signal, read))), untilAborted(signal)]);
8941
+ finding = await Promise.race([Promise.resolve().then(() => check.run(rowContext(ctx, signal, {
8942
+ read,
8943
+ runAs
8944
+ }))), untilAborted(signal)]);
8892
8945
  } catch (error) {
8893
8946
  if (ctx.signal.aborted) return void 0;
8894
- finding = !read.ok && error === read.error ? {
8895
- state: "skipped",
8896
- message: "the Project file is unusable; the project row says why"
8897
- } : problemOf(check, error, {
8947
+ finding = skippedFor(error, {
8948
+ read,
8949
+ tools
8950
+ }) ?? problemOf(check, error, {
8898
8951
  signal: ctx.signal,
8899
8952
  deadline: signal,
8900
- ms
8953
+ ms,
8954
+ runAs
8901
8955
  });
8902
8956
  }
8903
8957
  return {
@@ -8908,8 +8962,10 @@ async function rowOf(check, ctx, read) {
8908
8962
  advisory: check.advisory === true
8909
8963
  };
8910
8964
  }
8911
- function nextOf(rows) {
8912
- return [...new Set(rows.flatMap(({ fix }) => fix === void 0 ? [] : [fix]))];
8965
+ function nextOf(checked, doctorLine) {
8966
+ const fixes = checked.flatMap(({ row }) => row.fix === void 0 ? [] : [row.fix]);
8967
+ const unfixed = checked.some(({ row, advisory }) => advisory && row.state === "problem" && row.fix === void 0);
8968
+ return [...new Set(unfixed ? [...fixes, doctorLine] : fixes)];
8913
8969
  }
8914
8970
  function render({ rows, next }, style) {
8915
8971
  const width = Math.max(MIN_LABEL_WIDTH, ...rows.map(({ check }) => check.length)) + 2;
@@ -8927,8 +8983,8 @@ function failure(cliName, rerun, problems) {
8927
8983
  };
8928
8984
  return runtimeError(fix === void 0 ? {
8929
8985
  ...head,
8930
- hint: "the row says what is wrong",
8931
- next: []
8986
+ hint: "fix what the row says, then run the Next step",
8987
+ next: [rerun]
8932
8988
  } : {
8933
8989
  ...head,
8934
8990
  hint: "run the Next steps in order",
@@ -8951,12 +9007,18 @@ function doctorCommand(cli, launch) {
8951
9007
  effect: "read",
8952
9008
  output: reportSchema,
8953
9009
  run: async (_input, ctx) => {
8954
- const read = readProject(cli.project, ctx);
8955
- const checked = (await Promise.all(doctorChecks(cli, launch, read).map((check) => rowOf(check, ctx, read)))).filter((row) => row !== void 0);
8956
- const rows = checked.map(({ row }) => row);
9010
+ const rowsScope = {
9011
+ read: readProject(cli.project, ctx),
9012
+ runAs: runAsOf(cli)
9013
+ };
9014
+ const { checks, tools } = doctorChecks(cli, launch, rowsScope);
9015
+ const checked = (await Promise.all(checks.map((check) => rowOf(check, ctx, {
9016
+ ...rowsScope,
9017
+ tools
9018
+ })))).filter((row) => row !== void 0);
8957
9019
  const report = {
8958
- rows,
8959
- next: nextOf(rows)
9020
+ rows: checked.map(({ row }) => row),
9021
+ next: nextOf(checked, lineOf(ctx.rerun(), rowsScope.runAs))
8960
9022
  };
8961
9023
  if (ctx.signal.aborted) {
8962
9024
  ctx.deliver(report);
@@ -9226,18 +9288,26 @@ function installFrom(name, manifest, install) {
9226
9288
  mode: "dependency",
9227
9289
  package: manifest.name
9228
9290
  };
9229
- if (install !== "global") return {
9291
+ if (install === "global") return {
9292
+ mode: "global",
9293
+ package: manifest.name,
9294
+ registry: publishedTo(name, manifest)
9295
+ };
9296
+ if (install.mode === "global") return {
9297
+ mode: "global",
9298
+ package: manifest.name,
9299
+ registry: install.registry
9300
+ };
9301
+ return {
9230
9302
  mode: "dependency",
9231
9303
  package: manifest.name,
9232
9304
  lockstep: install.lockstep
9233
9305
  };
9306
+ }
9307
+ function publishedTo(name, manifest) {
9234
9308
  const registry = manifest.publishConfig?.registry;
9235
9309
  if (registry === void 0) throw new Error(`${name} declares a Global install, which needs publishConfig.registry in its package.json`);
9236
- return {
9237
- mode: "global",
9238
- package: manifest.name,
9239
- registry
9240
- };
9310
+ return registry;
9241
9311
  }
9242
9312
  function resolved(cli) {
9243
9313
  const { manifest, ...declared } = cli;
@@ -9302,6 +9372,30 @@ function startSideTask(sideTask, runWith, { clock, failed }) {
9302
9372
  };
9303
9373
  }
9304
9374
  //#endregion
9375
+ //#region ../agent-cli/dist/built-ins/skill/usage-hint.js
9376
+ const SKILL_USAGE_CODES = /* @__PURE__ */ new Set([
9377
+ UNKNOWN_OPTION,
9378
+ UNKNOWN_COMMAND,
9379
+ INVALID_INPUT
9380
+ ]);
9381
+ /** A usage error whose hint names each Skill copy that differs from this build, since one may have named the input. */
9382
+ async function withDifferingSkill(error, cli, place, runAs) {
9383
+ if (!(error instanceof UsageError) || !SKILL_USAGE_CODES.has(error.code)) return error;
9384
+ let differing;
9385
+ try {
9386
+ differing = await skillOf$1(cli, installOf(cli)).differing(place);
9387
+ } catch (lookup) {
9388
+ if (errnoOf(lookup) === void 0) throw lookup;
9389
+ return error;
9390
+ }
9391
+ if (differing.length === 0) return error;
9392
+ return new UsageError({
9393
+ code: error.code,
9394
+ message: error.message,
9395
+ hint: `${error.hint}; the ${cli.name} skill may name what this build does not know: ${differing.join("; ")}; run \`${runAs} doctor\` to see how to update it`
9396
+ });
9397
+ }
9398
+ //#endregion
9305
9399
  //#region ../agent-cli/dist/command-line/suggest.js
9306
9400
  const MAX_DISTANCE = 3;
9307
9401
  const MIN_SIMILARITY = .4;
@@ -11012,7 +11106,11 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11012
11106
  });
11013
11107
  });
11014
11108
  } catch (error) {
11015
- return failed(error, noSideTask);
11109
+ return failed(await withDifferingSkill(error, cli, {
11110
+ cwd,
11111
+ env,
11112
+ packageRoot
11113
+ }, invocation.runAs), noSideTask);
11016
11114
  }
11017
11115
  const { input, flags } = parsed;
11018
11116
  resumed = command.deadline?.resume(input, rerunOf);
@@ -11224,10 +11322,16 @@ async function routeAndRun(cli, argv, options, write, stopNotice) {
11224
11322
  ...format,
11225
11323
  json: routed.json
11226
11324
  }));
11227
- case "usage error": return ranAlone(errorResult(routed.error, {
11228
- ...misused(routed.node),
11229
- json: routed.json
11230
- }));
11325
+ case "usage error": {
11326
+ const place = {
11327
+ ...options,
11328
+ packageRoot: packageRootOf(options.bin, options.cwd)
11329
+ };
11330
+ return ranAlone(errorResult(await withDifferingSkill(routed.error, cli, place, runAs), {
11331
+ ...misused(routed.node),
11332
+ json: routed.json
11333
+ }));
11334
+ }
11231
11335
  case "command": return whileStopping(options.signal, stopNotice === void 0 ? void 0 : () => write.stderr(stopLine(stopNotice, routed.json)), () => runCommand(cli, routed, options, write, misused(routed.spec)));
11232
11336
  }
11233
11337
  }
@@ -11290,7 +11394,7 @@ async function runProcess(cli) {
11290
11394
  //#region package.json
11291
11395
  var package_default = {
11292
11396
  name: "@duyquangnvx/create-agent-cli",
11293
- version: "0.20.0",
11397
+ version: "0.22.0",
11294
11398
  description: "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
11295
11399
  type: "module",
11296
11400
  engines: { "node": ">=22.12.0" },