@duyquangnvx/create-agent-cli 0.20.0 → 0.21.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/bin.js +1032 -978
  3. package/package.json +2 -2
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,1047 @@ 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;
6796
- }
6797
- function escapedForCmd(text) {
6798
- return text.replace(CMD_META, "^$1");
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;
6799
6783
  }
6800
- function batchArgument(arg, shim) {
6801
- const escaped = escapedForCmd(`"${arg.replace(/(\\*)"/g, "$1$1\\\"").replace(/(\\*)$/, "$1$1")}"`);
6802
- return shim ? escapedForCmd(escaped) : escaped;
6784
+ function fieldOf(row, field) {
6785
+ return typeof row === "object" && row !== null ? row[field] : void 0;
6803
6786
  }
6804
- function batchCommandLine(command, file, args) {
6805
- const shim = CMD_SHIM.test(file);
6806
- return [escapedForCmd(command), ...args.map((arg) => batchArgument(arg, shim))].join(" ");
6787
+ function projected(rows, fields) {
6788
+ return rows.map((row) => Object.fromEntries(fields.map((field) => [field, fieldOf(row, field)])));
6807
6789
  }
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
6819
- };
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);
6820
6793
  }
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
- });
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`;
6843
6797
  }
6844
- /** Calls `onLine` with each complete line pushed, and with the unterminated rest at the end. */
6845
- function lineSplitter(onLine) {
6846
- let rest = "";
6847
- 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
- }
6858
- };
6798
+ function cursorWords(cursor) {
6799
+ const { flag } = PAGE_FLAGS.cursor;
6800
+ return cursor.startsWith("-") ? [`${flag}=${cursor}`] : [flag, cursor];
6859
6801
  }
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
- }
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)
6870
6813
  };
6871
6814
  }
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);
6882
- });
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`;
6883
6818
  }
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();
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";
6896
6824
  }
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
- });
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(/^--?/, "");
6967
6836
  }
6968
- async function launchOf(command, args, env) {
6969
- return process.platform === "win32" ? windowsLaunch(command, args, env) : {
6970
- command,
6971
- args,
6972
- shell: false
6837
+ function standardFlag(flag, { placeholder, ...rest }) {
6838
+ return {
6839
+ flag,
6840
+ usage: placeholder === void 0 ? flag : `${flag} ${placeholder}`,
6841
+ ...rest
6973
6842
  };
6974
6843
  }
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
- });
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]
6999
6880
  };
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`
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
6893
+ };
6894
+ return [
6895
+ ...EFFECT_FLAGS[effect],
6896
+ ...outputs || force !== void 0 ? [forceFlag] : [],
6897
+ ...cost ? [YES_FLAG] : [],
6898
+ ...list?.standardFlags ?? []
6899
+ ];
6900
+ }
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}`];
7007
6912
  });
6913
+ if (lines.length > 0) throw new Error([`Command '${label}' declares Standard flags the Runtime adds; rename them:`, ...lines].join("\n"));
6914
+ }
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`);
6921
+ }
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);
6932
+ }
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`);
6936
+ }
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 });
7016
- }
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
- });
6953
+ }, options);
7023
6954
  }
7024
- async function* reported(stream, onError) {
6955
+ function readText(path, fileError) {
7025
6956
  try {
7026
- yield* stream;
6957
+ return readFileSync(path, "utf8");
7027
6958
  } catch (error) {
7028
- onError(error);
7029
- throw error;
6959
+ if (isMissingFile(error)) return void 0;
6960
+ throw fileError(error, {
6961
+ path,
6962
+ access: "read"
6963
+ });
7030
6964
  }
7031
6965
  }
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);
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;
7035
6976
  }
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
- }
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)
7046
6983
  };
6984
+ const parent = dirname(folder);
6985
+ return parent === folder ? void 0 : projectFrom(projectFile, parent, scope);
7047
6986
  }
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);
7065
- }
7066
- if (options.check === true && result.exitCode !== 0) throw failedError(command, result);
7067
- return result;
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);
6990
+ }
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;
7001
+ try {
7002
+ text = readFileSync(path, "utf8");
7003
+ } catch (error) {
7004
+ if (isMissingFile(error)) return {};
7005
+ throw fileError(error, {
7006
+ path,
7007
+ access: "read"
7008
+ });
7009
+ }
7010
+ return parseEnv(text);
7011
+ }
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" }
7068
7018
  };
7019
+ const fromFile = readDotenv(envFile, fileError)[name];
7020
+ return fromFile ? {
7021
+ value: fromFile,
7022
+ origin: {
7023
+ kind: "file",
7024
+ path: envFile
7025
+ }
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": "75fd8d7",
7085
+ "dirty": false,
7086
+ "builtAt": "2026-10-29T03:29:07.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
+ }
7084
7094
  //#endregion
7085
7095
  //#region ../agent-cli/dist/built-ins/semver.js
7086
7096
  const VERSION = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
@@ -7475,7 +7485,7 @@ function packagesOf(install) {
7475
7485
  function isUnchecked(standing) {
7476
7486
  return standing.kind === "source" || standing.kind === "linked";
7477
7487
  }
7478
- function skippedFor(cli, install, standing) {
7488
+ function skippedFor$1(cli, install, standing) {
7479
7489
  return {
7480
7490
  state: "skipped",
7481
7491
  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 +7510,7 @@ function installFinding(cli, runAs, install, { cwd, root, standing }) {
7500
7510
  const lines = root === void 0 ? void 0 : LINES[root.manager];
7501
7511
  switch (standing.kind) {
7502
7512
  case "source":
7503
- case "linked": return skippedFor(cli, install, standing);
7513
+ case "linked": return skippedFor$1(cli, install, standing);
7504
7514
  case "missing": return problem(`no package ${searchedFrom(cwd, root)} depends on ${install.package}`, lines?.add(packagesOf(install)));
7505
7515
  case "listed": return problem(`${standing.manifest} lists ${install.package}, but no copy of it is installed at or above ${cwd}`, lines?.install);
7506
7516
  case "unreadable": return problem(`${standing.manifest} is not a package.json with a version: ${standing.reason}`, lines?.install);
@@ -7546,7 +7556,7 @@ function checkOf$1(cli, install, runAs, { bin, node }) {
7546
7556
  install: async (ctx) => installFinding(cli, runAs, install, await surveyed(ctx.cwd)),
7547
7557
  release: async (ctx) => {
7548
7558
  const { cwd, root, standing } = await surveyed(ctx.cwd);
7549
- if (isUnchecked(standing)) return { skipped: skippedFor(cli, install, standing) };
7559
+ if (isUnchecked(standing)) return { skipped: skippedFor$1(cli, install, standing) };
7550
7560
  return {
7551
7561
  npm: npmContext(cli, registryOf(install, root?.dir), ctx, node, root?.dir ?? cwd),
7552
7562
  advise: (latest) => adviceFor(install, root, latest)
@@ -8759,12 +8769,21 @@ function nodeRow(cli) {
8759
8769
  };
8760
8770
  }
8761
8771
  /** 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
- };
8772
+ function problemOfError(name, error, runAs) {
8773
+ if (error instanceof CliError) {
8774
+ const message = `${error.message}; ${error.hint}`;
8775
+ const fix = error.next.find((step) => !isRerun(step));
8776
+ return fix === void 0 ? {
8777
+ state: "problem",
8778
+ message,
8779
+ code: error.code
8780
+ } : {
8781
+ state: "problem",
8782
+ message,
8783
+ fix: lineOf(fix, runAs),
8784
+ code: error.code
8785
+ };
8786
+ }
8768
8787
  return {
8769
8788
  state: "problem",
8770
8789
  message: `the ${name} row hit a bug: ${messageOf(error)}`
@@ -8790,11 +8809,11 @@ function readProject(projectFile, ctx) {
8790
8809
  };
8791
8810
  }
8792
8811
  }
8793
- function projectRow({ file }, read) {
8812
+ function projectRow({ file }, { read, runAs }) {
8794
8813
  return {
8795
8814
  name: "project",
8796
8815
  run: (ctx) => {
8797
- if (!read.ok) return problemOfError("project", read.error);
8816
+ if (!read.ok) return problemOfError("project", read.error, runAs);
8798
8817
  return ok(read.project === void 0 ? `none found: no ${file} at or above ${ctx.cwd}` : join(read.project.root, file));
8799
8818
  }
8800
8819
  };
@@ -8834,20 +8853,24 @@ function registryRow(cli, check) {
8834
8853
  };
8835
8854
  }
8836
8855
  /** Every row `doctor` reports, in order: the fixed rows around the CLI's own and its tools'. */
8837
- function doctorChecks(cli, launch, read) {
8856
+ function doctorChecks(cli, launch, scope) {
8838
8857
  const install = installOf(cli);
8839
8858
  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
- ];
8859
+ const tools = toolRows(cli.tools ?? [], cli.commands);
8860
+ return {
8861
+ checks: [
8862
+ buildRow(cli),
8863
+ nodeRow(cli),
8864
+ ...cli.project === void 0 ? [] : [projectRow(cli.project, scope)],
8865
+ ...cli.doctor ?? [],
8866
+ ...tools,
8867
+ ...secretRows(cli.secrets ?? [], cli.commands),
8868
+ installRow(check),
8869
+ registryRow(cli, check),
8870
+ ...skillOf$1(cli, install).rows
8871
+ ],
8872
+ tools: new Set(tools.map(({ name }) => name))
8873
+ };
8851
8874
  }
8852
8875
  const DEFAULT_DEADLINE_MS = 1e4;
8853
8876
  const MS_PER_SECOND = 1e3;
@@ -8867,14 +8890,14 @@ const reportSchema = object({
8867
8890
  })),
8868
8891
  next: array(string())
8869
8892
  });
8870
- function problemOf(check, error, { signal, deadline, ms }) {
8893
+ function problemOf(check, error, { signal, deadline, ms, runAs }) {
8871
8894
  if (!signal.aborted && deadline.aborted && error === deadline.reason) return {
8872
8895
  state: "problem",
8873
8896
  message: `${check.name} did not answer within ${ms / MS_PER_SECOND} s`
8874
8897
  };
8875
- return problemOfError(check.name, error);
8898
+ return problemOfError(check.name, error, runAs);
8876
8899
  }
8877
- function rowContext(ctx, signal, read) {
8900
+ function rowContext(ctx, signal, { read }) {
8878
8901
  return Object.create(ctx, {
8879
8902
  signal: { value: signal },
8880
8903
  project: { get: () => {
@@ -8883,21 +8906,36 @@ function rowContext(ctx, signal, read) {
8883
8906
  } }
8884
8907
  });
8885
8908
  }
8886
- async function rowOf(check, ctx, read) {
8909
+ function skippedFor(error, { read, tools }) {
8910
+ if (!read.ok && error === read.error) return {
8911
+ state: "skipped",
8912
+ message: "the Project file is unusable; the project row says why"
8913
+ };
8914
+ const tool = missingToolOf(error);
8915
+ if (tool !== void 0 && tools.has(tool)) return {
8916
+ state: "skipped",
8917
+ message: `${tool} is not on PATH; the ${tool} row says why`
8918
+ };
8919
+ }
8920
+ async function rowOf(check, ctx, { read, runAs, tools }) {
8887
8921
  const ms = check.deadlineMs ?? DEFAULT_DEADLINE_MS;
8888
8922
  const signal = ctx.deadline(ms);
8889
8923
  let finding;
8890
8924
  try {
8891
- finding = await Promise.race([Promise.resolve().then(() => check.run(rowContext(ctx, signal, read))), untilAborted(signal)]);
8925
+ finding = await Promise.race([Promise.resolve().then(() => check.run(rowContext(ctx, signal, {
8926
+ read,
8927
+ runAs
8928
+ }))), untilAborted(signal)]);
8892
8929
  } catch (error) {
8893
8930
  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, {
8931
+ finding = skippedFor(error, {
8932
+ read,
8933
+ tools
8934
+ }) ?? problemOf(check, error, {
8898
8935
  signal: ctx.signal,
8899
8936
  deadline: signal,
8900
- ms
8937
+ ms,
8938
+ runAs
8901
8939
  });
8902
8940
  }
8903
8941
  return {
@@ -8908,8 +8946,10 @@ async function rowOf(check, ctx, read) {
8908
8946
  advisory: check.advisory === true
8909
8947
  };
8910
8948
  }
8911
- function nextOf(rows) {
8912
- return [...new Set(rows.flatMap(({ fix }) => fix === void 0 ? [] : [fix]))];
8949
+ function nextOf(checked, doctorLine) {
8950
+ const fixes = checked.flatMap(({ row }) => row.fix === void 0 ? [] : [row.fix]);
8951
+ const unfixed = checked.some(({ row, advisory }) => advisory && row.state === "problem" && row.fix === void 0);
8952
+ return [...new Set(unfixed ? [...fixes, doctorLine] : fixes)];
8913
8953
  }
8914
8954
  function render({ rows, next }, style) {
8915
8955
  const width = Math.max(MIN_LABEL_WIDTH, ...rows.map(({ check }) => check.length)) + 2;
@@ -8927,8 +8967,8 @@ function failure(cliName, rerun, problems) {
8927
8967
  };
8928
8968
  return runtimeError(fix === void 0 ? {
8929
8969
  ...head,
8930
- hint: "the row says what is wrong",
8931
- next: []
8970
+ hint: "fix what the row says, then run the Next step",
8971
+ next: [rerun]
8932
8972
  } : {
8933
8973
  ...head,
8934
8974
  hint: "run the Next steps in order",
@@ -8951,12 +8991,18 @@ function doctorCommand(cli, launch) {
8951
8991
  effect: "read",
8952
8992
  output: reportSchema,
8953
8993
  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);
8994
+ const rowsScope = {
8995
+ read: readProject(cli.project, ctx),
8996
+ runAs: runAsOf(cli)
8997
+ };
8998
+ const { checks, tools } = doctorChecks(cli, launch, rowsScope);
8999
+ const checked = (await Promise.all(checks.map((check) => rowOf(check, ctx, {
9000
+ ...rowsScope,
9001
+ tools
9002
+ })))).filter((row) => row !== void 0);
8957
9003
  const report = {
8958
- rows,
8959
- next: nextOf(rows)
9004
+ rows: checked.map(({ row }) => row),
9005
+ next: nextOf(checked, lineOf(ctx.rerun(), rowsScope.runAs))
8960
9006
  };
8961
9007
  if (ctx.signal.aborted) {
8962
9008
  ctx.deliver(report);
@@ -9226,18 +9272,26 @@ function installFrom(name, manifest, install) {
9226
9272
  mode: "dependency",
9227
9273
  package: manifest.name
9228
9274
  };
9229
- if (install !== "global") return {
9275
+ if (install === "global") return {
9276
+ mode: "global",
9277
+ package: manifest.name,
9278
+ registry: publishedTo(name, manifest)
9279
+ };
9280
+ if (install.mode === "global") return {
9281
+ mode: "global",
9282
+ package: manifest.name,
9283
+ registry: install.registry
9284
+ };
9285
+ return {
9230
9286
  mode: "dependency",
9231
9287
  package: manifest.name,
9232
9288
  lockstep: install.lockstep
9233
9289
  };
9290
+ }
9291
+ function publishedTo(name, manifest) {
9234
9292
  const registry = manifest.publishConfig?.registry;
9235
9293
  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
- };
9294
+ return registry;
9241
9295
  }
9242
9296
  function resolved(cli) {
9243
9297
  const { manifest, ...declared } = cli;
@@ -11290,7 +11344,7 @@ async function runProcess(cli) {
11290
11344
  //#region package.json
11291
11345
  var package_default = {
11292
11346
  name: "@duyquangnvx/create-agent-cli",
11293
- version: "0.20.0",
11347
+ version: "0.21.0",
11294
11348
  description: "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
11295
11349
  type: "module",
11296
11350
  engines: { "node": ">=22.12.0" },