@duyquangnvx/create-agent-cli 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -4,7 +4,7 @@ import { spawn } from "node:child_process";
4
4
  import { constants, homedir, tmpdir } from "node:os";
5
5
  import { parseArgs, parseEnv } from "node:util";
6
6
  import { constants as constants$1, existsSync, readFileSync, realpathSync } from "node:fs";
7
- import { access, chmod, link, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
7
+ import { access, chmod, link, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
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
@@ -5947,15 +5947,6 @@ function errnoOf(error) {
5947
5947
  function isMissingFile(error) {
5948
5948
  return errnoOf(error) === "ENOENT";
5949
5949
  }
5950
- /** What `pending` resolves with, or undefined when it fails because a file is missing. */
5951
- async function orMissing(pending) {
5952
- try {
5953
- return await pending;
5954
- } catch (error) {
5955
- if (isMissingFile(error)) return void 0;
5956
- throw error;
5957
- }
5958
- }
5959
5950
  function isNothingThere(error) {
5960
5951
  return isMissingFile(error) || errnoOf(error) === "ENOTDIR";
5961
5952
  }
@@ -5981,14 +5972,14 @@ function bugHint(format) {
5981
5972
  const rerun = `rerun with ${debugVariable(format.cliName)}=1 to see the stack trace`;
5982
5973
  return bugs === void 0 ? rerun : `${rerun}, then report it at ${bugs}`;
5983
5974
  }
5984
- function fieldsOf$1(error, format) {
5975
+ function fieldsOf$1({ error, exitClass }, format) {
5985
5976
  if (!(error instanceof CliError)) return {
5986
5977
  code: INTERNAL,
5987
5978
  message: messageOf(error),
5988
5979
  hint: bugHint(format)
5989
5980
  };
5990
5981
  const { code, message, hint, details, retryAt, next } = error;
5991
- if (format.isBug(error)) return {
5982
+ if (exitClass === "bug") return {
5992
5983
  code,
5993
5984
  message,
5994
5985
  hint: bugHint(format),
@@ -6012,9 +6003,9 @@ function traceOf(error) {
6012
6003
  return error instanceof Error ? error.stack ?? error.message : String(error);
6013
6004
  }
6014
6005
  /** Under debug: a bug's stack trace, then every error's cause chain. */
6015
- function debugTrace(error, format) {
6006
+ function debugTrace({ error, exitClass }, format) {
6016
6007
  if (!isDebug(format)) return void 0;
6017
- const lines = [...format.isBug(error) ? [traceOf(error)] : [], ...causesOf(error).map((cause) => `caused by: ${traceOf(cause)}`)];
6008
+ const lines = [...exitClass === "bug" ? [traceOf(error)] : [], ...causesOf(error).map((cause) => `caused by: ${traceOf(cause)}`)];
6018
6009
  return lines.length === 0 ? void 0 : lines.join("\n");
6019
6010
  }
6020
6011
  /**
@@ -6033,29 +6024,29 @@ function printedDetail(error, format) {
6033
6024
  const value = Reflect.get(details, key);
6034
6025
  return typeof value === "string" ? value : void 0;
6035
6026
  }
6036
- function labelOf(error, { isBug, isWait, style }) {
6037
- if (isWait(error)) return style.yellow("wait:");
6038
- return style.red(isBug(error) ? "internal error:" : "error:");
6027
+ function labelOf({ exitClass }, style) {
6028
+ if (exitClass === "wait") return style.yellow("wait:");
6029
+ return style.red(exitClass === "bug" ? "internal error:" : "error:");
6039
6030
  }
6040
- function errorLines(error, format) {
6041
- const fields = fieldsOf$1(error, format);
6042
- const blocks = [printedDetail(error, format), debugTrace(error, format)].filter((block) => block !== void 0);
6031
+ function errorLines(leaf, format) {
6032
+ const fields = fieldsOf$1(leaf, format);
6033
+ const blocks = [printedDetail(leaf.error, format), debugTrace(leaf, format)].filter((block) => block !== void 0);
6043
6034
  const { style } = format;
6044
- const label = labelOf(error, format);
6035
+ const label = labelOf(leaf, style);
6045
6036
  const between = blocks.map((block) => `${block}\n`).join("");
6046
6037
  const runs = (fields.next ?? []).map((line) => `${style.cyan("run:")} ${line}\n`).join("");
6047
6038
  return `${label} ${fields.message}\n${between}${style.cyan("hint:")} ${fields.hint}\n${runs}`;
6048
6039
  }
6049
- function formatRuntimeError(error, format) {
6050
- const errors = errorsOf(error);
6040
+ /** The classified errors a run failed with, as stderr carries them. */
6041
+ function formatRuntimeError(leaves, format) {
6051
6042
  if (format.json) {
6052
- const printed = errors.map((each) => ({
6053
- ...fieldsOf$1(each, format),
6054
- stack: debugTrace(each, format)
6043
+ const printed = leaves.map((leaf) => ({
6044
+ ...fieldsOf$1(leaf, format),
6045
+ stack: debugTrace(leaf, format)
6055
6046
  }));
6056
6047
  return `${JSON.stringify({ errors: printed })}\n`;
6057
6048
  }
6058
- return errors.map((each) => errorLines(each, format)).join("");
6049
+ return leaves.map((leaf) => errorLines(leaf, format)).join("");
6059
6050
  }
6060
6051
  //#endregion
6061
6052
  //#region ../agent-cli/dist/tail.js
@@ -6085,8 +6076,8 @@ const WINDOWS_EXTENSIONS = ".COM;.EXE;.BAT;.CMD";
6085
6076
  function pathOf(env) {
6086
6077
  return env[Object.keys(env).find((name) => name.toUpperCase() === "PATH") ?? "PATH"] ?? "";
6087
6078
  }
6088
- function extensionsOf(env) {
6089
- return process.platform === "win32" ? (env.PATHEXT ?? WINDOWS_EXTENSIONS).split(";") : [""];
6079
+ function extensionsOf({ env, platform }) {
6080
+ return platform === "win32" ? (env.PATHEXT ?? WINDOWS_EXTENSIONS).split(";") : [""];
6090
6081
  }
6091
6082
  async function isExecutable(file) {
6092
6083
  try {
@@ -6098,19 +6089,19 @@ async function isExecutable(file) {
6098
6089
  }
6099
6090
  }
6100
6091
  /** The file names `name` may have on `PATH`: itself when it already ends in one of the extensions. */
6101
- function fileNamesOf(name, env) {
6102
- const extensions = extensionsOf(env);
6092
+ function fileNamesOf(name, search) {
6093
+ const extensions = extensionsOf(search);
6103
6094
  const own = extname(name).toUpperCase();
6104
6095
  return own !== "" && extensions.some((extension) => extension.toUpperCase() === own) ? [name] : extensions.map((extension) => `${name}${extension}`);
6105
6096
  }
6106
- /** Where `name` is found on `PATH` from `env`, or undefined. */
6107
- async function findTool(name, env) {
6108
- const candidates = pathOf(env).split(delimiter).filter((folder) => folder !== "").flatMap((folder) => fileNamesOf(name, env).map((file) => join(folder, file)));
6097
+ /** Where `name` is found on `PATH`, or undefined. */
6098
+ async function findTool(name, search) {
6099
+ const candidates = pathOf(search.env).split(delimiter).filter((folder) => folder !== "").flatMap((folder) => fileNamesOf(name, search).map((file) => join(folder, file)));
6109
6100
  for (const file of candidates) if (await isExecutable(file)) return file;
6110
6101
  }
6111
6102
  /** Fails with `TOOL_MISSING`, naming every missing tool at once, unless all are on `PATH`. */
6112
- async function checkTools(tools, env, rerun) {
6113
- const found = await Promise.all(tools.map((tool) => findTool(tool.name, env)));
6103
+ async function checkTools(tools, search, rerun) {
6104
+ const found = await Promise.all(tools.map((tool) => findTool(tool.name, search)));
6114
6105
  const missing = tools.filter((_tool, index) => found[index] === void 0);
6115
6106
  if (missing.length === 0) return;
6116
6107
  const names = missing.map(({ name }) => name).join(", ");
@@ -6122,11 +6113,14 @@ async function checkTools(tools, env, rerun) {
6122
6113
  });
6123
6114
  }
6124
6115
  /** One `doctor` row per distinct tool the CLI and its Commands declare, with its install command as the fix. */
6125
- function toolRows(tools, commands) {
6116
+ function toolRows(tools, commands, platform) {
6126
6117
  return declaredIn(tools, commands, (command) => command.tools).map(({ name, install }) => ({
6127
6118
  name,
6128
6119
  run: async (ctx) => {
6129
- const file = await findTool(name, ctx.env);
6120
+ const file = await findTool(name, {
6121
+ env: ctx.env,
6122
+ platform
6123
+ });
6130
6124
  return file === void 0 ? {
6131
6125
  state: "problem",
6132
6126
  message: `${name} is not on PATH`,
@@ -6164,7 +6158,10 @@ function batchCommandLine(command, file, args) {
6164
6158
  return [escapedForCmd(command), ...args.map((arg) => batchArgument(arg, shim))].join(" ");
6165
6159
  }
6166
6160
  async function windowsLaunch(command, args, env) {
6167
- const file = PATH_SEPARATOR.test(command) ? command : await findTool(command, env);
6161
+ const file = PATH_SEPARATOR.test(command) ? command : await findTool(command, {
6162
+ env,
6163
+ platform: process.platform
6164
+ });
6168
6165
  if (file === void 0 || !BATCH_FILE.test(file)) return {
6169
6166
  command,
6170
6167
  args,
@@ -6928,188 +6925,42 @@ function checkForce(command, label) {
6928
6925
  if (force.trim() === "") throw new Error(`Command '${label}' declares an empty force; say what ${FORCE_FLAG.flag} replaces`);
6929
6926
  }
6930
6927
  //#endregion
6931
- //#region ../agent-cli/dist/define-command.js
6932
- /** Throws when the Command's declaration breaks a rule its type cannot express; `label` names it. */
6933
- function checkCommand(command, label, table) {
6934
- checkNoStandardFlags(command, label);
6935
- checkForce(command, label);
6936
- checkArrayArgLast(label, command.args);
6937
- checkStdinFields(label, command.args, command.options);
6938
- checkNoBooleanArrayOption(label, command.options);
6939
- checkErrors(command, label, table);
6940
- }
6941
- function checkErrors(command, label, table) {
6942
- const unknown = (command.errors ?? []).filter((code) => !Object.hasOwn(table, code));
6943
- if (unknown.length > 0) throw new Error(`${label} declares ${unknown.join(", ")} in errors, a code the Error table lacks`);
6944
- }
6945
- function defineCommandFor() {
6946
- return declared;
6947
- }
6948
- function declared(command) {
6949
- return command;
6950
- }
6951
- const defineCommand$1 = defineCommandFor();
6952
- //#endregion
6953
- //#region ../agent-cli/dist/context/project.js
6954
- const PROJECT_INVALID = "PROJECT_INVALID";
6955
- function invalid(path, problem, rerun, options) {
6956
- return new CliError({
6957
- code: PROJECT_INVALID,
6958
- message: `the Project file ${path} is invalid: ${problem}`,
6959
- hint: `fix ${path}, then run the Next step`,
6960
- next: [rerun]
6961
- }, options);
6962
- }
6963
- function readText(path, fileError) {
6964
- try {
6965
- return readFileSync(path, "utf8");
6966
- } catch (error) {
6967
- if (isMissingFile(error)) return void 0;
6968
- throw fileError(error, {
6969
- path,
6970
- access: "read"
6971
- });
6972
- }
6973
- }
6974
- function parsed(path, text, schema, rerun) {
6975
- let json;
6976
- try {
6977
- json = JSON.parse(text);
6978
- } catch (error) {
6979
- throw invalid(path, messageOf(error), rerun, { cause: error });
6980
- }
6981
- const result = schema.safeParse(json);
6982
- if (!result.success) throw invalid(path, prettifyError(result.error), rerun);
6983
- return result.data;
6984
- }
6985
- function projectFrom(projectFile, folder, scope) {
6986
- const path = join(folder, projectFile.file);
6987
- const text = readText(path, scope.fileError);
6988
- if (text !== void 0) return {
6989
- root: folder,
6990
- settings: parsed(path, text, projectFile.schema, scope.rerun)
6928
+ //#region ../agent-cli/dist/context/confirm.js
6929
+ const CONFIRMATION_REQUIRED = "CONFIRMATION_REQUIRED";
6930
+ const CONFIRMATION_MISMATCH = "CONFIRMATION_MISMATCH";
6931
+ function checkConfirmation({ rerun, commandLine, given }, expected) {
6932
+ if (given === expected) return;
6933
+ const advice = {
6934
+ hint: "once the user confirms the target, run the Next step",
6935
+ next: [rerun({
6936
+ remove: [CONFIRM_FLAG.flag],
6937
+ add: [CONFIRM_FLAG.flag, expected]
6938
+ })]
6991
6939
  };
6992
- const parent = dirname(folder);
6993
- return parent === folder ? void 0 : projectFrom(projectFile, parent, scope);
6994
- }
6995
- /** The nearest Project file at or above `cwd`, parsed by its schema; undefined when there is none. */
6996
- function projectOf(projectFile, cwd, scope) {
6997
- return projectFrom(projectFile, resolve(cwd), scope);
6940
+ if (given === void 0) throw new UsageError({
6941
+ code: CONFIRMATION_REQUIRED,
6942
+ message: `\`${commandLine}\` is destructive and needs ${CONFIRM_FLAG.flag}`,
6943
+ ...advice
6944
+ });
6945
+ throw new UsageError({
6946
+ code: CONFIRMATION_MISMATCH,
6947
+ message: `${CONFIRM_FLAG.flag} ${JSON.stringify(given)} does not match the target of \`${commandLine}\``,
6948
+ ...advice
6949
+ });
6998
6950
  }
6999
6951
  //#endregion
7000
- //#region ../agent-cli/dist/context/secrets.js
7001
- const SECRET_MISSING = "SECRET_MISSING";
7002
- /** `$XDG_CONFIG_HOME/<cli>/.env`, with `~/.config` when the variable is unset or empty. */
7003
- function envFileOf(cliName, env) {
7004
- const config = env.XDG_CONFIG_HOME || join(env.HOME || homedir(), ".config");
7005
- return join(config, cliName, ".env");
7006
- }
7007
- function readDotenv(path, fileError) {
7008
- let text;
7009
- try {
7010
- text = readFileSync(path, "utf8");
7011
- } catch (error) {
7012
- if (isMissingFile(error)) return {};
7013
- throw fileError(error, {
7014
- path,
7015
- access: "read"
7016
- });
7017
- }
7018
- return parseEnv(text);
7019
- }
7020
- /** Looks `name` up in the env, then in `envFile`; an empty value counts as unset. */
7021
- function secretOf({ envFile, env, fileError }, name) {
7022
- const fromEnv = env[name];
7023
- if (fromEnv) return {
7024
- value: fromEnv,
7025
- origin: { kind: "env" }
7026
- };
7027
- const fromFile = readDotenv(envFile, fileError)[name];
7028
- return fromFile ? {
7029
- value: fromFile,
7030
- origin: {
7031
- kind: "file",
7032
- path: envFile
7033
- }
7034
- } : void 0;
7035
- }
7036
- function isNeeded({ optional }) {
7037
- return optional !== true;
7038
- }
7039
- function missingIn(scope, secrets) {
7040
- return secrets.filter((secret) => isNeeded(secret) && secretOf(scope, secret.name) === void 0);
7041
- }
7042
- function getItFrom({ get }) {
7043
- return `(get it from ${get})`;
7044
- }
7045
- function whereToGet(secrets) {
7046
- return secrets.map((secret) => `${secret.name} ${getItFrom(secret)}`).join(", ");
7047
- }
7048
- /** Fails with `SECRET_MISSING`, naming every missing Secret at once, unless all are set. */
7049
- function checkSecrets(secrets, scope, rerun) {
7050
- const missing = missingIn(scope, secrets);
7051
- if (missing.length === 0) return;
7052
- const names = missing.map(({ name }) => name).join(", ");
6952
+ //#region ../agent-cli/dist/context/cost.js
6953
+ const NOT_CONFIRMED = "NOT_CONFIRMED";
6954
+ /** Refuses a run whose Plan states a cost the caller has not approved with `--yes`. */
6955
+ function checkCost({ rerun, commandLine, yes }, cost) {
6956
+ if (cost === void 0 || yes) return;
7053
6957
  throw new CliError({
7054
- code: SECRET_MISSING,
7055
- message: `${missing.length === 1 ? "a Secret it needs is" : "Secrets it needs are"} not set: ${names}`,
7056
- hint: `set ${whereToGet(missing)} in the environment or in ${scope.envFile}, then run the Next step`,
7057
- next: [rerun]
6958
+ code: NOT_CONFIRMED,
6959
+ message: `\`${commandLine}\` needs ${YES_FLAG.flag}: ${cost}`,
6960
+ hint: "once the user approves the cost, run the Next step",
6961
+ next: [rerun({ add: [YES_FLAG.flag] })]
7058
6962
  });
7059
6963
  }
7060
- function originOf({ origin }) {
7061
- return origin.kind === "env" ? "the environment" : origin.path;
7062
- }
7063
- /**
7064
- * The `doctor` row over every distinct Secret the CLI and its Commands declare; none without any. A
7065
- * name needed anywhere counts as needed, and an unset optional one goes unmentioned.
7066
- */
7067
- function secretRows(secrets, commands) {
7068
- const declared = declaredIn(secrets, commands, (command) => command.secrets);
7069
- if (declared.length === 0) return [];
7070
- const needed = new Map(declaredIn(secrets.filter(isNeeded), commands, (command) => command.secrets?.filter(isNeeded)).map((declaration) => [declaration.name, declaration]));
7071
- return [{
7072
- name: "secrets",
7073
- run: ({ env, envFile, fileError }) => {
7074
- const read = declared.map((declaration) => ({
7075
- declaration,
7076
- secret: secretOf({
7077
- env,
7078
- envFile,
7079
- fileError
7080
- }, declaration.name)
7081
- }));
7082
- const missing = read.flatMap(({ declaration, secret }) => {
7083
- const neededAs = needed.get(declaration.name);
7084
- return secret === void 0 && neededAs !== void 0 ? [neededAs] : [];
7085
- });
7086
- if (missing.length > 0) return {
7087
- state: "problem",
7088
- message: `${missing.map((declaration) => `${declaration.name} is not set ${getItFrom(declaration)}`).join(", ")}; add ${missing.length === 1 ? "it" : "them"} to ${envFile} or the environment`
7089
- };
7090
- const found = read.flatMap(({ declaration, secret }) => secret === void 0 ? [] : [`${declaration.name} from ${originOf(secret)}`]);
7091
- return {
7092
- state: "ok",
7093
- message: found.length > 0 ? found.join(", ") : "no Secret is set, and none is needed"
7094
- };
7095
- }
7096
- }];
7097
- }
7098
- //#endregion
7099
- //#region ../agent-cli/dist/version.js
7100
- /** The bundler's stamp, absent when the CLI runs unbundled. Read per call, so a test can define it. */
7101
- function buildStamp() {
7102
- return {
7103
- "sha": "8ba1668",
7104
- "dirty": false,
7105
- "builtAt": "2026-11-08T16:12:26.000Z"
7106
- };
7107
- }
7108
- /** The first line of `--version`: the version, and the commit and time of a stamped build. */
7109
- function versionLine({ version }) {
7110
- const build = buildStamp();
7111
- return build === void 0 ? version : `${version} (${build.sha}${build.dirty ? "+dirty" : ""}) built ${build.builtAt}`;
7112
- }
7113
6964
  //#endregion
7114
6965
  //#region ../agent-cli/dist/context/agent.js
7115
6966
  const AGENT_NAME = /^[a-zA-Z0-9_-]+$/u;
@@ -7145,33 +6996,772 @@ function isAgent(env) {
7145
6996
  return MARKERS.some((marker) => marker(env));
7146
6997
  }
7147
6998
  //#endregion
7148
- //#region ../agent-cli/dist/built-ins/semver.js
7149
- const VERSION = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
7150
- const ENGINE = /^>=\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?$/;
7151
- function isVersion(text) {
7152
- return VERSION.test(text);
7153
- }
7154
- function compareIdentifiers(x, y) {
7155
- const xNumeric = /^\d+$/.test(x);
7156
- const yNumeric = /^\d+$/.test(y);
7157
- if (xNumeric && yNumeric) return Number(x) - Number(y);
7158
- if (xNumeric !== yNumeric) return xNumeric ? -1 : 1;
7159
- if (x === y) return 0;
7160
- return x < y ? -1 : 1;
6999
+ //#region ../agent-cli/dist/context/errno.js
7000
+ const PERMISSION_DENIED = "PERMISSION_DENIED";
7001
+ const DISK_FULL = "DISK_FULL";
7002
+ const NOT_FOUND = "NOT_FOUND";
7003
+ const ALREADY_EXISTS = "ALREADY_EXISTS";
7004
+ const NOT_A_FILE = "NOT_A_FILE";
7005
+ function permissionAdvice({ path, access, rerun, env, platform }) {
7006
+ if (platform === "win32") return {
7007
+ hint: `a program may hold ${path} open: close it, then run the Next step`,
7008
+ next: [rerun]
7009
+ };
7010
+ if (!isAgent(env)) return { hint: `check the permissions of ${path} and its folder` };
7011
+ return {
7012
+ hint: `your sandbox may block it: ask your user to type ! then the Next step, or ${access === "read" ? `to allow reading ${path} in the sandbox` : `to allow ${path} in the sandbox's write list`}`,
7013
+ next: [rerun]
7014
+ };
7161
7015
  }
7162
- function comparePrerelease(a, b) {
7163
- if (a === b) return 0;
7164
- if (a === void 0) return 1;
7165
- if (b === void 0) return -1;
7166
- const left = a.split(".");
7167
- const right = b.split(".");
7168
- for (let i = 0; i < Math.min(left.length, right.length); i += 1) {
7169
- const difference = compareIdentifiers(left[i] ?? "", right[i] ?? "");
7170
- if (difference !== 0) return difference;
7171
- }
7172
- return left.length - right.length;
7016
+ function fileErrorOf(scope) {
7017
+ return (error, at) => classifyErrno(error, {
7018
+ ...at,
7019
+ ...scope
7020
+ });
7173
7021
  }
7174
- /** Negative when `a` is older than `b`, positive when newer, 0 when the same release. */
7022
+ function classifyErrno(error, context) {
7023
+ const { path } = context;
7024
+ switch (errnoOf(error)) {
7025
+ case "EACCES":
7026
+ case "EPERM":
7027
+ case "EROFS":
7028
+ case "EBUSY": return new CliError({
7029
+ code: PERMISSION_DENIED,
7030
+ message: `permission denied: ${path}`,
7031
+ ...permissionAdvice(context)
7032
+ }, { cause: error });
7033
+ case "ENOSPC":
7034
+ case "EDQUOT": return new CliError({
7035
+ code: DISK_FULL,
7036
+ message: `no space left on the disk holding ${path}`,
7037
+ hint: "free space on that disk, then run the Next step",
7038
+ next: [context.rerun]
7039
+ }, { cause: error });
7040
+ case "ENOENT":
7041
+ case "ENOTDIR":
7042
+ case "ENAMETOOLONG": return new CliError({
7043
+ code: NOT_FOUND,
7044
+ message: `not found: ${path}`,
7045
+ hint: `check that ${path} and its folder exist`
7046
+ }, { cause: error });
7047
+ case "EEXIST": return new CliError({
7048
+ code: ALREADY_EXISTS,
7049
+ message: `already exists: ${path}`,
7050
+ hint: `pass another path, or remove ${path}`
7051
+ }, { cause: error });
7052
+ case "EISDIR": return new CliError({
7053
+ code: NOT_A_FILE,
7054
+ message: `not a file: ${path} is a folder`,
7055
+ hint: `pass the path of a file, not of the folder ${path}`
7056
+ }, { cause: error });
7057
+ default: return error;
7058
+ }
7059
+ }
7060
+ //#endregion
7061
+ //#region ../agent-cli/dist/context/interrupts.js
7062
+ const INTERRUPTED = "INTERRUPTED";
7063
+ const DEADLINE_EXCEEDED = "DEADLINE_EXCEEDED";
7064
+ /** How long an interrupted Command may take to settle before the run ends without it. */
7065
+ const GRACE_MS = 3e3;
7066
+ const GRACE_OVER = Symbol("grace over");
7067
+ function interruptedError(rerun) {
7068
+ return new CliError({
7069
+ code: INTERRUPTED,
7070
+ message: "interrupted before it finished",
7071
+ hint: "to finish it, run the Next step, unless it was stopped on purpose; if a tool timeout stopped it, give it longer",
7072
+ next: [rerun]
7073
+ });
7074
+ }
7075
+ const BOUND_WORDS = {
7076
+ deadline: "deadline",
7077
+ ignoreStop: "timeout"
7078
+ };
7079
+ function waitHint(bound) {
7080
+ return bound === "ignoreStop" ? "the call may have been billed: ask your user before running the Next step" : "run the Next step later, when the service it waits on may answer faster";
7081
+ }
7082
+ function deadlineError(commandLine, ms, advice, bound = "deadline") {
7083
+ return new CliError({
7084
+ code: DEADLINE_EXCEEDED,
7085
+ message: `${commandLine} ran past its ${ms} ms ${BOUND_WORDS[bound]}`,
7086
+ ...advice
7087
+ });
7088
+ }
7089
+ /** One Command run's cancellation: its signal, its deadlines and the steps it must finish. */
7090
+ var RunControl = class {
7091
+ signal;
7092
+ #controller = new AbortController();
7093
+ #commandLine;
7094
+ #rerun;
7095
+ #outer;
7096
+ #onOuterAbort = () => this.#controller.abort(interruptedError(this.#rerun));
7097
+ /** Each running must-finish step, tracked until it settles, with its name. */
7098
+ #steps = /* @__PURE__ */ new Map();
7099
+ #progress;
7100
+ #clock;
7101
+ #disposed = new AbortController();
7102
+ /** The reasons this run's deadlines aborted with, to tell them in a Command's error. */
7103
+ #abortReasons = /* @__PURE__ */ new WeakSet();
7104
+ #graceCancel = new AbortController();
7105
+ #endGrace = () => {};
7106
+ #graceOver = new Promise((resolve) => {
7107
+ this.#endGrace = resolve;
7108
+ });
7109
+ constructor({ commandLine, rerun, outer, clock, progress, runDeadline }) {
7110
+ this.#commandLine = commandLine;
7111
+ this.#rerun = rerun;
7112
+ this.#outer = outer;
7113
+ this.#clock = clock;
7114
+ this.#progress = progress;
7115
+ this.signal = this.#controller.signal;
7116
+ this.signal.addEventListener("abort", () => this.#onStop(), { once: true });
7117
+ if (outer?.aborted === true) this.#onOuterAbort();
7118
+ else outer?.addEventListener("abort", this.#onOuterAbort, { once: true });
7119
+ if (runDeadline !== void 0) {
7120
+ const { ms, advice } = runDeadline;
7121
+ after(clock, ms, this.#disposed.signal, () => this.#controller.abort(deadlineError(commandLine, ms, advice())));
7122
+ }
7123
+ }
7124
+ deadline(ms, options = {}) {
7125
+ return options.ignoreStop === true ? this.#abortsAfter(ms, "ignoreStop") : AbortSignal.any([this.signal, this.#abortsAfter(ms, "deadline")]);
7126
+ }
7127
+ #abortsAfter(ms, bound) {
7128
+ const controller = new AbortController();
7129
+ after(this.#clock, ms, this.#disposed.signal, () => {
7130
+ const advice = {
7131
+ hint: waitHint(bound),
7132
+ next: [this.#rerun]
7133
+ };
7134
+ const reason = deadlineError(this.#commandLine, ms, advice, bound);
7135
+ this.#abortReasons.add(reason);
7136
+ controller.abort(reason);
7137
+ });
7138
+ return controller.signal;
7139
+ }
7140
+ async runToEnd(name, work) {
7141
+ this.#graceCancel.abort();
7142
+ if (this.signal.aborted) this.#finishing(name);
7143
+ const step = work();
7144
+ const settled = step.then(() => {}, () => {});
7145
+ this.#steps.set(settled, name);
7146
+ try {
7147
+ return await step;
7148
+ } finally {
7149
+ this.#steps.delete(settled);
7150
+ this.#startGrace();
7151
+ }
7152
+ }
7153
+ /** Waits for the Command, its must-finish steps, and no longer than the grace once interrupted. */
7154
+ async settle(work) {
7155
+ try {
7156
+ const value = await Promise.race([work, this.#graceOver]);
7157
+ if (this.signal.aborted || value === GRACE_OVER) throw this.signal.reason;
7158
+ return value;
7159
+ } finally {
7160
+ await this.#stepsDone();
7161
+ this.close();
7162
+ }
7163
+ }
7164
+ /**
7165
+ * The error a run ends with: an interrupt or the run deadline outranks what the Command threw,
7166
+ * unless the Command threw its own `INTERRUPTED`, saying what it finished before it stopped.
7167
+ * Otherwise each leaf that is not a `CliError` and whose cause chain holds a deadline's abort is
7168
+ * reported as that abort, so a declared error keeps its own code.
7169
+ */
7170
+ errorAfter(error) {
7171
+ if (this.signal.aborted) return error instanceof CliError && error.code === "INTERRUPTED" ? error : this.signal.reason;
7172
+ const leaves = errorsOf(error).map((leaf) => this.#abortIn(leaf) ?? leaf);
7173
+ return leaves.length === 1 ? leaves[0] : new AggregateError(leaves);
7174
+ }
7175
+ #abortIn(error) {
7176
+ if (error instanceof CliError) return void 0;
7177
+ return causesOf(error).find((each) => each instanceof CliError && this.#abortReasons.has(each));
7178
+ }
7179
+ #onStop() {
7180
+ for (const name of this.#steps.values()) this.#finishing(name);
7181
+ this.#startGrace();
7182
+ }
7183
+ #finishing(name) {
7184
+ this.#progress(`finishing ${name}`);
7185
+ }
7186
+ #startGrace() {
7187
+ if (this.signal.aborted && this.#steps.size === 0) {
7188
+ this.#graceCancel.abort();
7189
+ this.#graceCancel = new AbortController();
7190
+ after(this.#clock, GRACE_MS, this.#graceCancel.signal, () => this.#endGrace(GRACE_OVER));
7191
+ }
7192
+ }
7193
+ async #stepsDone() {
7194
+ while (this.#steps.size > 0) await Promise.all(this.#steps.keys());
7195
+ }
7196
+ /** Stops the run's timers and its hold on the caller's signal, for a run that never settles. */
7197
+ close() {
7198
+ this.#graceCancel.abort();
7199
+ this.#disposed.abort();
7200
+ this.#outer?.removeEventListener("abort", this.#onOuterAbort);
7201
+ }
7202
+ };
7203
+ //#endregion
7204
+ //#region ../agent-cli/dist/context/operator.js
7205
+ const NEEDS_OPERATOR = "NEEDS_OPERATOR";
7206
+ /** Fails with `NEEDS_OPERATOR` when a Coding agent runs an Operator Command. */
7207
+ function checkOperator({ operator, commandLine }, env, rerun) {
7208
+ if (!operator || !isAgent(env)) return;
7209
+ throw new CliError({
7210
+ code: NEEDS_OPERATOR,
7211
+ message: `\`${commandLine}\` must be run by a person at a terminal, not a Coding agent`,
7212
+ hint: "ask the operator to type ! then the Next step",
7213
+ next: [rerun]
7214
+ });
7215
+ }
7216
+ //#endregion
7217
+ //#region ../agent-cli/dist/context/files.js
7218
+ const TEMP_FILE = /^\..+\.agent-cli-(\d+)\.tmp$/;
7219
+ /** Where the Runtime writes `path` before moving it into place; a dead writer's is swept. */
7220
+ function tempPathOf(path) {
7221
+ return join(dirname(path), `.${basename(path)}.agent-cli-${process.pid}.tmp`);
7222
+ }
7223
+ function isAlive(pid) {
7224
+ try {
7225
+ process.kill(pid, 0);
7226
+ return true;
7227
+ } catch (error) {
7228
+ return errnoOf(error) === "EPERM";
7229
+ }
7230
+ }
7231
+ async function sweep(folder) {
7232
+ const dead = (await readdir(folder)).filter((name) => {
7233
+ const pid = TEMP_FILE.exec(name)?.[1];
7234
+ return pid !== void 0 && !isAlive(Number(pid));
7235
+ });
7236
+ await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
7237
+ }
7238
+ /** The file a symlink at `path` points to, so a replacing write keeps the link. */
7239
+ async function targetOf$1(path) {
7240
+ try {
7241
+ return await realpath(path);
7242
+ } catch (error) {
7243
+ if (isMissingFile(error)) return path;
7244
+ throw error;
7245
+ }
7246
+ }
7247
+ async function writeWithMode(path, data, mode) {
7248
+ if (mode === void 0) {
7249
+ await writeFile(path, data);
7250
+ return;
7251
+ }
7252
+ await writeFile(path, data, { mode });
7253
+ await chmod(path, mode);
7254
+ }
7255
+ /** What `link` answers on a file system without hard links, such as exFAT. */
7256
+ const NO_HARD_LINKS = /* @__PURE__ */ new Set([
7257
+ "EPERM",
7258
+ "ENOTSUP",
7259
+ "EXDEV"
7260
+ ]);
7261
+ /** A link fails atomically when `path` is taken, so no reader ever sees half a file there. */
7262
+ async function linkNew(temp, path, data, mode) {
7263
+ try {
7264
+ await link(temp, path);
7265
+ } catch (error) {
7266
+ if (!NO_HARD_LINKS.has(errnoOf(error))) throw error;
7267
+ await createNew(path, data, mode);
7268
+ }
7269
+ }
7270
+ /** A file this call created and could not finish is removed, so a rerun does not find half of it. */
7271
+ async function createNew(path, data, mode) {
7272
+ const handle = await open(path, "wx", mode);
7273
+ try {
7274
+ await handle.writeFile(data);
7275
+ if (mode !== void 0) await handle.chmod(mode);
7276
+ } catch (error) {
7277
+ await handle.close();
7278
+ await rm(path, { force: true });
7279
+ throw error;
7280
+ }
7281
+ await handle.close();
7282
+ }
7283
+ const RENAME_ATTEMPTS = 10;
7284
+ const RENAME_BACKOFF_MS = 10;
7285
+ const HELD_BY_ANOTHER_PROGRAM = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
7286
+ async function renameRetrying(from, to) {
7287
+ for (let attempt = 1;; attempt++) try {
7288
+ await rename(from, to);
7289
+ return;
7290
+ } catch (error) {
7291
+ if (!(process.platform === "win32" && HELD_BY_ANOTHER_PROGRAM.has(errnoOf(error))) || attempt === RENAME_ATTEMPTS) throw error;
7292
+ await setTimeout$1(attempt * RENAME_BACKOFF_MS);
7293
+ }
7294
+ }
7295
+ /** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
7296
+ function realFiles() {
7297
+ const swept = /* @__PURE__ */ new Set();
7298
+ return {
7299
+ exists: async (path) => {
7300
+ try {
7301
+ await access(path);
7302
+ return true;
7303
+ } catch (error) {
7304
+ if (isNothingThere(error)) return false;
7305
+ throw error;
7306
+ }
7307
+ },
7308
+ write: async (path, data, options = {}) => {
7309
+ await mkdir(dirname(path), { recursive: true });
7310
+ const target = options.exclusive === true ? path : await targetOf$1(path);
7311
+ const folder = dirname(target);
7312
+ if (!swept.has(folder)) {
7313
+ await sweep(folder);
7314
+ swept.add(folder);
7315
+ }
7316
+ const temp = tempPathOf(target);
7317
+ try {
7318
+ await writeWithMode(temp, data, options.mode);
7319
+ if (options.exclusive === true) {
7320
+ await linkNew(temp, target, data, options.mode);
7321
+ await rm(temp);
7322
+ } else await renameRetrying(temp, target);
7323
+ } catch (error) {
7324
+ await rm(temp, { force: true });
7325
+ throw error;
7326
+ }
7327
+ },
7328
+ read: (path) => readFile(path),
7329
+ list: (folder) => readdir(folder),
7330
+ remove: (path) => rm(path, {
7331
+ recursive: true,
7332
+ force: true
7333
+ }),
7334
+ mkdir: async (path, options) => {
7335
+ const mode = options?.mode;
7336
+ if (options?.exclusive !== true) {
7337
+ await mkdir(path, {
7338
+ recursive: true,
7339
+ mode
7340
+ });
7341
+ return;
7342
+ }
7343
+ await mkdir(dirname(path), {
7344
+ recursive: true,
7345
+ mode
7346
+ });
7347
+ await mkdir(path, { mode });
7348
+ },
7349
+ stat: async (path) => {
7350
+ try {
7351
+ const found = await stat(path, { bigint: true });
7352
+ return {
7353
+ size: Number(found.size),
7354
+ id: `${found.dev}:${found.ino}`,
7355
+ folder: found.isDirectory()
7356
+ };
7357
+ } catch (error) {
7358
+ if (isNothingThere(error)) return void 0;
7359
+ throw error;
7360
+ }
7361
+ },
7362
+ realPath: async (path) => {
7363
+ try {
7364
+ return await realpath(path);
7365
+ } catch (error) {
7366
+ if (isNothingThere(error)) return void 0;
7367
+ throw error;
7368
+ }
7369
+ }
7370
+ };
7371
+ }
7372
+ /**
7373
+ * `files` resolving paths against `cwd`, its failures classified by errno; `list` drops the
7374
+ * Runtime's temp files and sorts.
7375
+ */
7376
+ function classifiedFiles(files, { cwd, fileError }) {
7377
+ async function at(access, path, work) {
7378
+ const target = resolve(cwd, path);
7379
+ try {
7380
+ return await work(target);
7381
+ } catch (error) {
7382
+ throw fileError(error, {
7383
+ path: target,
7384
+ access
7385
+ });
7386
+ }
7387
+ }
7388
+ return {
7389
+ exists: (path) => at("read", path, (target) => files.exists(target)),
7390
+ write: (path, data, options) => at("write", path, (target) => files.write(target, data, options)),
7391
+ read: (path) => at("read", path, (target) => files.read(target)),
7392
+ list: (folder) => at("read", folder, async (target) => (await files.list(target)).filter((name) => !TEMP_FILE.test(name)).sort()),
7393
+ remove: (path) => at("write", path, (target) => files.remove(target)),
7394
+ mkdir: (path, options) => at("write", path, (target) => files.mkdir(target, options)),
7395
+ stat: (path) => at("read", path, (target) => files.stat(target)),
7396
+ realPath: (path) => at("read", path, (target) => files.realPath(target))
7397
+ };
7398
+ }
7399
+ //#endregion
7400
+ //#region ../agent-cli/dist/context/outputs.js
7401
+ const OUTPUT_EXISTS = "OUTPUT_EXISTS";
7402
+ /** The outputs of a run on `files`, whose paths resolve against `cwd`. */
7403
+ function outputsOf(files, { cwd, fileError }) {
7404
+ const classified = classifiedFiles(files, {
7405
+ cwd,
7406
+ fileError
7407
+ });
7408
+ const unwritten = /* @__PURE__ */ new Set();
7409
+ let forceStep = [];
7410
+ const exclusiveWrite = async (path, data, options) => {
7411
+ const target = resolve(cwd, path);
7412
+ unwritten.delete(target);
7413
+ try {
7414
+ await classified.write(path, data, {
7415
+ ...options,
7416
+ exclusive: true
7417
+ });
7418
+ } catch (error) {
7419
+ unwritten.add(target);
7420
+ if (!(error instanceof CliError) || error.code !== "ALREADY_EXISTS") throw error;
7421
+ throw new CliError({
7422
+ code: ALREADY_EXISTS,
7423
+ message: error.message,
7424
+ hint: "once the user agrees to replace it, run the Next step",
7425
+ next: [forceStep]
7426
+ }, { cause: error });
7427
+ }
7428
+ };
7429
+ const guarded = {
7430
+ ...classified,
7431
+ write: (path, data, options) => unwritten.has(resolve(cwd, path)) ? exclusiveWrite(path, data, options) : classified.write(path, data, options)
7432
+ };
7433
+ return {
7434
+ files: guarded,
7435
+ refuseOverwrite: async (outputs, { rerun, commandLine, force }) => {
7436
+ if (force) return;
7437
+ const found = await Promise.all(outputs.map((path) => guarded.exists(path)));
7438
+ const existing = outputs.filter((_path, index) => found[index]);
7439
+ if (existing.length === 0) return;
7440
+ const them = existing.length === 1 ? "it" : "them";
7441
+ throw new CliError({
7442
+ code: OUTPUT_EXISTS,
7443
+ message: `\`${commandLine}\` would overwrite ${existing.join(", ")}`,
7444
+ hint: `once the user agrees to replace ${them}, run the Next step`,
7445
+ next: [rerun({ add: [FORCE_FLAG.flag] })]
7446
+ });
7447
+ },
7448
+ claim: async (outputs, { rerun, force }) => {
7449
+ await probe(outputs, files, {
7450
+ cwd,
7451
+ fileError
7452
+ });
7453
+ if (force) return;
7454
+ forceStep = rerun({ add: [FORCE_FLAG.flag] });
7455
+ for (const path of outputs) unwritten.add(resolve(cwd, path));
7456
+ }
7457
+ };
7458
+ }
7459
+ async function probe(outputs, files, { cwd, fileError }) {
7460
+ const folders = new Set(outputs.map((path) => dirname(resolve(cwd, path))));
7461
+ await Promise.all([...folders].map(async (folder) => {
7462
+ const probePath = tempPathOf(join(folder, "outputs-probe"));
7463
+ try {
7464
+ await files.mkdir(folder);
7465
+ await files.write(probePath, "");
7466
+ await files.remove(probePath);
7467
+ } catch (error) {
7468
+ throw fileError(error, {
7469
+ path: folder,
7470
+ access: "write"
7471
+ });
7472
+ }
7473
+ }));
7474
+ }
7475
+ //#endregion
7476
+ //#region ../agent-cli/dist/context/project.js
7477
+ const PROJECT_INVALID = "PROJECT_INVALID";
7478
+ function invalid(path, problem, rerun, options) {
7479
+ return new CliError({
7480
+ code: PROJECT_INVALID,
7481
+ message: `the Project file ${path} is invalid: ${problem}`,
7482
+ hint: `fix ${path}, then run the Next step`,
7483
+ next: [rerun]
7484
+ }, options);
7485
+ }
7486
+ function readText(path, fileError) {
7487
+ try {
7488
+ return readFileSync(path, "utf8");
7489
+ } catch (error) {
7490
+ if (isMissingFile(error)) return void 0;
7491
+ throw fileError(error, {
7492
+ path,
7493
+ access: "read"
7494
+ });
7495
+ }
7496
+ }
7497
+ function parsed(path, text, schema, rerun) {
7498
+ let json;
7499
+ try {
7500
+ json = JSON.parse(text);
7501
+ } catch (error) {
7502
+ throw invalid(path, messageOf(error), rerun, { cause: error });
7503
+ }
7504
+ const result = schema.safeParse(json);
7505
+ if (!result.success) throw invalid(path, prettifyError(result.error), rerun);
7506
+ return result.data;
7507
+ }
7508
+ function projectFrom(projectFile, folder, scope) {
7509
+ const path = join(folder, projectFile.file);
7510
+ const text = readText(path, scope.fileError);
7511
+ if (text !== void 0) return {
7512
+ root: folder,
7513
+ settings: parsed(path, text, projectFile.schema, scope.rerun)
7514
+ };
7515
+ const parent = dirname(folder);
7516
+ return parent === folder ? void 0 : projectFrom(projectFile, parent, scope);
7517
+ }
7518
+ /** The nearest Project file at or above `cwd`, parsed by its schema; undefined when there is none. */
7519
+ function projectOf(projectFile, cwd, scope) {
7520
+ return projectFrom(projectFile, resolve(cwd), scope);
7521
+ }
7522
+ //#endregion
7523
+ //#region ../agent-cli/dist/context/secrets.js
7524
+ const SECRET_MISSING = "SECRET_MISSING";
7525
+ /** `$XDG_CONFIG_HOME/<cli>/.env`, with `~/.config` when the variable is unset or empty. */
7526
+ function envFileOf(cliName, env) {
7527
+ const config = env.XDG_CONFIG_HOME || join(env.HOME || homedir(), ".config");
7528
+ return join(config, cliName, ".env");
7529
+ }
7530
+ function readDotenv(path, fileError) {
7531
+ let text;
7532
+ try {
7533
+ text = readFileSync(path, "utf8");
7534
+ } catch (error) {
7535
+ if (isMissingFile(error)) return {};
7536
+ throw fileError(error, {
7537
+ path,
7538
+ access: "read"
7539
+ });
7540
+ }
7541
+ return parseEnv(text);
7542
+ }
7543
+ /** Looks `name` up in the env, then in `envFile`; an empty value counts as unset. */
7544
+ function secretOf({ envFile, env, fileError }, name) {
7545
+ const fromEnv = env[name];
7546
+ if (fromEnv) return {
7547
+ value: fromEnv,
7548
+ origin: { kind: "env" }
7549
+ };
7550
+ const fromFile = readDotenv(envFile, fileError)[name];
7551
+ return fromFile ? {
7552
+ value: fromFile,
7553
+ origin: {
7554
+ kind: "file",
7555
+ path: envFile
7556
+ }
7557
+ } : void 0;
7558
+ }
7559
+ function isNeeded({ optional }) {
7560
+ return optional !== true;
7561
+ }
7562
+ function missingIn(scope, secrets) {
7563
+ return secrets.filter((secret) => isNeeded(secret) && secretOf(scope, secret.name) === void 0);
7564
+ }
7565
+ function getItFrom({ get }) {
7566
+ return `(get it from ${get})`;
7567
+ }
7568
+ function whereToGet(secrets) {
7569
+ return secrets.map((secret) => `${secret.name} ${getItFrom(secret)}`).join(", ");
7570
+ }
7571
+ /** Fails with `SECRET_MISSING`, naming every missing Secret at once, unless all are set. */
7572
+ function checkSecrets(secrets, scope, rerun) {
7573
+ const missing = missingIn(scope, secrets);
7574
+ if (missing.length === 0) return;
7575
+ const names = missing.map(({ name }) => name).join(", ");
7576
+ throw new CliError({
7577
+ code: SECRET_MISSING,
7578
+ message: `${missing.length === 1 ? "a Secret it needs is" : "Secrets it needs are"} not set: ${names}`,
7579
+ hint: `set ${whereToGet(missing)} in the environment or in ${scope.envFile}, then run the Next step`,
7580
+ next: [rerun]
7581
+ });
7582
+ }
7583
+ function originOf({ origin }) {
7584
+ return origin.kind === "env" ? "the environment" : origin.path;
7585
+ }
7586
+ /**
7587
+ * The `doctor` row over every distinct Secret the CLI and its Commands declare; none without any. A
7588
+ * name needed anywhere counts as needed, and an unset optional one goes unmentioned.
7589
+ */
7590
+ function secretRows(secrets, commands) {
7591
+ const declared = declaredIn(secrets, commands, (command) => command.secrets);
7592
+ if (declared.length === 0) return [];
7593
+ const needed = new Map(declaredIn(secrets.filter(isNeeded), commands, (command) => command.secrets?.filter(isNeeded)).map((declaration) => [declaration.name, declaration]));
7594
+ return [{
7595
+ name: "secrets",
7596
+ run: ({ env, envFile, fileError }) => {
7597
+ const read = declared.map((declaration) => ({
7598
+ declaration,
7599
+ secret: secretOf({
7600
+ env,
7601
+ envFile,
7602
+ fileError
7603
+ }, declaration.name)
7604
+ }));
7605
+ const missing = read.flatMap(({ declaration, secret }) => {
7606
+ const neededAs = needed.get(declaration.name);
7607
+ return secret === void 0 && neededAs !== void 0 ? [neededAs] : [];
7608
+ });
7609
+ if (missing.length > 0) return {
7610
+ state: "problem",
7611
+ message: `${missing.map((declaration) => `${declaration.name} is not set ${getItFrom(declaration)}`).join(", ")}; add ${missing.length === 1 ? "it" : "them"} to ${envFile} or the environment`
7612
+ };
7613
+ const found = read.flatMap(({ declaration, secret }) => secret === void 0 ? [] : [`${declaration.name} from ${originOf(secret)}`]);
7614
+ return {
7615
+ state: "ok",
7616
+ message: found.length > 0 ? found.join(", ") : "no Secret is set, and none is needed"
7617
+ };
7618
+ }
7619
+ }];
7620
+ }
7621
+ //#endregion
7622
+ //#region ../agent-cli/dist/runtime-codes.js
7623
+ const DOCTOR_PROBLEMS = "DOCTOR_PROBLEMS";
7624
+ const NOT_GLOBAL_INSTALL = "NOT_GLOBAL_INSTALL";
7625
+ const NOT_ON_PATH = "NOT_ON_PATH";
7626
+ const REGISTRY_UNREACHABLE = "REGISTRY_UNREACHABLE";
7627
+ const REGISTRY_AUTH = "REGISTRY_AUTH";
7628
+ const INSTALL_FAILED = "INSTALL_FAILED";
7629
+ const NPM_FAILED = "NPM_FAILED";
7630
+ const NO_LATEST = "NO_LATEST";
7631
+ /** The Runtime codes any Command can meet, which no Command declares. */
7632
+ const anyCommandErrors = {
7633
+ [INVALID_INPUT]: { class: "caller" },
7634
+ [UNKNOWN_OPTION]: { class: "caller" },
7635
+ [UNKNOWN_COMMAND]: { class: "caller" },
7636
+ [UNKNOWN_FIELD]: { class: "caller" },
7637
+ [CONFIRMATION_REQUIRED]: { class: "caller" },
7638
+ [CONFIRMATION_MISMATCH]: { class: "caller" },
7639
+ [NOT_CONFIRMED]: { class: "operator" },
7640
+ [NEEDS_OPERATOR]: { class: "operator" },
7641
+ [OUTPUT_EXISTS]: { class: "caller" },
7642
+ [STDIN_STALLED]: { class: "caller" },
7643
+ [INTERRUPTED]: { class: "interrupted" },
7644
+ [DEADLINE_EXCEEDED]: { class: "wait" },
7645
+ [INTERNAL]: { class: "bug" },
7646
+ [PERMISSION_DENIED]: { class: "operator" },
7647
+ [DISK_FULL]: { class: "operator" },
7648
+ [NOT_FOUND]: { class: "caller" },
7649
+ [ALREADY_EXISTS]: { class: "caller" },
7650
+ [NOT_A_FILE]: { class: "caller" },
7651
+ [TOOL_FAILED]: { class: "operator" },
7652
+ [TOOL_MISSING]: { class: "operator" },
7653
+ [SECRET_MISSING]: { class: "operator" },
7654
+ [PROJECT_INVALID]: { class: "operator" }
7655
+ };
7656
+ /**
7657
+ * The Runtime codes only some Commands reach: each Command that throws one, Built-in or the CLI's
7658
+ * own, declares it in `errors`, as it does a code of the CLI's Error table.
7659
+ */
7660
+ const declaredRuntimeErrors = {
7661
+ [DOCTOR_PROBLEMS]: { class: "operator" },
7662
+ [NOT_GLOBAL_INSTALL]: { class: "operator" },
7663
+ [NOT_ON_PATH]: { class: "operator" },
7664
+ [REGISTRY_UNREACHABLE]: { class: "operator" },
7665
+ [REGISTRY_AUTH]: { class: "operator" },
7666
+ [INSTALL_FAILED]: { class: "operator" },
7667
+ [NPM_FAILED]: { class: "operator" },
7668
+ [NO_LATEST]: { class: "source" }
7669
+ };
7670
+ /** The Runtime's own Error table: every code it fails a run with, and the class that must act. */
7671
+ const runtimeErrors = {
7672
+ ...anyCommandErrors,
7673
+ ...declaredRuntimeErrors
7674
+ };
7675
+ const anyCommandClasses = Object.values(anyCommandErrors).map((declaration) => declaration.class);
7676
+ function isRuntimeCode(code) {
7677
+ return Object.hasOwn(runtimeErrors, code);
7678
+ }
7679
+ function isDeclaredRuntimeCode(code) {
7680
+ return Object.hasOwn(declaredRuntimeErrors, code);
7681
+ }
7682
+ /** Whether a Command or `doctor` row throwing `code` must declare it: a code of `table`, or a Runtime one only some reach. */
7683
+ function mustDeclare(code, table) {
7684
+ return Object.hasOwn(table, code) || isDeclaredRuntimeCode(code);
7685
+ }
7686
+ //#endregion
7687
+ //#region ../agent-cli/dist/define-command.js
7688
+ /** Throws when the Command's declaration breaks a rule its type cannot express; `label` names it. */
7689
+ function checkCommand(command, label, table) {
7690
+ checkNoStandardFlags(command, label);
7691
+ checkForce(command, label);
7692
+ checkArrayArgLast(label, command.args);
7693
+ checkStdinFields(label, command.args, command.options);
7694
+ checkNoBooleanArrayOption(label, command.options);
7695
+ checkErrors(command.errors ?? [], label, table);
7696
+ }
7697
+ /** Throws on a code in `errors` that is neither in `table` nor a Runtime code some Commands declare. */
7698
+ function checkErrors(errors, label, table) {
7699
+ const unknown = errors.filter((code) => !mustDeclare(code, table));
7700
+ if (unknown.length > 0) throw new Error(`${label} declares ${unknown.join(", ")} in errors, a code the Error table lacks`);
7701
+ }
7702
+ function defineCommandFor() {
7703
+ return declared;
7704
+ }
7705
+ function declared(command) {
7706
+ return command;
7707
+ }
7708
+ const defineCommand$1 = defineCommandFor();
7709
+ //#endregion
7710
+ //#region ../agent-cli/dist/error-table.js
7711
+ /** The Error table a CLI declares, empty when it declares none. */
7712
+ function errorTableOf({ errors }) {
7713
+ return errors ?? {};
7714
+ }
7715
+ /** The `CliError` for a code of `Table`, as `cliError` and `ctx.fail` build it. */
7716
+ function cliErrorOf(code, init, options) {
7717
+ return new CliError({
7718
+ ...init,
7719
+ code
7720
+ }, options);
7721
+ }
7722
+ //#endregion
7723
+ //#region ../agent-cli/dist/version.js
7724
+ /** The bundler's stamp, absent when the CLI runs unbundled. Read per call, so a test can define it. */
7725
+ function buildStamp() {
7726
+ return {
7727
+ "sha": "b509cf2",
7728
+ "dirty": false,
7729
+ "builtAt": "2026-11-09T01:38:28.000Z"
7730
+ };
7731
+ }
7732
+ /** The first line of `--version`: the version, and the commit and time of a stamped build. */
7733
+ function versionLine({ version }) {
7734
+ const build = buildStamp();
7735
+ return build === void 0 ? version : `${version} (${build.sha}${build.dirty ? "+dirty" : ""}) built ${build.builtAt}`;
7736
+ }
7737
+ //#endregion
7738
+ //#region ../agent-cli/dist/built-ins/semver.js
7739
+ const VERSION = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
7740
+ const ENGINE = /^>=\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?$/;
7741
+ function isVersion(text) {
7742
+ return VERSION.test(text);
7743
+ }
7744
+ function compareIdentifiers(x, y) {
7745
+ const xNumeric = /^\d+$/.test(x);
7746
+ const yNumeric = /^\d+$/.test(y);
7747
+ if (xNumeric && yNumeric) return Number(x) - Number(y);
7748
+ if (xNumeric !== yNumeric) return xNumeric ? -1 : 1;
7749
+ if (x === y) return 0;
7750
+ return x < y ? -1 : 1;
7751
+ }
7752
+ function comparePrerelease(a, b) {
7753
+ if (a === b) return 0;
7754
+ if (a === void 0) return 1;
7755
+ if (b === void 0) return -1;
7756
+ const left = a.split(".");
7757
+ const right = b.split(".");
7758
+ for (let i = 0; i < Math.min(left.length, right.length); i += 1) {
7759
+ const difference = compareIdentifiers(left[i] ?? "", right[i] ?? "");
7760
+ if (difference !== 0) return difference;
7761
+ }
7762
+ return left.length - right.length;
7763
+ }
7764
+ /** Negative when `a` is older than `b`, positive when newer, 0 when the same release. */
7175
7765
  function compareVersions(a, b) {
7176
7766
  const left = VERSION.exec(a);
7177
7767
  const right = VERSION.exec(b);
@@ -7207,12 +7797,15 @@ function meetsMinimum(version, minimum) {
7207
7797
  }
7208
7798
  //#endregion
7209
7799
  //#region ../agent-cli/dist/built-ins/npm.js
7210
- const REGISTRY_UNREACHABLE = "REGISTRY_UNREACHABLE";
7211
- const REGISTRY_AUTH = "REGISTRY_AUTH";
7212
- const INSTALL_FAILED = "INSTALL_FAILED";
7213
- const NPM_FAILED = "NPM_FAILED";
7214
- const NO_LATEST = "NO_LATEST";
7215
- function npmContext(cli, registry, ctx, node, cwd) {
7800
+ /** What a Command running npm may fail with, for it to declare in `errors`. */
7801
+ const NPM_ERRORS = [
7802
+ REGISTRY_UNREACHABLE,
7803
+ REGISTRY_AUTH,
7804
+ INSTALL_FAILED,
7805
+ NPM_FAILED,
7806
+ NO_LATEST
7807
+ ];
7808
+ function npmContext(cli, registry, ctx, { node, platform }, cwd) {
7216
7809
  return {
7217
7810
  cliName: cli.name,
7218
7811
  rerun: ctx.rerun(),
@@ -7220,7 +7813,9 @@ function npmContext(cli, registry, ctx, node, cwd) {
7220
7813
  cwd,
7221
7814
  env: ctx.env,
7222
7815
  signal: ctx.signal,
7223
- node
7816
+ node,
7817
+ platform,
7818
+ files: ctx.files
7224
7819
  };
7225
7820
  }
7226
7821
  const STDERR_TAIL_LINES = 10;
@@ -7262,9 +7857,10 @@ function setupCommandLine({ package: name, flags }) {
7262
7857
  "setup"
7263
7858
  ].join(" ");
7264
7859
  }
7265
- function npmOf(ctx, windows) {
7860
+ async function npmOf(ctx) {
7861
+ const windows = ctx.platform === "win32";
7266
7862
  const beside = ctx.node === void 0 ? void 0 : join(dirname(ctx.node), windows ? "npm.cmd" : "npm");
7267
- if (beside === void 0 || !existsSync(beside)) return {
7863
+ if (beside === void 0 || !await ctx.files.exists(beside)) return {
7268
7864
  path: "npm",
7269
7865
  env: ctx.env
7270
7866
  };
@@ -7281,8 +7877,7 @@ function npmOf(ctx, windows) {
7281
7877
  }
7282
7878
  };
7283
7879
  }
7284
- function spawnNpm(args, ctx, signal) {
7285
- const npm = npmOf(ctx, process.platform === "win32");
7880
+ function spawnNpm(npm, args, ctx, signal) {
7286
7881
  return spawnChild(npm.path, args, {
7287
7882
  cwd: ctx.cwd,
7288
7883
  env: npm.env,
@@ -7300,7 +7895,7 @@ function authHint(code, ctx) {
7300
7895
  if (code !== "E403" || !isAgent(ctx.env)) return login;
7301
7896
  return `a Coding agent's sandbox may refuse ${registry} with a 403: ask your user to type ! and ${RUN_AGAIN}, or to allow ${registry} in the sandbox's network list; if it fails outside the sandbox too, ${login}`;
7302
7897
  }
7303
- function failureOf(call, answer, ctx) {
7898
+ function failureOf$1(call, answer, ctx) {
7304
7899
  const { label: registry, package: name, unreachableHint } = ctx.registry;
7305
7900
  const code = NPM_CODE.exec(answer.stderr)?.[1];
7306
7901
  const output = tail$1(answer.stderr);
@@ -7345,7 +7940,7 @@ function noLatest(ctx, message) {
7345
7940
  async function runNpm(call, args, ctx, signal = ctx.signal) {
7346
7941
  const cache = await mkdtemp(join(tmpdir(), `${ctx.cliName}-npm-`));
7347
7942
  try {
7348
- const answer = await spawnNpm([
7943
+ const answer = await spawnNpm(await npmOf(ctx), [
7349
7944
  ...args,
7350
7945
  `--cache=${join(cache, "cache")}`,
7351
7946
  "--logs-max=0"
@@ -7357,7 +7952,7 @@ async function runNpm(call, args, ctx, signal = ctx.signal) {
7357
7952
  hint: "install Node with npm, or put npm on PATH"
7358
7953
  }, { cause: error });
7359
7954
  });
7360
- if (answer.exitCode !== 0) throw failureOf(call, answer, ctx);
7955
+ if (answer.exitCode !== 0) throw failureOf$1(call, answer, ctx);
7361
7956
  return answer.stdout;
7362
7957
  } finally {
7363
7958
  await rm(cache, {
@@ -7407,6 +8002,30 @@ async function latestVersion(ctx) {
7407
8002
  return version;
7408
8003
  }
7409
8004
  //#endregion
8005
+ //#region ../agent-cli/dist/built-ins/read.js
8006
+ function isNotFound(error) {
8007
+ return error instanceof CliError && error.code === "NOT_FOUND";
8008
+ }
8009
+ const decoder = new TextDecoder();
8010
+ /** The text of the file at `path`, or `undefined` when nothing is there. */
8011
+ async function readIfAny(files, path) {
8012
+ try {
8013
+ return decoder.decode(await files.read(path));
8014
+ } catch (error) {
8015
+ if (isNotFound(error)) return void 0;
8016
+ throw error;
8017
+ }
8018
+ }
8019
+ /** The text of a file the CLI's own package ships, whose absence is the release's bug. */
8020
+ async function readRequired(files, path) {
8021
+ try {
8022
+ return decoder.decode(await files.read(path));
8023
+ } catch (error) {
8024
+ if (!isNotFound(error)) throw error;
8025
+ throw new Error(`the package is missing its own file ${path}`, { cause: error });
8026
+ }
8027
+ }
8028
+ //#endregion
7410
8029
  //#region ../agent-cli/dist/built-ins/project-root.js
7411
8030
  const MANAGERS = [
7412
8031
  "pnpm",
@@ -7431,22 +8050,14 @@ function ancestorsOf(dir) {
7431
8050
  const parent = dirname(dir);
7432
8051
  return parent === dir ? [dir] : [dir, ...ancestorsOf(parent)];
7433
8052
  }
7434
- async function exists(path) {
7435
- return await orMissing(stat(path)) !== void 0;
7436
- }
7437
8053
  /** Whether `path` lies strictly inside `dir`. */
7438
8054
  function isInside(dir, path) {
7439
8055
  const inner = relative(dir, path);
7440
8056
  return inner !== "" && !inner.startsWith("..") && !isAbsolute(inner);
7441
8057
  }
7442
- async function manifestIn(dir) {
7443
- let text;
7444
- try {
7445
- text = await readFile(join(dir, "package.json"), "utf8");
7446
- } catch (error) {
7447
- if (isMissingFile(error)) return void 0;
7448
- throw error;
7449
- }
8058
+ async function manifestIn(files, dir) {
8059
+ const text = await readIfAny(files, join(dir, "package.json"));
8060
+ if (text === void 0) return void 0;
7450
8061
  let manifest;
7451
8062
  try {
7452
8063
  manifest = JSON.parse(text);
@@ -7455,19 +8066,19 @@ async function manifestIn(dir) {
7455
8066
  }
7456
8067
  return manifestSchema$1.safeParse(manifest).data;
7457
8068
  }
7458
- async function declaredManager(dir) {
7459
- const name = (await manifestIn(dir))?.packageManager?.split("@")[0];
8069
+ async function declaredManager(files, dir) {
8070
+ const name = (await manifestIn(files, dir))?.packageManager?.split("@")[0];
7460
8071
  return MANAGERS.find((manager) => manager === name);
7461
8072
  }
7462
- async function lockfileRootOf(cwd) {
7463
- for (const dir of ancestorsOf(cwd)) for (const [file, kind] of LOCKFILES) if (await exists(join(dir, file))) return {
8073
+ async function lockfileRootOf(files, cwd) {
8074
+ for (const dir of ancestorsOf(cwd)) for (const [file, kind] of LOCKFILES) if (await files.exists(join(dir, file))) return {
7464
8075
  dir,
7465
- manager: await declaredManager(dir) ?? kind
8076
+ manager: await declaredManager(files, dir) ?? kind
7466
8077
  };
7467
8078
  }
7468
8079
  /** The package manager's project root: the folder of the nearest lockfile, or `cwd` without one. */
7469
- async function projectRootOf(cwd) {
7470
- return (await lockfileRootOf(cwd))?.dir ?? cwd;
8080
+ async function projectRootOf(files, cwd) {
8081
+ return (await lockfileRootOf(files, cwd))?.dir ?? cwd;
7471
8082
  }
7472
8083
  //#endregion
7473
8084
  //#region ../agent-cli/dist/built-ins/install/run-as.js
@@ -7490,29 +8101,30 @@ const LINES = {
7490
8101
  }
7491
8102
  };
7492
8103
  const packageSchema = object({ version: string() });
7493
- async function manifestListing(name, cwd, root) {
8104
+ async function manifestListing(files, name, cwd, root) {
7494
8105
  const dirs = ancestorsOf(cwd);
7495
8106
  const top = root === void 0 ? dirs.length : dirs.indexOf(root.dir) + 1;
7496
8107
  for (const dir of dirs.slice(0, top)) {
7497
- const manifest = await manifestIn(dir);
8108
+ const manifest = await manifestIn(files, dir);
7498
8109
  if (manifest?.dependencies?.[name] !== void 0 || manifest?.devDependencies?.[name] !== void 0) return join(dir, "package.json");
7499
8110
  }
7500
8111
  }
7501
- async function versionIn(manifest) {
8112
+ function versionIn(text) {
7502
8113
  try {
7503
- return packageSchema.parse(JSON.parse(await readFile(manifest, "utf8"))).version;
8114
+ return packageSchema.parse(JSON.parse(text)).version;
7504
8115
  } catch (error) {
7505
8116
  if (error instanceof SyntaxError || error instanceof ZodError) return { reason: messageOf(error) };
7506
8117
  throw error;
7507
8118
  }
7508
8119
  }
7509
- async function standingOf(install, bin, cwd, root) {
8120
+ async function standingOf(files, install, bin, cwd, root) {
7510
8121
  if (bin === void 0) return { kind: "source" };
7511
8122
  for (const projectDir of ancestorsOf(cwd)) {
7512
8123
  const folder = join(projectDir, "node_modules", install.package);
7513
8124
  const manifest = join(folder, "package.json");
7514
- if (!await exists(manifest)) continue;
7515
- const version = await versionIn(manifest);
8125
+ const text = await readIfAny(files, manifest);
8126
+ if (text === void 0) continue;
8127
+ const version = versionIn(text);
7516
8128
  if (typeof version !== "string") return {
7517
8129
  kind: "unreadable",
7518
8130
  manifest,
@@ -7520,14 +8132,14 @@ async function standingOf(install, bin, cwd, root) {
7520
8132
  };
7521
8133
  const copy = {
7522
8134
  projectDir,
7523
- realDir: await realpath(folder),
8135
+ realDir: await files.realPath(folder) ?? folder,
7524
8136
  version
7525
8137
  };
7526
8138
  if (!copy.realDir.split(sep).includes("node_modules")) return {
7527
8139
  kind: "linked",
7528
8140
  copy
7529
8141
  };
7530
- const runningFrom = await orMissing(realpath(bin)) ?? bin;
8142
+ const runningFrom = await files.realPath(bin) ?? bin;
7531
8143
  return isInside(copy.realDir, runningFrom) ? {
7532
8144
  kind: "installed",
7533
8145
  copy
@@ -7537,18 +8149,18 @@ async function standingOf(install, bin, cwd, root) {
7537
8149
  runningFrom
7538
8150
  };
7539
8151
  }
7540
- const manifest = await manifestListing(install.package, cwd, root);
8152
+ const manifest = await manifestListing(files, install.package, cwd, root);
7541
8153
  return manifest === void 0 ? { kind: "missing" } : {
7542
8154
  kind: "listed",
7543
8155
  manifest
7544
8156
  };
7545
8157
  }
7546
- async function surveyDependency(install, bin, cwd) {
7547
- const root = await lockfileRootOf(cwd);
8158
+ async function surveyDependency(install, bin, { files, cwd }) {
8159
+ const root = await lockfileRootOf(files, cwd);
7548
8160
  return {
7549
8161
  cwd,
7550
8162
  root,
7551
- standing: await standingOf(install, bin, cwd, root)
8163
+ standing: await standingOf(files, install, bin, cwd, root)
7552
8164
  };
7553
8165
  }
7554
8166
  function packagesOf(install) {
@@ -7618,19 +8230,19 @@ function adviceFor(install, root, latest) {
7618
8230
  if (lines !== void 0) return { fix: lines.upgrade(packages) };
7619
8231
  return { note: `move ${packages.length === 1 ? packages[0] : `${packages.slice(0, -1).join(", ")} and ${packages.at(-1)}`} to ${latest} with ${root?.manager ?? "the project's package manager"}` };
7620
8232
  }
7621
- function checkOf$1(cli, install, runAs, { bin, node }) {
8233
+ function checkOf$1(cli, install, runAs, launch) {
7622
8234
  let survey;
7623
- const surveyed = (cwd) => {
7624
- survey ??= surveyDependency(install, bin, cwd);
8235
+ const surveyed = (ctx) => {
8236
+ survey ??= surveyDependency(install, launch.bin, ctx);
7625
8237
  return survey;
7626
8238
  };
7627
8239
  return {
7628
- install: async (ctx) => installFinding(cli, runAs, install, await surveyed(ctx.cwd)),
8240
+ install: async (ctx) => installFinding(cli, runAs, install, await surveyed(ctx)),
7629
8241
  release: async (ctx) => {
7630
- const { cwd, root, standing } = await surveyed(ctx.cwd);
8242
+ const { cwd, root, standing } = await surveyed(ctx);
7631
8243
  if (isUnchecked(standing)) return { skipped: skippedFor$1(cli, install, standing) };
7632
8244
  return {
7633
- npm: npmContext(cli, registryOf(install, root?.dir), ctx, node, root?.dir ?? cwd),
8245
+ npm: npmContext(cli, registryOf(install, root?.dir), ctx, launch, root?.dir ?? cwd),
7634
8246
  advise: (latest) => adviceFor(install, root, latest)
7635
8247
  };
7636
8248
  }
@@ -7642,222 +8254,22 @@ function dependencyInstall(cli, install) {
7642
8254
  return {
7643
8255
  runAs,
7644
8256
  builtIns: () => ({}),
7645
- exitClasses: [],
7646
8257
  check: (launch) => checkOf$1(cli, install, runAs, launch),
7647
8258
  notice: async () => {}
7648
8259
  };
7649
8260
  }
7650
8261
  //#endregion
7651
- //#region ../agent-cli/dist/context/errno.js
7652
- const PERMISSION_DENIED = "PERMISSION_DENIED";
7653
- const DISK_FULL = "DISK_FULL";
7654
- const NOT_FOUND = "NOT_FOUND";
7655
- const ALREADY_EXISTS = "ALREADY_EXISTS";
7656
- const NOT_A_FILE = "NOT_A_FILE";
7657
- function permissionAdvice({ path, access, rerun, env, platform }) {
7658
- if (platform === "win32") return {
7659
- hint: `a program may hold ${path} open: close it, then run the Next step`,
7660
- next: [rerun]
7661
- };
7662
- if (!isAgent(env)) return { hint: `check the permissions of ${path} and its folder` };
7663
- return {
7664
- hint: `your sandbox may block it: ask your user to type ! then the Next step, or ${access === "read" ? `to allow reading ${path} in the sandbox` : `to allow ${path} in the sandbox's write list`}`,
7665
- next: [rerun]
7666
- };
7667
- }
7668
- function fileErrorOf(scope) {
7669
- return (error, at) => classifyErrno(error, {
7670
- ...at,
7671
- ...scope
7672
- });
7673
- }
7674
- function classifyErrno(error, context) {
7675
- const { path } = context;
7676
- switch (errnoOf(error)) {
7677
- case "EACCES":
7678
- case "EPERM":
7679
- case "EROFS":
7680
- case "EBUSY": return new CliError({
7681
- code: PERMISSION_DENIED,
7682
- message: `permission denied: ${path}`,
7683
- ...permissionAdvice(context)
7684
- }, { cause: error });
7685
- case "ENOSPC":
7686
- case "EDQUOT": return new CliError({
7687
- code: DISK_FULL,
7688
- message: `no space left on the disk holding ${path}`,
7689
- hint: "free space on that disk, then run the Next step",
7690
- next: [context.rerun]
7691
- }, { cause: error });
7692
- case "ENOENT":
7693
- case "ENOTDIR":
7694
- case "ENAMETOOLONG": return new CliError({
7695
- code: NOT_FOUND,
7696
- message: `not found: ${path}`,
7697
- hint: `check that ${path} and its folder exist`
7698
- }, { cause: error });
7699
- case "EEXIST": return new CliError({
7700
- code: ALREADY_EXISTS,
7701
- message: `already exists: ${path}`,
7702
- hint: `pass another path, or remove ${path}`
7703
- }, { cause: error });
7704
- case "EISDIR": return new CliError({
7705
- code: NOT_A_FILE,
7706
- message: `not a file: ${path} is a folder`,
7707
- hint: `pass the path of a file, not of the folder ${path}`
7708
- }, { cause: error });
7709
- default: return error;
7710
- }
7711
- }
7712
- //#endregion
7713
- //#region ../agent-cli/dist/context/interrupts.js
7714
- const INTERRUPTED = "INTERRUPTED";
7715
- const DEADLINE_EXCEEDED = "DEADLINE_EXCEEDED";
7716
- /** How long an interrupted Command may take to settle before the run ends without it. */
7717
- const GRACE_MS = 3e3;
7718
- const GRACE_OVER = Symbol("grace over");
7719
- function interruptedError(rerun) {
7720
- return new CliError({
7721
- code: INTERRUPTED,
7722
- message: "interrupted before it finished",
7723
- hint: "to finish it, run the Next step, unless it was stopped on purpose; if a tool timeout stopped it, give it longer",
7724
- next: [rerun]
7725
- });
7726
- }
7727
- const BOUND_WORDS = {
7728
- deadline: "deadline",
7729
- ignoreStop: "timeout"
7730
- };
7731
- function waitHint(bound) {
7732
- return bound === "ignoreStop" ? "the call may have been billed: ask your user before running the Next step" : "run the Next step later, when the service it waits on may answer faster";
7733
- }
7734
- function deadlineError(commandLine, ms, advice, bound = "deadline") {
7735
- return new CliError({
7736
- code: DEADLINE_EXCEEDED,
7737
- message: `${commandLine} ran past its ${ms} ms ${BOUND_WORDS[bound]}`,
7738
- ...advice
7739
- });
7740
- }
7741
- /** One Command run's cancellation: its signal, its deadlines and the steps it must finish. */
7742
- var RunControl = class {
7743
- signal;
7744
- #controller = new AbortController();
7745
- #commandLine;
7746
- #rerun;
7747
- #outer;
7748
- #onOuterAbort = () => this.#controller.abort(interruptedError(this.#rerun));
7749
- /** Each running must-finish step, tracked until it settles, with its name. */
7750
- #steps = /* @__PURE__ */ new Map();
7751
- #progress;
7752
- #clock;
7753
- #disposed = new AbortController();
7754
- /** The reasons this run's deadlines aborted with, to tell them in a Command's error. */
7755
- #abortReasons = /* @__PURE__ */ new WeakSet();
7756
- #graceCancel = new AbortController();
7757
- #endGrace = () => {};
7758
- #graceOver = new Promise((resolve) => {
7759
- this.#endGrace = resolve;
7760
- });
7761
- constructor({ commandLine, rerun, outer, clock, progress, runDeadline }) {
7762
- this.#commandLine = commandLine;
7763
- this.#rerun = rerun;
7764
- this.#outer = outer;
7765
- this.#clock = clock;
7766
- this.#progress = progress;
7767
- this.signal = this.#controller.signal;
7768
- this.signal.addEventListener("abort", () => this.#onStop(), { once: true });
7769
- if (outer?.aborted === true) this.#onOuterAbort();
7770
- else outer?.addEventListener("abort", this.#onOuterAbort, { once: true });
7771
- if (runDeadline !== void 0) {
7772
- const { ms, advice } = runDeadline;
7773
- after(clock, ms, this.#disposed.signal, () => this.#controller.abort(deadlineError(commandLine, ms, advice())));
7774
- }
7775
- }
7776
- deadline(ms, options = {}) {
7777
- return options.ignoreStop === true ? this.#abortsAfter(ms, "ignoreStop") : AbortSignal.any([this.signal, this.#abortsAfter(ms, "deadline")]);
7778
- }
7779
- #abortsAfter(ms, bound) {
7780
- const controller = new AbortController();
7781
- after(this.#clock, ms, this.#disposed.signal, () => {
7782
- const advice = {
7783
- hint: waitHint(bound),
7784
- next: [this.#rerun]
7785
- };
7786
- const reason = deadlineError(this.#commandLine, ms, advice, bound);
7787
- this.#abortReasons.add(reason);
7788
- controller.abort(reason);
7789
- });
7790
- return controller.signal;
7791
- }
7792
- async runToEnd(name, work) {
7793
- this.#graceCancel.abort();
7794
- if (this.signal.aborted) this.#finishing(name);
7795
- const step = work();
7796
- const settled = step.then(() => {}, () => {});
7797
- this.#steps.set(settled, name);
7798
- try {
7799
- return await step;
7800
- } finally {
7801
- this.#steps.delete(settled);
7802
- this.#startGrace();
7803
- }
7804
- }
7805
- /** Waits for the Command, its must-finish steps, and no longer than the grace once interrupted. */
7806
- async settle(work) {
7807
- try {
7808
- const value = await Promise.race([work, this.#graceOver]);
7809
- if (this.signal.aborted || value === GRACE_OVER) throw this.signal.reason;
7810
- return value;
7811
- } finally {
7812
- await this.#stepsDone();
7813
- this.close();
7814
- }
7815
- }
7816
- /**
7817
- * The error a run ends with: an interrupt or the run deadline outranks what the Command threw,
7818
- * unless the Command threw its own `INTERRUPTED`, saying what it finished before it stopped.
7819
- * Otherwise each leaf that is not a `CliError` and whose cause chain holds a deadline's abort is
7820
- * reported as that abort, so a declared error keeps its own code.
7821
- */
7822
- errorAfter(error) {
7823
- if (this.signal.aborted) return error instanceof CliError && error.code === "INTERRUPTED" ? error : this.signal.reason;
7824
- const leaves = errorsOf(error).map((leaf) => this.#abortIn(leaf) ?? leaf);
7825
- return leaves.length === 1 ? leaves[0] : new AggregateError(leaves);
7826
- }
7827
- #abortIn(error) {
7828
- if (error instanceof CliError) return void 0;
7829
- return causesOf(error).find((each) => each instanceof CliError && this.#abortReasons.has(each));
7830
- }
7831
- #onStop() {
7832
- for (const name of this.#steps.values()) this.#finishing(name);
7833
- this.#startGrace();
7834
- }
7835
- #finishing(name) {
7836
- this.#progress(`finishing ${name}`);
7837
- }
7838
- #startGrace() {
7839
- if (this.signal.aborted && this.#steps.size === 0) {
7840
- this.#graceCancel.abort();
7841
- this.#graceCancel = new AbortController();
7842
- after(this.#clock, GRACE_MS, this.#graceCancel.signal, () => this.#endGrace(GRACE_OVER));
7843
- }
7844
- }
7845
- async #stepsDone() {
7846
- while (this.#steps.size > 0) await Promise.all(this.#steps.keys());
7847
- }
7848
- /** Stops the run's timers and its hold on the caller's signal, for a run that never settles. */
7849
- close() {
7850
- this.#graceCancel.abort();
7851
- this.#disposed.abort();
7852
- this.#outer?.removeEventListener("abort", this.#onOuterAbort);
7853
- }
7854
- };
7855
- //#endregion
7856
8262
  //#region ../agent-cli/dist/built-ins/install/global-npm.js
7857
- const NOT_GLOBAL_INSTALL = "NOT_GLOBAL_INSTALL";
7858
8263
  /** Where npm puts global packages under its global prefix. */
7859
- function globalModulesDir(prefix) {
7860
- return process.platform === "win32" ? join(prefix, "node_modules") : join(prefix, "lib", "node_modules");
8264
+ function globalModulesDir(prefix, platform) {
8265
+ return platform === "win32" ? join(prefix, "node_modules") : join(prefix, "lib", "node_modules");
8266
+ }
8267
+ /** Whether `path` itself is a symlink, which a link in a folder above it does not make it. */
8268
+ async function isLink(files, path) {
8269
+ const real = await files.realPath(path);
8270
+ if (real === void 0) return false;
8271
+ const folder = await files.realPath(dirname(path));
8272
+ return folder === void 0 || real !== join(folder, basename(path));
7861
8273
  }
7862
8274
  function notGlobal(ctx, message) {
7863
8275
  return runtimeError({
@@ -7874,11 +8286,11 @@ function notGlobal(ctx, message) {
7874
8286
  async function globalInstallOf(bin, ctx) {
7875
8287
  if (bin === void 0) throw notGlobal(ctx, `${ctx.cliName} runs from source, not from an npm install`);
7876
8288
  const prefix = await globalPrefix(ctx);
7877
- const packageDir = join(globalModulesDir(prefix), ctx.registry.package);
7878
- const installed = await orMissing(realpath(packageDir));
7879
- const here = await orMissing(realpath(bin)) ?? bin;
8289
+ const packageDir = join(globalModulesDir(prefix, ctx.platform), ctx.registry.package);
8290
+ const installed = await ctx.files.realPath(packageDir);
8291
+ const here = await ctx.files.realPath(bin) ?? bin;
7880
8292
  if (installed === void 0 || !isInside(installed, here)) throw notGlobal(ctx, `${ctx.cliName} runs from ${here}, not from npm's global install at ${packageDir}`);
7881
- if ((await lstat(packageDir)).isSymbolicLink()) throw notGlobal(ctx, `${ctx.cliName} runs from a link to ${installed} (npm link), not from an npm install`);
8293
+ if (await isLink(ctx.files, packageDir)) throw notGlobal(ctx, `${ctx.cliName} runs from a link to ${installed} (npm link), not from an npm install`);
7882
8294
  return {
7883
8295
  prefix,
7884
8296
  packageDir
@@ -7907,25 +8319,32 @@ function globalRegistry(install) {
7907
8319
  };
7908
8320
  }
7909
8321
  /** npm for a Global install, run in the working directory. */
7910
- function globalNpm(cli, install, ctx, node) {
7911
- return npmContext(cli, globalRegistry(install), ctx, node, ctx.cwd);
8322
+ function globalNpm(cli, install, ctx, launch) {
8323
+ return npmContext(cli, globalRegistry(install), ctx, launch, ctx.cwd);
7912
8324
  }
7913
8325
  //#endregion
7914
8326
  //#region ../agent-cli/dist/built-ins/skill/sync.js
7915
- /** Every file under `dir`, relative to it and sorted; none when `dir` is missing. */
7916
- async function filesIn(dir) {
7917
- return (await orMissing(readdir(dir, {
7918
- recursive: true,
7919
- withFileTypes: true
7920
- })) ?? []).filter((entry) => entry.isFile()).map((entry) => relative(dir, join(entry.parentPath, entry.name))).sort();
7921
- }
7922
- async function sameContent(left, right) {
7923
- const [a, b] = await Promise.all([readFile(left), readFile(right)]);
7924
- return a.equals(b);
7925
- }
7926
- async function syncOf(source, copy) {
7927
- const [shipped, present] = await Promise.all([filesIn(source), filesIn(copy)]);
7928
- const differs = await Promise.all(shipped.map(async (file) => present.includes(file) && !await sameContent(join(source, file), join(copy, file))));
8327
+ async function walk$1(files, dir, inner) {
8328
+ const names = await files.list(join(dir, inner));
8329
+ return (await Promise.all(names.map(async (name) => {
8330
+ const path = join(inner, name);
8331
+ const found = await files.stat(join(dir, path));
8332
+ if (found === void 0) return [];
8333
+ return found.folder ? walk$1(files, dir, path) : [path];
8334
+ }))).flat();
8335
+ }
8336
+ /** Every file under `dir`, relative to it and sorted; none when `dir` is no folder. */
8337
+ async function filesIn(files, dir) {
8338
+ if ((await files.stat(dir))?.folder !== true) return [];
8339
+ return (await walk$1(files, dir, "")).sort();
8340
+ }
8341
+ async function sameContent(files, left, right) {
8342
+ const [a, b] = await Promise.all([files.read(left), files.read(right)]);
8343
+ return Buffer.from(a).equals(b);
8344
+ }
8345
+ async function syncOf(files, source, copy) {
8346
+ const [shipped, present] = await Promise.all([filesIn(files, source), filesIn(files, copy)]);
8347
+ const differs = await Promise.all(shipped.map(async (file) => present.includes(file) && !await sameContent(files, join(source, file), join(copy, file))));
7929
8348
  return {
7930
8349
  source,
7931
8350
  copy,
@@ -7935,7 +8354,7 @@ async function syncOf(source, copy) {
7935
8354
  };
7936
8355
  }
7937
8356
  async function applySync(sync, ctx) {
7938
- for (const file of [...sync.write, ...sync.replace]) await ctx.files.write(join(sync.copy, file), await readFile(join(sync.source, file)));
8357
+ for (const file of [...sync.write, ...sync.replace]) await ctx.files.write(join(sync.copy, file), await ctx.files.read(join(sync.source, file)));
7939
8358
  for (const file of sync.remove) await ctx.files.remove(join(sync.copy, file));
7940
8359
  }
7941
8360
  //#endregion
@@ -7956,9 +8375,9 @@ function homeOf(env) {
7956
8375
  return env.HOME ?? env.USERPROFILE ?? homedir();
7957
8376
  }
7958
8377
  /** The home folders of the Skill copy; the skills installer may have made one a link to the other. */
7959
- async function homeCopiesOf(name, env) {
8378
+ async function homeCopiesOf(files, name, env) {
7960
8379
  const paths = SKILL_DIRS.map((dir) => join(homeOf(env), ...dir, name));
7961
- const reals = await Promise.all(paths.map(async (path) => orMissing(realpath(path))));
8380
+ const reals = await Promise.all(paths.map((path) => files.realPath(path)));
7962
8381
  return paths.filter((_path, index) => {
7963
8382
  const real = reals[index];
7964
8383
  return real === void 0 || reals.indexOf(real) === index;
@@ -7976,9 +8395,9 @@ const homeSyncSchema = object({
7976
8395
  async function syncHome(cli, packageDir, ctx) {
7977
8396
  if (cli.skill === void 0) return null;
7978
8397
  const source = join(packageDir, cli.skill.folder);
7979
- if ((await filesIn(source)).length === 0) return null;
7980
- const copies = await homeCopiesOf(cli.name, ctx.env);
7981
- const syncs = await Promise.all(copies.map((copy) => syncOf(source, copy)));
8398
+ if ((await filesIn(ctx.files, source)).length === 0) return null;
8399
+ const copies = await homeCopiesOf(ctx.files, cli.name, ctx.env);
8400
+ const syncs = await Promise.all(copies.map((copy) => syncOf(ctx.files, source, copy)));
7982
8401
  for (const sync of syncs) await applySync(sync, ctx);
7983
8402
  return {
7984
8403
  copies,
@@ -7995,19 +8414,25 @@ function renderHomeSync(name, sync) {
7995
8414
  const at = sync.copies.join(", ");
7996
8415
  return sync.written.length + sync.removed.length === 0 ? [`the ${name} skill at ${at} already matches the package`] : [`copied the ${name} skill to ${at}: wrote ${sync.written.length} files, removed ${sync.removed.length}`];
7997
8416
  }
7998
- async function homeCopyAt(path) {
8417
+ async function homeCopyAt(files, path) {
8418
+ let text;
7999
8419
  try {
8000
- return {
8001
- path,
8002
- state: "readable",
8003
- stated: skillVersionIn(await readFile(join(path, "SKILL.md"), "utf8"))
8004
- };
8420
+ text = await readIfAny(files, join(path, "SKILL.md"));
8005
8421
  } catch (error) {
8422
+ if (!(error instanceof CliError)) throw error;
8006
8423
  return {
8007
8424
  path,
8008
- state: isMissingFile(error) ? "missing" : "unreadable"
8425
+ state: "unreadable"
8009
8426
  };
8010
8427
  }
8428
+ return text === void 0 ? {
8429
+ path,
8430
+ state: "missing"
8431
+ } : {
8432
+ path,
8433
+ state: "readable",
8434
+ stated: skillVersionIn(text)
8435
+ };
8011
8436
  }
8012
8437
  function describeCopy(copy) {
8013
8438
  if (copy.state === "missing") return `no copy at ${copy.path}`;
@@ -8015,8 +8440,8 @@ function describeCopy(copy) {
8015
8440
  return copy.stated === void 0 ? copy.path : `${copy.path} (${statedVersionNote(copy.stated)})`;
8016
8441
  }
8017
8442
  async function findingOf$1(cli, folder, install, ctx) {
8018
- const paths = await homeCopiesOf(cli.name, ctx.env);
8019
- const [copies, shipped] = await Promise.all([Promise.all(paths.map(homeCopyAt)), orMissing(readFile(join(ctx.packageRoot, folder, "SKILL.md"), "utf8"))]);
8443
+ const paths = await homeCopiesOf(ctx.files, cli.name, ctx.env);
8444
+ const [copies, shipped] = await Promise.all([Promise.all(paths.map((path) => homeCopyAt(ctx.files, path))), readIfAny(ctx.files, join(ctx.packageRoot, folder, "SKILL.md"))]);
8020
8445
  const message = copies.map(describeCopy).join("; ");
8021
8446
  const shipsStated = shipped !== void 0 && skillVersionIn(shipped) !== void 0;
8022
8447
  return copies.every((copy) => copy.state === "readable" && (copy.stated === void 0 || shipsStated)) ? {
@@ -8094,14 +8519,9 @@ function upgradeOf({ version, lines }) {
8094
8519
  * What each release after `from` up to `to` asks of the operator, oldest first, from the
8095
8520
  * CHANGELOG.md in `packageDir`; none when the package ships no CHANGELOG.
8096
8521
  */
8097
- async function upgradesOf(packageDir, from, to) {
8098
- let changelog;
8099
- try {
8100
- changelog = await readFile(join(packageDir, "CHANGELOG.md"), "utf8");
8101
- } catch (error) {
8102
- if (isMissingFile(error)) return [];
8103
- throw error;
8104
- }
8522
+ async function upgradesOf(files, packageDir, from, to) {
8523
+ const changelog = await readIfAny(files, join(packageDir, "CHANGELOG.md"));
8524
+ if (changelog === void 0) return [];
8105
8525
  return releasesIn(changelog).filter(({ version }) => compareVersions(version, from) > 0 && compareVersions(version, to) <= 0).sort((a, b) => compareVersions(a.version, b.version)).flatMap(upgradeOf);
8106
8526
  }
8107
8527
  function renderUpgrade(upgrade) {
@@ -8109,7 +8529,6 @@ function renderUpgrade(upgrade) {
8109
8529
  }
8110
8530
  //#endregion
8111
8531
  //#region ../agent-cli/dist/built-ins/setup.js
8112
- const NOT_ON_PATH = "NOT_ON_PATH";
8113
8532
  const setupSchema = object({
8114
8533
  version: string(),
8115
8534
  prefix: string(),
@@ -8119,8 +8538,8 @@ const setupSchema = object({
8119
8538
  docs: string().nullable()
8120
8539
  });
8121
8540
  const installedManifest = object({ version: string() });
8122
- async function versionAt(packageDir) {
8123
- const text = await orMissing(readFile(join(packageDir, "package.json"), "utf8"));
8541
+ async function versionAt(files, packageDir) {
8542
+ const text = await readIfAny(files, join(packageDir, "package.json"));
8124
8543
  if (text === void 0) return null;
8125
8544
  try {
8126
8545
  const parsed = installedManifest.safeParse(JSON.parse(text));
@@ -8130,26 +8549,29 @@ async function versionAt(packageDir) {
8130
8549
  throw error;
8131
8550
  }
8132
8551
  }
8133
- function npmOfSetup(cli, install, ctx, node) {
8552
+ function npmOfSetup(cli, install, ctx, launch) {
8134
8553
  return {
8135
- ...globalNpm(cli, install, ctx, node),
8554
+ ...globalNpm(cli, install, ctx, launch),
8136
8555
  rerun: wholeLine(setupLineOf(install))
8137
8556
  };
8138
8557
  }
8139
8558
  function needsInstall({ from, latest }) {
8140
8559
  return from === null || standingOf$1(from, latest) === "behind";
8141
8560
  }
8142
- function binDirOf(prefix) {
8143
- return process.platform === "win32" ? prefix : join(prefix, "bin");
8561
+ function binDirOf(prefix, platform) {
8562
+ return platform === "win32" ? prefix : join(prefix, "bin");
8144
8563
  }
8145
- async function canonical(path) {
8146
- return normalize(await realpath(path).catch(() => path));
8564
+ async function canonical(files, path) {
8565
+ const real = await files.realPath(path).catch((error) => {
8566
+ if (!(error instanceof CliError) && errnoOf(error) === void 0) throw error;
8567
+ });
8568
+ return normalize(real ?? path);
8147
8569
  }
8148
- async function assertOnPath(cli, prefix, node, ctx) {
8149
- const binDir = binDirOf(prefix);
8150
- const wanted = await canonical(binDir);
8570
+ async function assertOnPath(cli, prefix, { node, platform }, ctx) {
8571
+ const binDir = binDirOf(prefix, platform);
8572
+ const wanted = await canonical(ctx.files, binDir);
8151
8573
  const entries = pathOf(ctx.env).split(delimiter).filter(Boolean);
8152
- if ((await Promise.all(entries.map(canonical))).includes(wanted)) return;
8574
+ if ((await Promise.all(entries.map((entry) => canonical(ctx.files, entry)))).includes(wanted)) return;
8153
8575
  throw runtimeError({
8154
8576
  code: NOT_ON_PATH,
8155
8577
  message: `${cli.name} is installed in ${binDir}, by the npm of ${node ?? "the Node on PATH"}, a folder not on this shell's PATH`,
@@ -8181,7 +8603,7 @@ function renderSetup(cli, install, setup) {
8181
8603
  ].join("\n");
8182
8604
  }
8183
8605
  /** The built-in `setup`, which runs from any copy, such as npx's, so it never asks to be the global one. */
8184
- function setupCommand(cli, install, { node }) {
8606
+ function setupCommand(cli, install, launch) {
8185
8607
  return defineCommand$1({
8186
8608
  description: "install this CLI and its skill on this machine",
8187
8609
  examples: [{
@@ -8192,12 +8614,17 @@ function setupCommand(cli, install, { node }) {
8192
8614
  note: "show what it would change"
8193
8615
  }],
8194
8616
  effect: "write",
8617
+ errors: [
8618
+ NOT_GLOBAL_INSTALL,
8619
+ NOT_ON_PATH,
8620
+ ...NPM_ERRORS
8621
+ ],
8195
8622
  output: setupSchema,
8196
8623
  plan: async (_input, ctx) => {
8197
- const npm = npmOfSetup(cli, install, ctx, node);
8624
+ const npm = npmOfSetup(cli, install, ctx, launch);
8198
8625
  const prefix = await globalPrefix(npm);
8199
- const packageDir = join(globalModulesDir(prefix), install.package);
8200
- if ((await orMissing(lstat(packageDir)))?.isSymbolicLink() === true) throw runtimeError({
8626
+ const packageDir = join(globalModulesDir(prefix, launch.platform), install.package);
8627
+ if (await isLink(ctx.files, packageDir)) throw runtimeError({
8201
8628
  code: NOT_GLOBAL_INSTALL,
8202
8629
  message: `${packageDir} is a link (npm link), which an install would replace`,
8203
8630
  hint: "remove the link with npm unlink, then run the Next step",
@@ -8205,309 +8632,137 @@ function setupCommand(cli, install, { node }) {
8205
8632
  });
8206
8633
  return {
8207
8634
  package: install.package,
8208
- from: await versionAt(packageDir),
8635
+ from: await versionAt(ctx.files, packageDir),
8209
8636
  latest: await latestVersion(npm),
8210
8637
  prefix,
8211
8638
  packageDir,
8212
- copies: cli.skill === void 0 ? [] : await homeCopiesOf(cli.name, ctx.env)
8639
+ copies: cli.skill === void 0 ? [] : await homeCopiesOf(ctx.files, cli.name, ctx.env)
8213
8640
  };
8214
8641
  },
8215
8642
  apply: async (plan, ctx) => {
8216
8643
  const { from, latest, prefix, packageDir } = plan;
8217
8644
  const installs = needsInstall(plan);
8218
8645
  const skill = await ctx.runToEnd(`installing ${plan.package}@${latest}`, async () => {
8219
- if (installs) {
8220
- ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
8221
- await installGlobally(latest, npmOfSetup(cli, install, ctx, node));
8222
- }
8223
- return syncHome(cli, packageDir, ctx);
8224
- }).catch((error) => {
8225
- throw withSetupLine(error, install);
8226
- });
8227
- const setup = {
8228
- version: installs || from === null ? latest : from,
8229
- prefix,
8230
- installed: installs,
8231
- skill,
8232
- upgrades: installs && from !== null ? await upgradesOf(packageDir, from, latest) : [],
8233
- docs: cli.docs === void 0 ? null : join(packageDir, cli.docs)
8234
- };
8235
- ctx.deliver(setup);
8236
- if (ctx.signal.aborted) throw new CliError({
8237
- code: INTERRUPTED,
8238
- message: `interrupted, but ${plan.package}@${setup.version} and its skill are already in place`,
8239
- hint: "nothing to run again"
8240
- });
8241
- await assertOnPath(cli, prefix, node, ctx);
8242
- return setup;
8243
- },
8244
- render: (setup) => renderSetup(cli, install, setup),
8245
- renderPlan: (plan) => [needsInstall(plan) ? `would install ${plan.package}@${plan.latest} with the npm under ${plan.prefix}` : `${plan.package}@${plan.from} is already installed under ${plan.prefix}`, ...renderPlannedSync(cli.name, plan.copies)].join("\n")
8246
- });
8247
- }
8248
- //#endregion
8249
- //#region ../agent-cli/dist/built-ins/update.js
8250
- const updateSchema = object({
8251
- from: string(),
8252
- latest: string(),
8253
- installed: object({
8254
- version: string(),
8255
- prefix: string()
8256
- }).nullable(),
8257
- skill: homeSyncSchema.nullable(),
8258
- upgrades: array(upgradeSchema)
8259
- });
8260
- function nothingToInstall({ from, latest }) {
8261
- return standingOf$1(from, latest) === "current" ? `already on ${from}, the latest release: nothing to install` : `${from} is newer than the latest release ${latest}: nothing to install`;
8262
- }
8263
- function renderUpdate(cli, install, update) {
8264
- const { installed } = update;
8265
- const skill = renderHomeSync(cli.name, update.skill);
8266
- return installed === null ? [nothingToInstall(update), ...skill].join("\n") : [
8267
- `installed ${install.package}@${installed.version} with the npm under ${installed.prefix}, replacing ${update.from}`,
8268
- ...skill,
8269
- ...update.upgrades.map(renderUpgrade)
8270
- ].join("\n");
8271
- }
8272
- function interruptedAfterInstall(plan) {
8273
- return new CliError({
8274
- code: INTERRUPTED,
8275
- message: `interrupted, but ${plan.package}@${plan.latest} had already been installed with the npm under ${plan.prefix}, replacing ${plan.from}`,
8276
- hint: "nothing to run again"
8277
- });
8278
- }
8279
- /** The built-in `update`, for a CLI installed globally with npm. */
8280
- function updateCommand(cli, install, { bin, node }) {
8281
- return defineCommand$1({
8282
- description: "install the latest version of this CLI from its registry",
8283
- examples: [{
8284
- command: `${cli.name} update`,
8285
- note: "install the latest version and show what changed"
8286
- }, {
8287
- command: `${cli.name} update --dry-run`,
8288
- note: "show the version it would install"
8289
- }],
8290
- effect: "write",
8291
- output: updateSchema,
8292
- plan: async (_input, ctx) => {
8293
- const npm = globalNpm(cli, install, ctx, node);
8294
- const copy = await globalInstallOf(bin, npm);
8295
- return {
8296
- package: install.package,
8297
- from: cli.version,
8298
- latest: await latestVersion(npm),
8299
- prefix: copy.prefix,
8300
- packageDir: copy.packageDir,
8301
- copies: cli.skill === void 0 ? [] : await homeCopiesOf(cli.name, ctx.env)
8302
- };
8303
- },
8304
- apply: async (plan, ctx) => {
8305
- const { from, latest, prefix } = plan;
8306
- if (standingOf$1(from, latest) !== "behind") return {
8307
- from,
8308
- latest,
8309
- installed: null,
8310
- skill: await syncHome(cli, plan.packageDir, ctx),
8311
- upgrades: []
8312
- };
8313
- ctx.signal.throwIfAborted();
8314
- ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
8315
- const skill = await ctx.runToEnd(`installing ${plan.package}@${latest}`, async () => {
8316
- await installGlobally(latest, globalNpm(cli, install, ctx, node));
8317
- return syncHome(cli, plan.packageDir, ctx);
8318
- });
8319
- const update = {
8320
- from,
8321
- latest,
8322
- installed: {
8323
- version: latest,
8324
- prefix
8325
- },
8326
- skill,
8327
- upgrades: await upgradesOf(plan.packageDir, from, latest)
8328
- };
8329
- if (ctx.signal.aborted) {
8330
- ctx.deliver(update);
8331
- throw interruptedAfterInstall(plan);
8332
- }
8333
- return update;
8334
- },
8335
- render: (update) => renderUpdate(cli, install, update),
8336
- renderPlan: (plan) => [standingOf$1(plan.from, plan.latest) === "behind" ? `would install ${plan.package}@${plan.latest} with the npm under ${plan.prefix}, replacing ${plan.from}` : nothingToInstall(plan), ...renderPlannedSync(cli.name, plan.copies)].join("\n")
8337
- });
8338
- }
8339
- //#endregion
8340
- //#region ../agent-cli/dist/context/files.js
8341
- const TEMP_FILE = /^\..+\.agent-cli-(\d+)\.tmp$/;
8342
- /** Where the Runtime writes `path` before moving it into place; a dead writer's is swept. */
8343
- function tempPathOf(path) {
8344
- return join(dirname(path), `.${basename(path)}.agent-cli-${process.pid}.tmp`);
8345
- }
8346
- function isAlive(pid) {
8347
- try {
8348
- process.kill(pid, 0);
8349
- return true;
8350
- } catch (error) {
8351
- return errnoOf(error) === "EPERM";
8352
- }
8353
- }
8354
- async function sweep(folder) {
8355
- const dead = (await readdir(folder)).filter((name) => {
8356
- const pid = TEMP_FILE.exec(name)?.[1];
8357
- return pid !== void 0 && !isAlive(Number(pid));
8358
- });
8359
- await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
8360
- }
8361
- /** The file a symlink at `path` points to, so a replacing write keeps the link. */
8362
- async function targetOf$1(path) {
8363
- try {
8364
- return await realpath(path);
8365
- } catch (error) {
8366
- if (isMissingFile(error)) return path;
8367
- throw error;
8368
- }
8369
- }
8370
- async function writeWithMode(path, data, mode) {
8371
- if (mode === void 0) {
8372
- await writeFile(path, data);
8373
- return;
8374
- }
8375
- await writeFile(path, data, { mode });
8376
- await chmod(path, mode);
8377
- }
8378
- /** What `link` answers on a file system without hard links, such as exFAT. */
8379
- const NO_HARD_LINKS = /* @__PURE__ */ new Set([
8380
- "EPERM",
8381
- "ENOTSUP",
8382
- "EXDEV"
8383
- ]);
8384
- /** A link fails atomically when `path` is taken, so no reader ever sees half a file there. */
8385
- async function linkNew(temp, path, data, mode) {
8386
- try {
8387
- await link(temp, path);
8388
- } catch (error) {
8389
- if (!NO_HARD_LINKS.has(errnoOf(error))) throw error;
8390
- await createNew(path, data, mode);
8391
- }
8392
- }
8393
- /** A file this call created and could not finish is removed, so a rerun does not find half of it. */
8394
- async function createNew(path, data, mode) {
8395
- const handle = await open(path, "wx", mode);
8396
- try {
8397
- await handle.writeFile(data);
8398
- if (mode !== void 0) await handle.chmod(mode);
8399
- } catch (error) {
8400
- await handle.close();
8401
- await rm(path, { force: true });
8402
- throw error;
8403
- }
8404
- await handle.close();
8405
- }
8406
- const RENAME_ATTEMPTS = 10;
8407
- const RENAME_BACKOFF_MS = 10;
8408
- const HELD_BY_ANOTHER_PROGRAM = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
8409
- async function renameRetrying(from, to) {
8410
- for (let attempt = 1;; attempt++) try {
8411
- await rename(from, to);
8412
- return;
8413
- } catch (error) {
8414
- if (!(process.platform === "win32" && HELD_BY_ANOTHER_PROGRAM.has(errnoOf(error))) || attempt === RENAME_ATTEMPTS) throw error;
8415
- await setTimeout$1(attempt * RENAME_BACKOFF_MS);
8416
- }
8417
- }
8418
- /** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
8419
- function realFiles() {
8420
- const swept = /* @__PURE__ */ new Set();
8421
- return {
8422
- exists: async (path) => {
8423
- try {
8424
- await access(path);
8425
- return true;
8426
- } catch (error) {
8427
- if (isNothingThere(error)) return false;
8428
- throw error;
8429
- }
8430
- },
8431
- write: async (path, data, options = {}) => {
8432
- await mkdir(dirname(path), { recursive: true });
8433
- const target = options.exclusive === true ? path : await targetOf$1(path);
8434
- const folder = dirname(target);
8435
- if (!swept.has(folder)) {
8436
- await sweep(folder);
8437
- swept.add(folder);
8438
- }
8439
- const temp = tempPathOf(target);
8440
- try {
8441
- await writeWithMode(temp, data, options.mode);
8442
- if (options.exclusive === true) {
8443
- await linkNew(temp, target, data, options.mode);
8444
- await rm(temp);
8445
- } else await renameRetrying(temp, target);
8446
- } catch (error) {
8447
- await rm(temp, { force: true });
8448
- throw error;
8449
- }
8450
- },
8451
- read: (path) => readFile(path),
8452
- list: (folder) => readdir(folder),
8453
- remove: (path) => rm(path, {
8454
- recursive: true,
8455
- force: true
8456
- }),
8457
- mkdir: async (path, options) => {
8458
- const mode = options?.mode;
8459
- if (options?.exclusive !== true) {
8460
- await mkdir(path, {
8461
- recursive: true,
8462
- mode
8463
- });
8464
- return;
8465
- }
8466
- await mkdir(dirname(path), {
8467
- recursive: true,
8468
- mode
8646
+ if (installs) {
8647
+ ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
8648
+ await installGlobally(latest, npmOfSetup(cli, install, ctx, launch));
8649
+ }
8650
+ return syncHome(cli, packageDir, ctx);
8651
+ }).catch((error) => {
8652
+ throw withSetupLine(error, install);
8469
8653
  });
8470
- await mkdir(path, { mode });
8654
+ const setup = {
8655
+ version: installs || from === null ? latest : from,
8656
+ prefix,
8657
+ installed: installs,
8658
+ skill,
8659
+ upgrades: installs && from !== null ? await upgradesOf(ctx.files, packageDir, from, latest) : [],
8660
+ docs: cli.docs === void 0 ? null : join(packageDir, cli.docs)
8661
+ };
8662
+ ctx.deliver(setup);
8663
+ if (ctx.signal.aborted) throw new CliError({
8664
+ code: INTERRUPTED,
8665
+ message: `interrupted, but ${plan.package}@${setup.version} and its skill are already in place`,
8666
+ hint: "nothing to run again"
8667
+ });
8668
+ await assertOnPath(cli, prefix, launch, ctx);
8669
+ return setup;
8471
8670
  },
8472
- stat: async (path) => {
8473
- try {
8474
- const { size, dev, ino } = await stat(path, { bigint: true });
8475
- return {
8476
- size: Number(size),
8477
- id: `${dev}:${ino}`
8478
- };
8479
- } catch (error) {
8480
- if (isNothingThere(error)) return void 0;
8481
- throw error;
8482
- }
8483
- }
8484
- };
8671
+ render: (setup) => renderSetup(cli, install, setup),
8672
+ renderPlan: (plan) => [needsInstall(plan) ? `would install ${plan.package}@${plan.latest} with the npm under ${plan.prefix}` : `${plan.package}@${plan.from} is already installed under ${plan.prefix}`, ...renderPlannedSync(cli.name, plan.copies)].join("\n")
8673
+ });
8485
8674
  }
8486
- /**
8487
- * `files` resolving paths against `cwd`, its failures classified by errno; `list` drops the
8488
- * Runtime's temp files and sorts.
8489
- */
8490
- function classifiedFiles(files, { cwd, fileError }) {
8491
- async function at(access, path, work) {
8492
- const target = resolve(cwd, path);
8493
- try {
8494
- return await work(target);
8495
- } catch (error) {
8496
- throw fileError(error, {
8497
- path: target,
8498
- access
8675
+ //#endregion
8676
+ //#region ../agent-cli/dist/built-ins/update.js
8677
+ const updateSchema = object({
8678
+ from: string(),
8679
+ latest: string(),
8680
+ installed: object({
8681
+ version: string(),
8682
+ prefix: string()
8683
+ }).nullable(),
8684
+ skill: homeSyncSchema.nullable(),
8685
+ upgrades: array(upgradeSchema)
8686
+ });
8687
+ function nothingToInstall({ from, latest }) {
8688
+ return standingOf$1(from, latest) === "current" ? `already on ${from}, the latest release: nothing to install` : `${from} is newer than the latest release ${latest}: nothing to install`;
8689
+ }
8690
+ function renderUpdate(cli, install, update) {
8691
+ const { installed } = update;
8692
+ const skill = renderHomeSync(cli.name, update.skill);
8693
+ return installed === null ? [nothingToInstall(update), ...skill].join("\n") : [
8694
+ `installed ${install.package}@${installed.version} with the npm under ${installed.prefix}, replacing ${update.from}`,
8695
+ ...skill,
8696
+ ...update.upgrades.map(renderUpgrade)
8697
+ ].join("\n");
8698
+ }
8699
+ function interruptedAfterInstall(plan) {
8700
+ return new CliError({
8701
+ code: INTERRUPTED,
8702
+ message: `interrupted, but ${plan.package}@${plan.latest} had already been installed with the npm under ${plan.prefix}, replacing ${plan.from}`,
8703
+ hint: "nothing to run again"
8704
+ });
8705
+ }
8706
+ /** The built-in `update`, for a CLI installed globally with npm. */
8707
+ function updateCommand(cli, install, launch) {
8708
+ return defineCommand$1({
8709
+ description: "install the latest version of this CLI from its registry",
8710
+ examples: [{
8711
+ command: `${cli.name} update`,
8712
+ note: "install the latest version and show what changed"
8713
+ }, {
8714
+ command: `${cli.name} update --dry-run`,
8715
+ note: "show the version it would install"
8716
+ }],
8717
+ effect: "write",
8718
+ errors: [NOT_GLOBAL_INSTALL, ...NPM_ERRORS],
8719
+ output: updateSchema,
8720
+ plan: async (_input, ctx) => {
8721
+ const npm = globalNpm(cli, install, ctx, launch);
8722
+ const copy = await globalInstallOf(launch.bin, npm);
8723
+ return {
8724
+ package: install.package,
8725
+ from: cli.version,
8726
+ latest: await latestVersion(npm),
8727
+ prefix: copy.prefix,
8728
+ packageDir: copy.packageDir,
8729
+ copies: cli.skill === void 0 ? [] : await homeCopiesOf(ctx.files, cli.name, ctx.env)
8730
+ };
8731
+ },
8732
+ apply: async (plan, ctx) => {
8733
+ const { from, latest, prefix } = plan;
8734
+ if (standingOf$1(from, latest) !== "behind") return {
8735
+ from,
8736
+ latest,
8737
+ installed: null,
8738
+ skill: await syncHome(cli, plan.packageDir, ctx),
8739
+ upgrades: []
8740
+ };
8741
+ ctx.signal.throwIfAborted();
8742
+ ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
8743
+ const skill = await ctx.runToEnd(`installing ${plan.package}@${latest}`, async () => {
8744
+ await installGlobally(latest, globalNpm(cli, install, ctx, launch));
8745
+ return syncHome(cli, plan.packageDir, ctx);
8499
8746
  });
8500
- }
8501
- }
8502
- return {
8503
- exists: (path) => at("read", path, (target) => files.exists(target)),
8504
- write: (path, data, options) => at("write", path, (target) => files.write(target, data, options)),
8505
- read: (path) => at("read", path, (target) => files.read(target)),
8506
- list: (folder) => at("read", folder, async (target) => (await files.list(target)).filter((name) => !TEMP_FILE.test(name)).sort()),
8507
- remove: (path) => at("write", path, (target) => files.remove(target)),
8508
- mkdir: (path, options) => at("write", path, (target) => files.mkdir(target, options)),
8509
- stat: (path) => at("read", path, (target) => files.stat(target))
8510
- };
8747
+ const update = {
8748
+ from,
8749
+ latest,
8750
+ installed: {
8751
+ version: latest,
8752
+ prefix
8753
+ },
8754
+ skill,
8755
+ upgrades: await upgradesOf(ctx.files, plan.packageDir, from, latest)
8756
+ };
8757
+ if (ctx.signal.aborted) {
8758
+ ctx.deliver(update);
8759
+ throw interruptedAfterInstall(plan);
8760
+ }
8761
+ return update;
8762
+ },
8763
+ render: (update) => renderUpdate(cli, install, update),
8764
+ renderPlan: (plan) => [standingOf$1(plan.from, plan.latest) === "behind" ? `would install ${plan.package}@${plan.latest} with the npm under ${plan.prefix}, replacing ${plan.from}` : nothingToInstall(plan), ...renderPlannedSync(cli.name, plan.copies)].join("\n")
8765
+ });
8511
8766
  }
8512
8767
  //#endregion
8513
8768
  //#region ../agent-cli/dist/built-ins/install/release-check.js
@@ -8572,25 +8827,19 @@ function parsedCheck(text) {
8572
8827
  return;
8573
8828
  }
8574
8829
  }
8575
- async function lastCheck(file) {
8576
- const text = await orMissing(readFile(file, "utf8"));
8830
+ async function lastCheck(files, file) {
8831
+ const text = await readIfAny(files, file);
8577
8832
  return text === void 0 ? void 0 : parsedCheck(text);
8578
8833
  }
8579
- async function writeCheck(file, check) {
8580
- await mkdir(dirname(file), { recursive: true });
8581
- const temp = tempPathOf(file);
8582
- await writeFile(temp, JSON.stringify(check));
8583
- await rename(temp, file);
8584
- }
8585
- async function startCheck(cli, install, file, last, { node }, ctx) {
8834
+ async function startCheck(cli, install, file, last, launch, ctx) {
8586
8835
  const checkedAt = ctx.clock.now();
8587
8836
  const previous = last?.latest ?? "";
8588
- await writeCheck(file, {
8837
+ await ctx.files.write(file, JSON.stringify({
8589
8838
  checkedAt,
8590
8839
  latest: last?.latest
8591
- });
8592
- const npm = globalNpm(cli, install, ctx, node);
8593
- const { path, env } = npmOf(npm, process.platform === "win32");
8840
+ }));
8841
+ const npm = globalNpm(cli, install, ctx, launch);
8842
+ const { path, env } = await npmOf(npm);
8594
8843
  await ctx.spawnDetached(process.execPath, [
8595
8844
  "-e",
8596
8845
  CHECK_SCRIPT,
@@ -8623,7 +8872,7 @@ async function releaseCheck(cli, install, runAs, launch, ctx) {
8623
8872
  }
8624
8873
  async function checkAndTell(cli, install, runAs, launch, ctx) {
8625
8874
  const file = cacheFileOf(cli.name, ctx.env);
8626
- const last = await lastCheck(file);
8875
+ const last = await lastCheck(ctx.files, file);
8627
8876
  if (last?.error !== void 0) debugLine(cli, ctx, last.error);
8628
8877
  if (last === void 0 || ctx.clock.now() - last.checkedAt >= CHECK_INTERVAL_MS) await startCheck(cli, install, file, last, launch, ctx);
8629
8878
  const latest = last?.latest;
@@ -8632,14 +8881,14 @@ async function checkAndTell(cli, install, runAs, launch, ctx) {
8632
8881
  }
8633
8882
  //#endregion
8634
8883
  //#region ../agent-cli/dist/built-ins/install/global.js
8635
- function checkOf(cli, install, { bin, node }) {
8884
+ function checkOf(cli, install, launch) {
8636
8885
  return {
8637
8886
  install: async (ctx) => {
8638
- const npm = globalNpm(cli, install, ctx, node);
8887
+ const npm = globalNpm(cli, install, ctx, launch);
8639
8888
  try {
8640
8889
  return {
8641
8890
  state: "ok",
8642
- message: `a global npm install under ${(await globalInstallOf(bin, npm)).prefix}`
8891
+ message: `a global npm install under ${(await globalInstallOf(launch.bin, npm)).prefix}`
8643
8892
  };
8644
8893
  } catch (error) {
8645
8894
  if (error instanceof CliError && error.code === "NOT_GLOBAL_INSTALL") return {
@@ -8653,7 +8902,7 @@ function checkOf(cli, install, { bin, node }) {
8653
8902
  release: async (ctx) => {
8654
8903
  const fix = `${cli.name} update`;
8655
8904
  return {
8656
- npm: globalNpm(cli, install, ctx, node),
8905
+ npm: globalNpm(cli, install, ctx, launch),
8657
8906
  advise: () => ({ fix })
8658
8907
  };
8659
8908
  }
@@ -8667,7 +8916,6 @@ function globalInstall(cli, install) {
8667
8916
  setup: setupCommand(cli, install, launch),
8668
8917
  update: updateCommand(cli, install, launch)
8669
8918
  }),
8670
- exitClasses: ["source"],
8671
8919
  check: (launch) => checkOf(cli, install, launch),
8672
8920
  notice: (launch, ctx) => releaseCheck(cli, install, adapter.runAs, launch, ctx)
8673
8921
  };
@@ -8684,7 +8932,6 @@ function noInstall(cli) {
8684
8932
  return {
8685
8933
  runAs: runAsOf(cli),
8686
8934
  builtIns: () => ({}),
8687
- exitClasses: [],
8688
8935
  check: () => ({
8689
8936
  install: skipped,
8690
8937
  release: async () => ({ skipped: await skipped() })
@@ -8715,9 +8962,9 @@ function withSection({ name, text: section }, text) {
8715
8962
  if (text === "") return `${whole}\n`;
8716
8963
  return `${text}${text.endsWith("\n") ? "" : "\n"}\n${whole}\n`;
8717
8964
  }
8718
- async function agentsMdAt(agents, root) {
8965
+ async function agentsMdAt(files, agents, root) {
8719
8966
  const path = join(root, "AGENTS.md");
8720
- const existing = await orMissing(readFile(path, "utf8"));
8967
+ const existing = await readIfAny(files, path);
8721
8968
  if (existing === void 0) return {
8722
8969
  path,
8723
8970
  agents,
@@ -8734,9 +8981,9 @@ async function agentsMdAt(agents, root) {
8734
8981
  change: existing.includes(markersOf(agents.name).begin) ? "rewrite" : "add"
8735
8982
  };
8736
8983
  }
8737
- async function handEditsAt(root) {
8984
+ async function handEditsAt(files, root) {
8738
8985
  const claudeMd = join(root, "CLAUDE.md");
8739
- const text = await orMissing(readFile(claudeMd, "utf8"));
8986
+ const text = await readIfAny(files, claudeMd);
8740
8987
  if (text === void 0 || text.split(/\r?\n/).some((line) => line.trim() === "@AGENTS.md")) return [];
8741
8988
  return [`add a line \`@AGENTS.md\` to ${claudeMd}, so Claude Code reads AGENTS.md`];
8742
8989
  }
@@ -8746,8 +8993,8 @@ function agentsRow(agents, install) {
8746
8993
  name: "agents",
8747
8994
  advisory: true,
8748
8995
  run: async (ctx) => {
8749
- const root = await projectRootOf(ctx.cwd);
8750
- const { path, change } = await agentsMdAt(agents, root);
8996
+ const root = await projectRootOf(ctx.files, ctx.cwd);
8997
+ const { path, change } = await agentsMdAt(ctx.files, agents, root);
8751
8998
  const fix = `${install.runAs} skill install`;
8752
8999
  if (change === "rewrite") return {
8753
9000
  state: "problem",
@@ -8760,7 +9007,7 @@ function agentsRow(agents, install) {
8760
9007
  fix
8761
9008
  };
8762
9009
  const holds = `${path} holds the ${name} Agents section`;
8763
- const [edit] = await handEditsAt(root);
9010
+ const [edit] = await handEditsAt(ctx.files, root);
8764
9011
  return edit === void 0 ? {
8765
9012
  state: "ok",
8766
9013
  message: holds
@@ -8774,8 +9021,8 @@ function agentsRow(agents, install) {
8774
9021
  //#endregion
8775
9022
  //#region ../agent-cli/dist/built-ins/skill/install-command.js
8776
9023
  /** The project root and the Skill copy under it, as `skill install` writes it. */
8777
- async function skillCopyOf(cli, cwd) {
8778
- const root = await projectRootOf(cwd);
9024
+ async function skillCopyOf(files, cli, cwd) {
9025
+ const root = await projectRootOf(files, cwd);
8779
9026
  return {
8780
9027
  root,
8781
9028
  copy: join(root, ".claude", "skills", cli.name)
@@ -8791,12 +9038,14 @@ const installSchema = object({
8791
9038
  }).nullable(),
8792
9039
  handEdits: array(string())
8793
9040
  });
8794
- async function planOf(cli, folder, agents, cwd, packageRoot) {
8795
- const { root, copy } = await skillCopyOf(cli, cwd);
9041
+ async function planOf(cli, folder, agents, { files, cwd, packageRoot }) {
9042
+ const source = join(packageRoot, folder);
9043
+ await readRequired(files, join(source, "SKILL.md"));
9044
+ const { root, copy } = await skillCopyOf(files, cli, cwd);
8796
9045
  return {
8797
- ...await syncOf(join(packageRoot, folder), copy),
8798
- agentsMd: agents === void 0 ? null : await agentsMdAt(agents, root),
8799
- handEdits: agents === void 0 ? [] : await handEditsAt(root)
9046
+ ...await syncOf(files, source, copy),
9047
+ agentsMd: agents === void 0 ? null : await agentsMdAt(files, agents, root),
9048
+ handEdits: agents === void 0 ? [] : await handEditsAt(files, root)
8800
9049
  };
8801
9050
  }
8802
9051
  const SECTION_VERBS = {
@@ -8839,12 +9088,12 @@ function skillInstallCommand(cli, folder, agents) {
8839
9088
  }],
8840
9089
  effect: "write",
8841
9090
  output: installSchema,
8842
- plan: (_input, ctx) => planOf(cli, folder, agents, ctx.cwd, ctx.packageRoot),
9091
+ plan: (_input, ctx) => planOf(cli, folder, agents, ctx),
8843
9092
  apply: async (plan, ctx) => {
8844
9093
  await applySync(plan, ctx);
8845
9094
  const { agentsMd } = plan;
8846
9095
  if (agentsMd !== null && agentsMd.change !== null) {
8847
- const existing = await orMissing(readFile(agentsMd.path, "utf8")) ?? "";
9096
+ const existing = await readIfAny(ctx.files, agentsMd.path) ?? "";
8848
9097
  await ctx.files.write(agentsMd.path, withSection(agentsMd.agents, existing));
8849
9098
  }
8850
9099
  return {
@@ -8866,13 +9115,13 @@ function skillInstallCommand(cli, folder, agents) {
8866
9115
  //#region ../agent-cli/dist/built-ins/skill/project.js
8867
9116
  async function personalCopyOf(name, copy, ctx) {
8868
9117
  const home = join(homeOf(ctx.env), ".claude", "skills", name);
8869
- const real = await orMissing(realpath(home));
8870
- return real === void 0 || real === await orMissing(realpath(copy)) ? void 0 : home;
9118
+ const real = await ctx.files.realPath(home);
9119
+ return real === void 0 || real === await ctx.files.realPath(copy) ? void 0 : home;
8871
9120
  }
8872
9121
  /** The project root's copy against the package's, and any under home. */
8873
9122
  async function findingOf(cli, folder, install, ctx) {
8874
- const { copy } = await skillCopyOf(cli, ctx.cwd);
8875
- const [installed, shipped] = await Promise.all([orMissing(readFile(join(copy, "SKILL.md"), "utf8")), readFile(join(ctx.packageRoot, folder, "SKILL.md"), "utf8")]);
9123
+ const { copy } = await skillCopyOf(ctx.files, cli, ctx.cwd);
9124
+ const [installed, shipped] = await Promise.all([readIfAny(ctx.files, join(copy, "SKILL.md")), readRequired(ctx.files, join(ctx.packageRoot, folder, "SKILL.md"))]);
8876
9125
  const stated = installed === void 0 ? void 0 : skillVersionIn(installed);
8877
9126
  const left = stated !== void 0 && skillVersionIn(shipped) === void 0;
8878
9127
  const found = left ? `${copy} (${statedVersionNote(stated)})` : copy;
@@ -9050,7 +9299,7 @@ function registryRow(cli, check) {
9050
9299
  function doctorChecks(cli, launch, scope) {
9051
9300
  const install = installOf(cli);
9052
9301
  const check = install.check(launch);
9053
- const tools = toolRows(cli.tools ?? [], cli.commands);
9302
+ const tools = toolRows(cli.tools ?? [], cli.commands, launch.platform);
9054
9303
  return {
9055
9304
  checks: [
9056
9305
  buildRow(cli),
@@ -9066,6 +9315,8 @@ function doctorChecks(cli, launch, scope) {
9066
9315
  tools: new Set(tools.map(({ name }) => name))
9067
9316
  };
9068
9317
  }
9318
+ //#endregion
9319
+ //#region ../agent-cli/dist/built-ins/doctor.js
9069
9320
  const DEFAULT_DEADLINE_MS = 1e4;
9070
9321
  const MS_PER_SECOND = 1e3;
9071
9322
  const MIN_LABEL_WIDTH = 8;
@@ -9136,12 +9387,12 @@ async function rowOf(check, ctx, { read, runAs, tools }) {
9136
9387
  check: check.name,
9137
9388
  ...finding
9138
9389
  },
9139
- advisory: check.advisory === true
9390
+ check
9140
9391
  };
9141
9392
  }
9142
9393
  function nextOf(checked, doctorLine) {
9143
9394
  const fixes = checked.flatMap(({ row }) => row.fix === void 0 ? [] : [row.fix]);
9144
- const unfixed = checked.some(({ row, advisory }) => advisory && row.state === "problem" && row.fix === void 0);
9395
+ const unfixed = checked.some(({ row, check }) => check.advisory === true && row.state === "problem" && row.fix === void 0);
9145
9396
  return [...new Set(unfixed ? [...fixes, doctorLine] : fixes)];
9146
9397
  }
9147
9398
  function render({ rows, next }, style) {
@@ -9170,9 +9421,14 @@ function failure(cliName, rerun, problems) {
9170
9421
  });
9171
9422
  return new AggregateError(errors);
9172
9423
  }
9424
+ function checkedRows(error, problems, table) {
9425
+ const undeclared = problems.flatMap(({ row, check }) => row.code !== void 0 && mustDeclare(row.code, table) && !check.errors?.includes(row.code) ? [`the ${check.name} row failed with ${row.code}`] : []);
9426
+ if (undeclared.length === 0) return error;
9427
+ return new Error(`doctor: ${undeclared.join("; ")}, which its errors do not declare`, { cause: error });
9428
+ }
9173
9429
  /** The built-in `doctor`: the fixed rows around the CLI's own, each under its own deadline. */
9174
9430
  function doctorCommand(cli, launch) {
9175
- return defineCommand$1({
9431
+ const doctor = defineCommand$1({
9176
9432
  description: "check the setup and say what fixes each problem",
9177
9433
  examples: [{
9178
9434
  command: `${cli.name} doctor`,
@@ -9201,15 +9457,20 @@ function doctorCommand(cli, launch) {
9201
9457
  ctx.deliver(report);
9202
9458
  ctx.signal.throwIfAborted();
9203
9459
  }
9204
- const problems = checked.filter(({ row, advisory }) => row.state === "problem" && !advisory).map(({ row }) => row);
9460
+ const problems = checked.filter(({ row, check }) => row.state === "problem" && check.advisory !== true);
9205
9461
  if (problems.length > 0) {
9206
9462
  ctx.deliver(report);
9207
- throw failure(cli.name, ctx.rerun(), problems);
9463
+ throw checkedRows(failure(cli.name, ctx.rerun(), problems.map(({ row }) => row)), problems, errorTableOf(cli));
9208
9464
  }
9209
9465
  return report;
9210
9466
  },
9211
9467
  render
9212
9468
  });
9469
+ const rowCodes = (cli.doctor ?? []).flatMap((row) => row.advisory === true ? [] : row.errors ?? []);
9470
+ return {
9471
+ ...doctor,
9472
+ errors: [.../* @__PURE__ */ new Set([DOCTOR_PROBLEMS, ...rowCodes])]
9473
+ };
9213
9474
  }
9214
9475
  //#endregion
9215
9476
  //#region ../agent-cli/dist/built-ins/commands.js
@@ -9309,19 +9570,6 @@ function specOf(commandLine, node, place) {
9309
9570
  return isCommand(node) ? commandSpec(commandLine, node, place) : group(commandLine, specsOf(commandLine, node, place));
9310
9571
  }
9311
9572
  //#endregion
9312
- //#region ../agent-cli/dist/error-table.js
9313
- /** The Error table a CLI declares, empty when it declares none. */
9314
- function errorTableOf({ errors }) {
9315
- return errors ?? {};
9316
- }
9317
- /** The `CliError` for a code of `Table`, as `cliError` and `ctx.fail` build it. */
9318
- function cliErrorOf(code, init, options) {
9319
- return new CliError({
9320
- ...init,
9321
- code
9322
- }, options);
9323
- }
9324
- //#endregion
9325
9573
  //#region ../agent-cli/dist/exit-classes.js
9326
9574
  const SIGNAL_EXIT_CODES = {
9327
9575
  SIGINT: 130,
@@ -9334,95 +9582,38 @@ const EXIT_CODES = {
9334
9582
  wait: 75,
9335
9583
  bug: 70
9336
9584
  };
9337
- const RUNTIME_EXIT_CLASSES = {
9338
- INVALID_INPUT: "caller",
9339
- UNKNOWN_OPTION: "caller",
9340
- UNKNOWN_COMMAND: "caller",
9341
- UNKNOWN_FIELD: "caller",
9342
- CONFIRMATION_REQUIRED: "caller",
9343
- CONFIRMATION_MISMATCH: "caller",
9344
- NOT_CONFIRMED: "operator",
9345
- NEEDS_OPERATOR: "operator",
9346
- OUTPUT_EXISTS: "caller",
9347
- STDIN_STALLED: "caller",
9348
- INTERRUPTED: "interrupted",
9349
- DEADLINE_EXCEEDED: "wait",
9350
- INTERNAL: "bug",
9351
- PERMISSION_DENIED: "operator",
9352
- DISK_FULL: "operator",
9353
- NOT_FOUND: "caller",
9354
- ALREADY_EXISTS: "caller",
9355
- NOT_A_FILE: "caller",
9356
- TOOL_FAILED: "operator",
9357
- TOOL_MISSING: "operator",
9358
- SECRET_MISSING: "operator",
9359
- PROJECT_INVALID: "operator",
9360
- DOCTOR_PROBLEMS: "operator",
9361
- NOT_GLOBAL_INSTALL: "operator",
9362
- NOT_ON_PATH: "operator",
9363
- REGISTRY_UNREACHABLE: "operator",
9364
- REGISTRY_AUTH: "operator",
9365
- INSTALL_FAILED: "operator",
9366
- NPM_FAILED: "operator",
9367
- NO_LATEST: "source"
9368
- };
9369
- function isRuntimeCode(code) {
9370
- return Object.hasOwn(RUNTIME_EXIT_CLASSES, code);
9371
- }
9372
9585
  /** The codes of `errors` that the Runtime already declares, which a CLI cannot redeclare. */
9373
9586
  function runtimeCodesIn(errors) {
9374
9587
  return Object.keys(errors).filter(isRuntimeCode);
9375
9588
  }
9376
- function exitClassOfOne(error, errors) {
9377
- if (!(error instanceof CliError)) return "bug";
9378
- if (error instanceof UsageError) return "caller";
9379
- if (isRuntimeCode(error.code)) return RUNTIME_EXIT_CLASSES[error.code];
9380
- return errors[error.code]?.class ?? "bug";
9381
- }
9382
- function isBug(error, errors) {
9383
- return exitClassOfOne(error, errors) === "bug";
9589
+ /** The class of `code` in the Runtime's Error table or else in the CLI's `errors`. */
9590
+ function classOfCode(code, errors) {
9591
+ return isRuntimeCode(code) ? runtimeErrors[code].class : errors[code]?.class;
9384
9592
  }
9385
- function isWait(error, errors) {
9386
- return exitClassOfOne(error, errors) === "wait";
9593
+ function declaredCodesOf(node) {
9594
+ return node.kind === "command" ? node.command.errors ?? [] : Object.values(node.commands).flatMap(declaredCodesOf);
9387
9595
  }
9388
- const ACT_FIRST = [
9389
- "caller",
9390
- "operator",
9391
- "source",
9392
- "wait",
9393
- "interrupted"
9394
- ];
9395
- function exitClassOf(error, errors) {
9396
- const classes = errorsOf(error).map((each) => exitClassOfOne(each, errors));
9397
- if (classes.includes("bug")) return "bug";
9398
- return ACT_FIRST.find((exitClass) => classes.includes(exitClass)) ?? "bug";
9596
+ function isDeclarable(exitClass) {
9597
+ return exitClass !== void 0 && exitClass !== "bug" && exitClass !== "interrupted";
9399
9598
  }
9400
9599
  /**
9401
- * The classes other than bug and interrupt that a run of `cli` can end in: those of the Runtime's
9402
- * codes it can reach, and those of its own table.
9600
+ * The classes other than bug and interrupt that a run of `cli` can end in: those of the Runtime
9601
+ * codes any Command can meet, those of the codes its Commands under `root` declare, Built-ins
9602
+ * included, and those of its own table.
9403
9603
  */
9404
- function reachableExitClasses(cli) {
9405
- const runtime = [
9406
- "caller",
9407
- "operator",
9408
- "wait",
9409
- ...cli.commands === void 0 ? [] : installOf(cli).exitClasses
9604
+ function reachableExitClasses(cli, root) {
9605
+ const table = errorTableOf(cli);
9606
+ const classes = [
9607
+ ...anyCommandClasses,
9608
+ ...declaredCodesOf(root).map((code) => classOfCode(code, table)),
9609
+ ...Object.values(table).map((declaration) => declaration.class)
9410
9610
  ];
9411
- const own = Object.values(errorTableOf(cli)).map((declaration) => declaration.class);
9412
- return /* @__PURE__ */ new Set([...runtime, ...own]);
9611
+ return new Set(classes.filter(isDeclarable));
9413
9612
  }
9414
9613
  /** The signal named by an interrupt's abort reason; any other reason counts as Ctrl-C. */
9415
9614
  function stopSignalOf(reason) {
9416
9615
  return reason === "SIGTERM" ? "SIGTERM" : "SIGINT";
9417
9616
  }
9418
- /**
9419
- * The exit code of a run that failed with `error`, given the CLI's error table; `stop` is the
9420
- * signal that interrupted it, if any.
9421
- */
9422
- function exitCodeOf(error, errors, stop) {
9423
- const exitClass = exitClassOf(error, errors);
9424
- return exitClass === "interrupted" ? SIGNAL_EXIT_CODES[stopSignalOf(stop?.reason)] : EXIT_CODES[exitClass];
9425
- }
9426
9617
  //#endregion
9427
9618
  //#region ../agent-cli/dist/agent-cli.js
9428
9619
  function checkOwnCodes(errors) {
@@ -9454,9 +9645,11 @@ function checkCli(cli) {
9454
9645
  checkSkill(cli);
9455
9646
  const { commands } = cli;
9456
9647
  checkTree(commands, [], table);
9648
+ for (const row of cli.doctor ?? []) checkErrors(row.errors ?? [], `the ${row.name} row of doctor`, table);
9457
9649
  const taken = Object.keys(builtInCommands(cli, {
9458
9650
  bin: void 0,
9459
- node: void 0
9651
+ node: void 0,
9652
+ platform: process.platform
9460
9653
  })).filter((name) => commands[name] !== void 0);
9461
9654
  if (taken.length > 0) throw new Error(`${cli.name} defines ${taken.join(", ")}, a built-in Command it cannot replace`);
9462
9655
  return cli;
@@ -9844,37 +10037,6 @@ function route(root, invocation) {
9844
10037
  return walk(root, argv.filter((word, index) => !(index < options.length && word === JSON_FLAG.flag)), json, invocation);
9845
10038
  }
9846
10039
  //#endregion
9847
- //#region ../agent-cli/dist/command-line/route-line.js
9848
- /** Routes `argv`, the line's words after the Run-as, to a Command of `root`. */
9849
- function routeLine(root, runAs, argv) {
9850
- if (!argv.ok) return {
9851
- ok: false,
9852
- reason: `it does not tokenize: ${argv.reason}`
9853
- };
9854
- const invocation = {
9855
- runAs,
9856
- argv: argv.words,
9857
- flags: []
9858
- };
9859
- const routed = route(root, invocation);
9860
- switch (routed.kind) {
9861
- case "usage error": return {
9862
- ok: false,
9863
- reason: routed.error.message
9864
- };
9865
- case "help":
9866
- case "version": return {
9867
- ok: false,
9868
- reason: "it names no Command"
9869
- };
9870
- case "command": return {
9871
- ok: true,
9872
- routed,
9873
- invocation
9874
- };
9875
- }
9876
- }
9877
- //#endregion
9878
10040
  //#region ../agent-cli/dist/command-line/shell-words.js
9879
10041
  const BLANK = /\s/;
9880
10042
  const SHELL_SYNTAX = /* @__PURE__ */ new Set([
@@ -9973,39 +10135,126 @@ function shellWords(line) {
9973
10135
  };
9974
10136
  }
9975
10137
  //#endregion
9976
- //#region ../agent-cli/dist/check-next-steps.js
9977
- function argvOf(words, runAs) {
9978
- const line = shellWords(lineOf(words, runAs));
9979
- const lead = shellWords(runAs);
9980
- if (!lead.ok) return lead;
9981
- if (!line.ok) return line;
9982
- return {
9983
- ok: true,
9984
- words: line.words.slice(lead.words.length)
10138
+ //#region ../agent-cli/dist/command-line/line-problem.js
10139
+ const STDIN_TEXT = "text from stdin";
10140
+ /**
10141
+ * Why `line` would not run a Command of `root`: it does not tokenize, start with its lead, route to
10142
+ * a Command or pass its schemas; `undefined` when it would.
10143
+ */
10144
+ async function lineProblem(root, { text, lead, runAs }) {
10145
+ const words = shellWords(text);
10146
+ if (!words.ok) return `it does not tokenize: ${words.reason}`;
10147
+ const leadWords = shellWords(lead);
10148
+ if (!leadWords.ok) return `its lead does not tokenize: ${leadWords.reason}`;
10149
+ if (leadWords.words.some((word, index) => words.words[index] !== word)) return `it does not start with \`${lead}\``;
10150
+ const invocation = {
10151
+ runAs,
10152
+ argv: words.words.slice(leadWords.words.length),
10153
+ flags: []
9985
10154
  };
10155
+ const routed = route(root, invocation);
10156
+ switch (routed.kind) {
10157
+ case "usage error": return routed.error.message;
10158
+ case "help":
10159
+ case "version": return "it names no Command";
10160
+ case "command": try {
10161
+ await parseCommand(routed, invocation, async () => STDIN_TEXT);
10162
+ return;
10163
+ } catch (error) {
10164
+ if (error instanceof UsageError) return error.message;
10165
+ throw error;
10166
+ }
10167
+ }
9986
10168
  }
10169
+ //#endregion
10170
+ //#region ../agent-cli/dist/check-next-steps.js
9987
10171
  /**
9988
10172
  * Why `step` would not run a Command of `root`, or `undefined` when it would. A line given whole is
9989
10173
  * not checked, since it may run another program.
9990
10174
  */
9991
- function nextStepProblem(root, runAs, step) {
10175
+ async function nextStepProblem(root, runAs, step) {
9992
10176
  const words = wordsOf(step);
9993
10177
  if (words === void 0) return void 0;
9994
- const line = routeLine(root, runAs, argvOf(words, runAs));
9995
- if (!line.ok) return line.reason;
9996
- const { routed } = line;
9997
- const { unknownOptions } = tokenize(routed.words, knownFlags(routed.spec));
9998
- return unknownOptions.length === 0 ? void 0 : `unknown option ${unknownOptions.join(", ")}`;
9999
- }
10000
- /** `error`, or a bug naming each of its Next steps that would not run. */
10001
- function checkedNextSteps(error, check, runAs) {
10002
- const problems = errorsOf(error).flatMap((leaf) => leaf instanceof CliError ? leaf.next.flatMap((step) => {
10003
- const problem = check(step);
10004
- return problem === void 0 ? [] : [`\`${lineOf(step, runAs)}\`, a Next step of ${leaf.code}: ${problem}`];
10005
- }) : []);
10178
+ return lineProblem(root, {
10179
+ text: lineOf(words, runAs),
10180
+ lead: runAs,
10181
+ runAs
10182
+ });
10183
+ }
10184
+ /** The check that a failure's Next steps run Commands of `root`: a bug names each that would not. */
10185
+ function nextStepCheck(root, runAs) {
10186
+ return (error) => checkedNextSteps(error, root, runAs);
10187
+ }
10188
+ async function checkedNextSteps(error, root, runAs) {
10189
+ const problems = [];
10190
+ for (const leaf of errorsOf(error)) {
10191
+ if (!(leaf instanceof CliError)) continue;
10192
+ for (const step of leaf.next) {
10193
+ const problem = await nextStepProblem(root, runAs, step);
10194
+ if (problem !== void 0) problems.push(`\`${lineOf(step, runAs)}\`, a Next step of ${leaf.code}: ${problem}`);
10195
+ }
10196
+ }
10006
10197
  return problems.length === 0 ? error : new Error(`a Next step would not run: ${problems.join("; ")}`, { cause: error });
10007
10198
  }
10008
10199
  //#endregion
10200
+ //#region ../agent-cli/dist/failure.js
10201
+ /**
10202
+ * What a run that threw `error` reports: the interrupt and deadline rewrite, then the undeclared
10203
+ * code check, then the Next step check, each leaf classified, and the exit code they give.
10204
+ */
10205
+ async function failureOf(error, sources) {
10206
+ const { command, control, nextStepCheck } = sources;
10207
+ const rewritten = control === void 0 ? error : control.errorAfter(error);
10208
+ const declared = command === void 0 ? rewritten : checkedCodes(rewritten, command, sources);
10209
+ return failureFrom(classified(nextStepCheck === void 0 ? declared : await nextStepCheck(declared), sources.errors), sources.stop);
10210
+ }
10211
+ /** `failure` with `error`'s leaves after its own, which may change its exit code. */
10212
+ function withLeaves(failure, error, sources) {
10213
+ return failureFrom([...failure.leaves, ...classified(error, sources.errors)], sources.stop);
10214
+ }
10215
+ function classified(error, errors) {
10216
+ return errorsOf(error).map((leaf) => ({
10217
+ error: leaf,
10218
+ exitClass: exitClassOfOne(leaf, errors)
10219
+ }));
10220
+ }
10221
+ function failureFrom(leaves, stop) {
10222
+ const exitClass = exitClassOf(leaves);
10223
+ return {
10224
+ leaves,
10225
+ exitCode: exitClass === "interrupted" ? SIGNAL_EXIT_CODES[stopSignalOf(stop?.reason)] : EXIT_CODES[exitClass]
10226
+ };
10227
+ }
10228
+ function exitClassOfOne(error, errors) {
10229
+ if (!(error instanceof CliError)) return "bug";
10230
+ if (error instanceof UsageError) return "caller";
10231
+ return classOfCode(error.code, errors) ?? "bug";
10232
+ }
10233
+ const ACT_FIRST = [
10234
+ "caller",
10235
+ "operator",
10236
+ "source",
10237
+ "wait",
10238
+ "interrupted"
10239
+ ];
10240
+ function exitClassOf(leaves) {
10241
+ const classes = leaves.map((leaf) => leaf.exitClass);
10242
+ if (classes.includes("bug")) return "bug";
10243
+ return ACT_FIRST.find((exitClass) => classes.includes(exitClass)) ?? "bug";
10244
+ }
10245
+ /**
10246
+ * `error`, or a bug naming each code in it that the Command must declare in its `errors` and does
10247
+ * not: one of the Error table, or a Runtime code only some Commands reach.
10248
+ */
10249
+ function checkedCodes(error, { command, path }, { cliName, errors }) {
10250
+ const declared = new Set(command.errors ?? []);
10251
+ const undeclared = errorsOf(error).flatMap((leaf) => leaf instanceof CliError && mustDeclare(leaf.code, errors) && !declared.has(leaf.code) ? [leaf.code] : []);
10252
+ if (undeclared.length === 0) return error;
10253
+ const label = commandLabel(path, cliName).join(" ");
10254
+ const codes = [...new Set(undeclared)].join(", ");
10255
+ return new Error(`\`${label}\` threw ${codes}, which its errors do not declare`, { cause: error });
10256
+ }
10257
+ //#endregion
10009
10258
  //#region ../agent-cli/dist/help.js
10010
10259
  function table(rows) {
10011
10260
  const width = Math.max(...rows.map(([left]) => left.length));
@@ -10043,10 +10292,10 @@ const EXIT_MEANINGS = {
10043
10292
  source: "someone at the source acts",
10044
10293
  wait: "not done yet: do what the hint says, then run it again"
10045
10294
  };
10046
- function exitCodeRows(cli) {
10295
+ function exitCodeRows(cli, root) {
10047
10296
  return [
10048
10297
  ["0", "done"],
10049
- ...[...[...reachableExitClasses(cli)].map((exitClass) => [EXIT_CODES[exitClass], EXIT_MEANINGS[exitClass]]), [EXIT_CODES.bug, `a bug in ${cli.name}`]].sort(([a], [b]) => a - b).map(([code, meaning]) => [String(code), meaning]),
10298
+ ...[...[...reachableExitClasses(cli, root)].map((exitClass) => [EXIT_CODES[exitClass], EXIT_MEANINGS[exitClass]]), [EXIT_CODES.bug, `a bug in ${cli.name}`]].sort(([a], [b]) => a - b).map(([code, meaning]) => [String(code), meaning]),
10050
10299
  [Object.values(SIGNAL_EXIT_CODES).join(", "), "interrupted"]
10051
10300
  ];
10052
10301
  }
@@ -10064,7 +10313,7 @@ function examplesSection(examples, style) {
10064
10313
  }
10065
10314
  /** The CLI's own help: a listing or its single Command's help, ending with exit codes and examples. */
10066
10315
  function cliHelp(cli, root, style) {
10067
- return lines(...root.kind === "command" ? commandLines(root, style, { examples: false }) : listingLines(`${cli.name} ${cli.version}`, root, style, cli.description), ...section("Exit codes", exitCodeRows(cli), style), ...examplesSection(rootExamples(root), style));
10316
+ return lines(...root.kind === "command" ? commandLines(root, style, { examples: false }) : listingLines(`${cli.name} ${cli.version}`, root, style, cli.description), ...section("Exit codes", exitCodeRows(cli, root), style), ...examplesSection(rootExamples(root), style));
10068
10317
  }
10069
10318
  function groupHelp(group, style) {
10070
10319
  return lines(...listingLines(group.commandLine, group, style));
@@ -10142,7 +10391,7 @@ function commandData(spec, table) {
10142
10391
  secrets: spec.command.secrets ?? [],
10143
10392
  errors: (spec.command.errors ?? []).map((code) => ({
10144
10393
  code,
10145
- class: table[code]?.class
10394
+ class: classOfCode(code, table)
10146
10395
  })),
10147
10396
  args: inputSchema(spec.command.args ?? object({})),
10148
10397
  options: optionsSchema(spec.options),
@@ -10175,7 +10424,10 @@ function errorsData(errors) {
10175
10424
  function publishedTable(table, root) {
10176
10425
  if (root.kind !== "command") return table;
10177
10426
  const declared = new Set(root.command.errors ?? []);
10178
- return Object.fromEntries(Object.entries(table).filter(([code]) => declared.has(code)));
10427
+ return Object.fromEntries(Object.entries({
10428
+ ...table,
10429
+ ...declaredRuntimeErrors
10430
+ }).filter(([code]) => declared.has(code)));
10179
10431
  }
10180
10432
  function cliHelpJson(cli, root) {
10181
10433
  const { name, version, description } = cli;
@@ -10225,15 +10477,20 @@ function noticeLine(text, json) {
10225
10477
  function stderrLine(key, text, json) {
10226
10478
  return json ? `${JSON.stringify({ [key]: text })}\n` : `${text}\n`;
10227
10479
  }
10228
- function errorText(error, format) {
10229
- const text = formatRuntimeError(error, {
10480
+ function errorText({ leaves }, format) {
10481
+ const text = formatRuntimeError(leaves, {
10230
10482
  ...format,
10231
- runAs: format.invocation.runAs,
10232
- isBug: (each) => isBug(each, format.errors),
10233
- isWait: (each) => isWait(each, format.errors)
10483
+ runAs: format.invocation.runAs
10234
10484
  });
10235
10485
  const { usageHelp, json } = format;
10236
- return usageHelp !== void 0 && !json && error instanceof UsageError ? `${text}\n${usageHelp}` : text;
10486
+ return usageHelp !== void 0 && !json && leaves.some((leaf) => leaf.error instanceof UsageError) ? `${text}\n${usageHelp}` : text;
10487
+ }
10488
+ function failedResult(failure, format) {
10489
+ return {
10490
+ stdout: "",
10491
+ stderr: errorText(failure, format),
10492
+ exitCode: failure.exitCode
10493
+ };
10237
10494
  }
10238
10495
  function stdoutOf({ value, render }, format, spill) {
10239
10496
  const text = printed(value, render, format);
@@ -10251,14 +10508,9 @@ const STOPPED_RESULT = {
10251
10508
  stdout: "",
10252
10509
  stderr: "",
10253
10510
  exitCode: SUCCESS_EXIT_CODE
10254
- };
10255
- function errorResult(error, format) {
10256
- const checked = format.checkNextStep === void 0 ? error : checkedNextSteps(error, format.checkNextStep, format.invocation.runAs);
10257
- return {
10258
- stdout: "",
10259
- stderr: errorText(checked, format),
10260
- exitCode: exitCodeOf(checked, format.errors, format.stop)
10261
- };
10511
+ };
10512
+ async function errorResult(error, format) {
10513
+ return failedResult(await failureOf(error, format), format);
10262
10514
  }
10263
10515
  async function commandResult(settled, format, spill) {
10264
10516
  if (settled.ok) try {
@@ -10272,23 +10524,23 @@ async function commandResult(settled, format, spill) {
10272
10524
  }
10273
10525
  const { error, delivered } = settled;
10274
10526
  if (delivered === void 0) return errorResult(error, format);
10527
+ const failure = await failureOf(error, format);
10275
10528
  let outcome;
10276
10529
  try {
10277
10530
  outcome = delivered();
10278
10531
  } catch (rejected) {
10279
- return errorResult(new AggregateError([...errorsOf(error), rejected]), format);
10532
+ return failedResult(withLeaves(failure, rejected, format), format);
10280
10533
  }
10281
- const result = errorResult(error, format);
10534
+ const result = failedResult(failure, format);
10282
10535
  try {
10283
10536
  return {
10284
10537
  ...result,
10285
10538
  stdout: await stdoutOf(outcome, format, spill)
10286
10539
  };
10287
10540
  } catch (spillError) {
10288
- const both = new AggregateError([...errorsOf(error), spillError]);
10289
10541
  return {
10290
10542
  ...result,
10291
- stderr: errorText(both, format)
10543
+ stderr: errorText(withLeaves(failure, spillError, format), format)
10292
10544
  };
10293
10545
  }
10294
10546
  }
@@ -10352,107 +10604,6 @@ function rootSpec(cli, launch) {
10352
10604
  } : groupSpec(names, cli.commands, builtInCommands(cli, launch));
10353
10605
  }
10354
10606
  //#endregion
10355
- //#region ../agent-cli/dist/command-errors.js
10356
- /**
10357
- * `error`, or a bug naming each Error table code in it that the Command does not declare in its
10358
- * `errors`. Runtime codes are never in the table, and a built-in's failures, `doctor` rows'
10359
- * included, belong to no declaration, so both pass.
10360
- */
10361
- function checkedCodes(error, { command, path, builtIn }, cli) {
10362
- if (builtIn) return error;
10363
- const table = errorTableOf(cli);
10364
- const declared = new Set(command.errors ?? []);
10365
- const undeclared = errorsOf(error).flatMap((leaf) => leaf instanceof CliError && Object.hasOwn(table, leaf.code) && !declared.has(leaf.code) ? [leaf.code] : []);
10366
- if (undeclared.length === 0) return error;
10367
- const label = commandLabel(path, cli.name).join(" ");
10368
- const codes = [...new Set(undeclared)].join(", ");
10369
- return new Error(`\`${label}\` threw ${codes}, which its errors do not declare`, { cause: error });
10370
- }
10371
- //#endregion
10372
- //#region ../agent-cli/dist/context/operator.js
10373
- const NEEDS_OPERATOR = "NEEDS_OPERATOR";
10374
- /** Fails with `NEEDS_OPERATOR` when a Coding agent runs an Operator Command. */
10375
- function checkOperator({ operator, commandLine }, env, rerun) {
10376
- if (!operator || !isAgent(env)) return;
10377
- throw new CliError({
10378
- code: NEEDS_OPERATOR,
10379
- message: `\`${commandLine}\` must be run by a person at a terminal, not a Coding agent`,
10380
- hint: "ask the operator to type ! then the Next step",
10381
- next: [rerun]
10382
- });
10383
- }
10384
- //#endregion
10385
- //#region ../agent-cli/dist/context/outputs.js
10386
- const OUTPUT_EXISTS = "OUTPUT_EXISTS";
10387
- /** Refuses a run that would overwrite existing outputs unless the caller passed `--force`. */
10388
- async function checkOutputs({ rerun, commandLine, force, files }, outputs) {
10389
- if (outputs === void 0 || force) return;
10390
- const found = await Promise.all(outputs.map((path) => files.exists(path)));
10391
- const existing = outputs.filter((_path, index) => found[index]);
10392
- if (existing.length === 0) return;
10393
- const them = existing.length === 1 ? "it" : "them";
10394
- throw new CliError({
10395
- code: OUTPUT_EXISTS,
10396
- message: `\`${commandLine}\` would overwrite ${existing.join(", ")}`,
10397
- hint: `once the user agrees to replace ${them}, run the Next step`,
10398
- next: [rerun({ add: [FORCE_FLAG.flag] })]
10399
- });
10400
- }
10401
- /** Guards the writes to declared outputs of `files`, whose paths resolve against `cwd`. */
10402
- function outputsGuard(files, cwd) {
10403
- const unwritten = /* @__PURE__ */ new Set();
10404
- let forceStep = [];
10405
- const exclusiveWrite = async (path, data, options) => {
10406
- const target = resolve(cwd, path);
10407
- unwritten.delete(target);
10408
- try {
10409
- await files.write(path, data, {
10410
- ...options,
10411
- exclusive: true
10412
- });
10413
- } catch (error) {
10414
- unwritten.add(target);
10415
- if (!(error instanceof CliError) || error.code !== "ALREADY_EXISTS") throw error;
10416
- throw new CliError({
10417
- code: ALREADY_EXISTS,
10418
- message: error.message,
10419
- hint: "once the user agrees to replace it, run the Next step",
10420
- next: [forceStep]
10421
- }, { cause: error });
10422
- }
10423
- };
10424
- return {
10425
- files: {
10426
- ...files,
10427
- write: (path, data, options) => unwritten.has(resolve(cwd, path)) ? exclusiveWrite(path, data, options) : files.write(path, data, options)
10428
- },
10429
- guard: (outputs, step) => {
10430
- forceStep = step;
10431
- for (const path of outputs) unwritten.add(resolve(cwd, path));
10432
- }
10433
- };
10434
- }
10435
- /**
10436
- * Creates each output folder and a temp file in it, so a sandbox denial fails before `apply`
10437
- * spends anything. The temp file's name is the Runtime's, so a crashed run's leftover is swept.
10438
- */
10439
- async function probeOutputs({ files, cwd, fileError }, outputs) {
10440
- const folders = new Set(outputs.map((path) => dirname(resolve(cwd, path))));
10441
- await Promise.all([...folders].map(async (folder) => {
10442
- const probe = tempPathOf(join(folder, "outputs-probe"));
10443
- try {
10444
- await files.mkdir(folder);
10445
- await files.write(probe, "");
10446
- await files.remove(probe);
10447
- } catch (error) {
10448
- throw fileError(error, {
10449
- path: folder,
10450
- access: "write"
10451
- });
10452
- }
10453
- }));
10454
- }
10455
- //#endregion
10456
10607
  //#region ../agent-cli/dist/context/spill.js
10457
10608
  const STDOUT_BUDGET_CHARS = 3e4;
10458
10609
  const SPILL_RETENTION_MS = 6048e5;
@@ -11033,43 +11184,6 @@ function resolveOptions(options) {
11033
11184
  };
11034
11185
  }
11035
11186
  //#endregion
11036
- //#region ../agent-cli/dist/context/confirm.js
11037
- const CONFIRMATION_REQUIRED = "CONFIRMATION_REQUIRED";
11038
- const CONFIRMATION_MISMATCH = "CONFIRMATION_MISMATCH";
11039
- function checkConfirmation({ rerun, commandLine, given }, expected) {
11040
- if (given === expected) return;
11041
- const advice = {
11042
- hint: "once the user confirms the target, run the Next step",
11043
- next: [rerun({
11044
- remove: [CONFIRM_FLAG.flag],
11045
- add: [CONFIRM_FLAG.flag, expected]
11046
- })]
11047
- };
11048
- if (given === void 0) throw new UsageError({
11049
- code: CONFIRMATION_REQUIRED,
11050
- message: `\`${commandLine}\` is destructive and needs ${CONFIRM_FLAG.flag}`,
11051
- ...advice
11052
- });
11053
- throw new UsageError({
11054
- code: CONFIRMATION_MISMATCH,
11055
- message: `${CONFIRM_FLAG.flag} ${JSON.stringify(given)} does not match the target of \`${commandLine}\``,
11056
- ...advice
11057
- });
11058
- }
11059
- //#endregion
11060
- //#region ../agent-cli/dist/context/cost.js
11061
- const NOT_CONFIRMED = "NOT_CONFIRMED";
11062
- /** Refuses a run whose Plan states a cost the caller has not approved with `--yes`. */
11063
- function checkCost({ rerun, commandLine, yes }, cost) {
11064
- if (cost === void 0 || yes) return;
11065
- throw new CliError({
11066
- code: NOT_CONFIRMED,
11067
- message: `\`${commandLine}\` needs ${YES_FLAG.flag}: ${cost}`,
11068
- hint: "once the user approves the cost, run the Next step",
11069
- next: [rerun({ add: [YES_FLAG.flag] })]
11070
- });
11071
- }
11072
- //#endregion
11073
11187
  //#region ../agent-cli/dist/execute.js
11074
11188
  const PLAN_INDENT = " ";
11075
11189
  function outcomeOf(value, render) {
@@ -11117,8 +11231,8 @@ function serveContextOf(ctx, end) {
11117
11231
  const serveCtx = Object.create(ctx);
11118
11232
  return Object.assign(serveCtx, { end });
11119
11233
  }
11120
- async function execute(command, input, ctx, { dryRun, confirm, yes, page, commandLine, started, files, guardOutputs }) {
11121
- await checkTools(command.tools ?? [], ctx.env, ctx.rerun());
11234
+ async function execute(command, input, ctx, { flags, commandLine, started, outputs: runOutputs }) {
11235
+ await checkTools(command.tools ?? [], ctx, ctx.rerun());
11122
11236
  const { env, envFile, fileError } = ctx;
11123
11237
  checkSecrets(command.secrets ?? [], {
11124
11238
  env,
@@ -11129,7 +11243,7 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, page, comman
11129
11243
  const { render } = command;
11130
11244
  return outcomeOf(await command.run({
11131
11245
  ...input,
11132
- ...page
11246
+ ...flags.page
11133
11247
  }, ctx), (data, style) => render === void 0 ? encode(data.items) : render(data, style));
11134
11248
  }
11135
11249
  if (command.effect === "read" || command.effect === "act") return dataOutcome(command, await command.run(input, ctx), commandLine);
@@ -11144,32 +11258,28 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, page, comman
11144
11258
  return outcome;
11145
11259
  }
11146
11260
  const plan = await command.plan(input, ctx);
11147
- if (dryRun) return dryRunOutcome(command, plan, commandLine);
11261
+ if (ctx.dryRun) return dryRunOutcome(command, plan, commandLine);
11148
11262
  const outputs = command.outputs?.(plan);
11149
- await checkOutputs({
11150
- rerun: ctx.rerun,
11263
+ const { rerun, force } = ctx;
11264
+ if (outputs !== void 0) await runOutputs.refuseOverwrite(outputs, {
11265
+ rerun,
11151
11266
  commandLine,
11152
- force: ctx.force,
11153
- files: ctx.files
11154
- }, outputs);
11267
+ force
11268
+ });
11155
11269
  checkCost({
11156
- rerun: ctx.rerun,
11270
+ rerun,
11157
11271
  commandLine,
11158
- yes
11272
+ yes: flags.yes
11159
11273
  }, command.cost?.(plan));
11160
11274
  if (command.effect === "destructive") checkConfirmation({
11161
- rerun: ctx.rerun,
11275
+ rerun,
11162
11276
  commandLine,
11163
- given: confirm
11277
+ given: flags.confirm
11164
11278
  }, command.confirm(plan));
11165
- if (outputs !== void 0) {
11166
- await probeOutputs({
11167
- files,
11168
- cwd: ctx.cwd,
11169
- fileError: ctx.fileError
11170
- }, outputs);
11171
- if (!ctx.force) guardOutputs(outputs, ctx.rerun({ add: [FORCE_FLAG.flag] }));
11172
- }
11279
+ if (outputs !== void 0) await runOutputs.claim(outputs, {
11280
+ rerun,
11281
+ force
11282
+ });
11173
11283
  return dataOutcome(command, await command.apply(plan, ctx), commandLine);
11174
11284
  }
11175
11285
  const manifestSchema = object({ bugs: union([string(), object({ url: string() })]).transform((value) => typeof value === "string" ? value : value.url).optional().catch(void 0) });
@@ -11185,16 +11295,7 @@ function manifestAt(packageRoot) {
11185
11295
  return parsed.success ? parsed.data : NO_MANIFEST;
11186
11296
  }
11187
11297
  //#endregion
11188
- //#region ../agent-cli/dist/run-command.js
11189
- function stdinNeverEndedHint(callerLine) {
11190
- return `stdin never ended: pipe the text into \`${callerLine}\`, or redirect a file into it`;
11191
- }
11192
- function runDeadlineAdvice(resumed, pipedLine) {
11193
- return resumed === void 0 ? { hint: stdinNeverEndedHint(pipedLine) } : {
11194
- hint: "run the Next step to continue the work",
11195
- next: [resumed]
11196
- };
11197
- }
11298
+ //#region ../agent-cli/dist/run-scope.js
11198
11299
  function selfOf(name, bin) {
11199
11300
  return bin === void 0 ? [name] : [
11200
11301
  process.execPath,
@@ -11213,49 +11314,83 @@ function portsOf(cli, replacement) {
11213
11314
  if (replacement !== void 0) return () => replacement;
11214
11315
  return cli.ports ?? (() => ({}));
11215
11316
  }
11216
- function portsBaseOf(scope, signal) {
11217
- const { cli, options, packageRoot, progress, fileError, rerun, clock, files } = scope;
11218
- const { cwd, env } = options;
11317
+ function runScopeOf(cli, routed, settings, write, baseInvocation) {
11318
+ const { cwd, env } = settings;
11319
+ const clock = settings.clock ?? realClock;
11320
+ const platform = settings.platform ?? process.platform;
11321
+ const invocation = {
11322
+ ...baseInvocation,
11323
+ flags: knownFlags(routed.spec),
11324
+ stdinArg: stdinArgOf(routed)
11325
+ };
11326
+ const rerun = (change) => ({
11327
+ kind: "rerun",
11328
+ words: changedArgv(invocation, change),
11329
+ stdinArg: invocation.stdinArg
11330
+ });
11331
+ const fileError = fileErrorOf({
11332
+ rerun: rerun(),
11333
+ env,
11334
+ platform
11335
+ });
11336
+ const outputs = outputsOf(settings.files ?? realFiles(), {
11337
+ cwd,
11338
+ fileError
11339
+ });
11340
+ const packageRoot = packageRootOf(settings.bin, cwd);
11341
+ const progress = (message) => write.stderr(progressLine(message, routed.json));
11219
11342
  const envFile = envFileOf(cli.name, env);
11343
+ const spawning = {
11344
+ fileError,
11345
+ rerun: rerun()
11346
+ };
11220
11347
  return {
11221
- cwd,
11222
- env,
11223
- packageRoot,
11224
- progress,
11225
- secret: (name) => secretOf({
11226
- envFile,
11227
- env,
11228
- fileError
11229
- }, name),
11230
- envFile,
11348
+ invocation,
11349
+ rerun,
11231
11350
  fileError,
11232
- version: cli.version,
11233
- build: buildStamp(),
11234
- self: selfOf(cli.name, options.bin),
11235
- signal,
11236
11351
  clock,
11237
- files,
11238
- spawn: contextSpawn(options.spawn ?? realSpawn, {
11239
- cwd,
11240
- env,
11241
- signal
11242
- }, {
11243
- fileError,
11244
- rerun
11245
- }),
11246
- spawnDetached: contextSpawnDetached(spawnDetachedOf(options), {
11352
+ platform,
11353
+ outputs,
11354
+ progress,
11355
+ notice: (text) => write.stderr(noticeLine(text, routed.json)),
11356
+ packageRoot,
11357
+ ports: portsOf(cli, settings.ports),
11358
+ portsBase: (signal) => ({
11247
11359
  cwd,
11248
11360
  env,
11249
- signal
11250
- }, {
11361
+ platform,
11362
+ packageRoot,
11363
+ progress,
11364
+ secret: (name) => secretOf({
11365
+ envFile,
11366
+ env,
11367
+ fileError
11368
+ }, name),
11369
+ envFile,
11251
11370
  fileError,
11252
- rerun
11371
+ version: cli.version,
11372
+ build: buildStamp(),
11373
+ self: selfOf(cli.name, settings.bin),
11374
+ signal,
11375
+ clock,
11376
+ files: outputs.files,
11377
+ spawn: contextSpawn(settings.spawn ?? realSpawn, {
11378
+ cwd,
11379
+ env,
11380
+ signal
11381
+ }, spawning),
11382
+ spawnDetached: contextSpawnDetached(spawnDetachedOf(settings), {
11383
+ cwd,
11384
+ env,
11385
+ signal
11386
+ }, spawning)
11253
11387
  })
11254
11388
  };
11255
11389
  }
11256
- function contextOf(portsBase, control, parts) {
11257
- const { ports, project, force, dryRun, secrets = [], rerun, deliver, notice } = parts;
11258
- const declared = new Map(secrets.map((declaration) => [declaration.name, declaration]));
11390
+ function contextOf(scope, control, { command, flags, project, deliver }) {
11391
+ const portsBase = scope.portsBase(control.signal);
11392
+ const ports = once(() => scope.ports(portsBase));
11393
+ const declared = new Map((command.secrets ?? []).map((declaration) => [declaration.name, declaration]));
11259
11394
  function secret(name) {
11260
11395
  const declaration = declared.get(name);
11261
11396
  if (declaration === void 0) throw new Error(`ctx.secret("${name}") names a Secret the Command does not declare`);
@@ -11273,47 +11408,57 @@ function contextOf(portsBase, control, parts) {
11273
11408
  return ports();
11274
11409
  },
11275
11410
  project,
11276
- force,
11277
- dryRun,
11278
- rerun,
11411
+ force: flags.force,
11412
+ dryRun: flags.dryRun,
11413
+ rerun: scope.rerun,
11279
11414
  poll: (check, { intervalMs, signal: stop }) => poll(portsBase.clock, check, intervalMs, stop === void 0 ? control.signal : AbortSignal.any([control.signal, stop])),
11280
11415
  deliver,
11281
- notice
11416
+ notice: scope.notice
11417
+ };
11418
+ }
11419
+ /** `build` runs on the first call; later calls give its value or throw its error again. */
11420
+ function once(build) {
11421
+ let outcome;
11422
+ return () => {
11423
+ if (outcome === void 0) try {
11424
+ outcome = {
11425
+ ok: true,
11426
+ value: build()
11427
+ };
11428
+ } catch (error) {
11429
+ outcome = {
11430
+ ok: false,
11431
+ error
11432
+ };
11433
+ }
11434
+ if (outcome.ok) return outcome.value;
11435
+ throw outcome.error;
11436
+ };
11437
+ }
11438
+ //#endregion
11439
+ //#region ../agent-cli/dist/run-command.js
11440
+ function stdinNeverEndedHint(callerLine) {
11441
+ return `stdin never ended: pipe the text into \`${callerLine}\`, or redirect a file into it`;
11442
+ }
11443
+ function runDeadlineAdvice(resumed, pipedLine) {
11444
+ return resumed === void 0 ? { hint: stdinNeverEndedHint(pipedLine) } : {
11445
+ hint: "run the Next step to continue the work",
11446
+ next: [resumed]
11282
11447
  };
11283
11448
  }
11284
- /** Leaves the Side task running, so the caller writes the result before it waits for it. */
11449
+ /**
11450
+ * Leaves the Side task running, so the caller writes the result before it waits for it.
11451
+ *
11452
+ * A gate that needs no `ctx` (operator, parse, Project file) runs here; one that does (tools,
11453
+ * Secrets, the Plan's) runs in `execute`, after the Notices, so a failing run still delivers them.
11454
+ */
11285
11455
  async function runCommand(cli, routed, options, write, baseFormat) {
11286
11456
  const { command, commandLine, builtIn } = routed.spec;
11287
11457
  const { cwd, env } = options;
11288
- const clock = options.clock ?? realClock;
11289
- const files = options.files ?? realFiles();
11290
11458
  const stdin = options.stdin ?? "";
11291
- const platform = options.platform ?? process.platform;
11292
- const ports = portsOf(cli, options.ports);
11293
- const packageRoot = packageRootOf(options.bin, cwd);
11294
- const progress = (message) => write.stderr(progressLine(message, routed.json));
11295
- const invocation = {
11296
- ...baseFormat.invocation,
11297
- flags: knownFlags(routed.spec),
11298
- stdinArg: stdinArgOf(routed)
11299
- };
11300
- const rerunOf = (change) => ({
11301
- kind: "rerun",
11302
- words: changedArgv(invocation, change),
11303
- stdinArg: invocation.stdinArg
11304
- });
11459
+ const scope = runScopeOf(cli, routed, options, write, baseFormat.invocation);
11460
+ const { invocation, rerun: rerunOf, fileError, clock, outputs, progress, platform } = scope;
11305
11461
  const rerun = rerunOf();
11306
- const fileError = fileErrorOf({
11307
- rerun,
11308
- env,
11309
- platform
11310
- });
11311
- const format = {
11312
- ...baseFormat,
11313
- bugs: () => manifestAt(packageRoot).bugs,
11314
- json: routed.json,
11315
- invocation
11316
- };
11317
11462
  const pipedLine = commandLineOf({
11318
11463
  ...invocation,
11319
11464
  stdinArg: void 0
@@ -11330,10 +11475,18 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11330
11475
  advice: () => runDeadlineAdvice(resumed, pipedLine)
11331
11476
  }
11332
11477
  });
11333
- const failed = (error, finishSideTask) => {
11478
+ const format = {
11479
+ ...baseFormat,
11480
+ command: routed.spec,
11481
+ control,
11482
+ bugs: () => manifestAt(scope.packageRoot).bugs,
11483
+ json: routed.json,
11484
+ invocation
11485
+ };
11486
+ const failed = async (error, finishSideTask) => {
11334
11487
  control.close();
11335
11488
  return {
11336
- result: errorResult(error, format),
11489
+ result: await errorResult(error, format),
11337
11490
  finishSideTask
11338
11491
  };
11339
11492
  };
@@ -11352,24 +11505,9 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11352
11505
  }
11353
11506
  const { input, flags } = parsed;
11354
11507
  resumed = command.deadline?.resume(input, rerunOf);
11355
- const outputs = outputsGuard(classifiedFiles(files, {
11356
- cwd,
11357
- fileError
11358
- }), cwd);
11359
- const scope = {
11360
- cli,
11361
- options,
11362
- packageRoot,
11363
- progress,
11364
- fileError,
11365
- rerun,
11366
- clock,
11367
- files: outputs.files
11368
- };
11369
- const portsBase = portsBaseOf(scope, control.signal);
11370
11508
  const finishSideTask = startSideTask(builtIn ? void 0 : cli.sideTask?.bind(cli), (sideSignal) => {
11371
- const sideBase = portsBaseOf(scope, sideSignal);
11372
- const sidePorts = once(() => ports(sideBase));
11509
+ const sideBase = scope.portsBase(sideSignal);
11510
+ const sidePorts = once(() => scope.ports(sideBase));
11373
11511
  return {
11374
11512
  ...sideBase,
11375
11513
  command: routed.spec.path,
@@ -11394,14 +11532,10 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11394
11532
  return failed(error, finishSideTask);
11395
11533
  }
11396
11534
  let delivered;
11397
- const ctx = contextOf(portsBase, control, {
11398
- ports: once(() => ports(portsBase)),
11535
+ const ctx = contextOf(scope, control, {
11536
+ command,
11537
+ flags,
11399
11538
  project,
11400
- force: flags.force,
11401
- dryRun: flags.dryRun,
11402
- secrets: command.secrets,
11403
- rerun: rerunOf,
11404
- notice: (text) => write.stderr(noticeLine(text, routed.json)),
11405
11539
  deliver: (data) => {
11406
11540
  delivered = () => dataOutcome(command, data, commandLine);
11407
11541
  }
@@ -11420,19 +11554,16 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11420
11554
  const outcome = await control.settle(noticesOf(cli, {
11421
11555
  builtIn,
11422
11556
  bin: options.bin,
11423
- node: options.node
11557
+ node: options.node,
11558
+ platform
11424
11559
  }, ctx).then(() => execute(command, input, ctx, {
11425
- dryRun: flags.dryRun,
11426
- confirm: flags.confirm,
11427
- yes: flags.yes,
11428
- page: flags.page,
11560
+ flags,
11429
11561
  commandLine,
11430
11562
  started: async (outcome) => {
11431
11563
  write.stdout(await startedOutput(outcome, format, spill));
11432
11564
  served = true;
11433
11565
  },
11434
- files,
11435
- guardOutputs: outputs.guard
11566
+ outputs
11436
11567
  })));
11437
11568
  if (served) return {
11438
11569
  result: STOPPED_RESULT,
@@ -11449,7 +11580,7 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11449
11580
  };
11450
11581
  settled = {
11451
11582
  ok: false,
11452
- error: checkedCodes(control.errorAfter(error), routed.spec, cli),
11583
+ error,
11453
11584
  delivered
11454
11585
  };
11455
11586
  }
@@ -11461,25 +11592,6 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11461
11592
  finishSideTask
11462
11593
  };
11463
11594
  }
11464
- /** `build` runs on the first call; later calls give its value or throw its error again. */
11465
- function once(build) {
11466
- let outcome;
11467
- return () => {
11468
- if (outcome === void 0) try {
11469
- outcome = {
11470
- ok: true,
11471
- value: build()
11472
- };
11473
- } catch (error) {
11474
- outcome = {
11475
- ok: false,
11476
- error
11477
- };
11478
- }
11479
- if (outcome.ok) return outcome.value;
11480
- throw outcome.error;
11481
- };
11482
- }
11483
11595
  async function noticesOf(cli, launch, ctx) {
11484
11596
  await installOf(cli).notice(launch, ctx);
11485
11597
  }
@@ -11538,7 +11650,8 @@ async function routeAndRun(cli, argv, options, write, stopNotice) {
11538
11650
  const runAs = runAsOf(cli);
11539
11651
  const root = rootSpec(cli, {
11540
11652
  bin: options.bin,
11541
- node: options.node
11653
+ node: options.node,
11654
+ platform: options.platform ?? process.platform
11542
11655
  });
11543
11656
  const format = {
11544
11657
  cliName: cli.name,
@@ -11552,7 +11665,7 @@ async function routeAndRun(cli, argv, options, write, stopNotice) {
11552
11665
  json: false,
11553
11666
  errors: errorTableOf(cli),
11554
11667
  stop: options.signal,
11555
- ...options.checkNextSteps === true ? { checkNextStep: (step) => nextStepProblem(root, runAs, step) } : {}
11668
+ ...options.checkNextSteps === true ? { nextStepCheck: nextStepCheck(root, runAs) } : {}
11556
11669
  };
11557
11670
  const routed = route(root, format.invocation);
11558
11671
  const misused = (node) => isAgent(options.env) ? {
@@ -11631,7 +11744,7 @@ async function runProcess(cli) {
11631
11744
  //#region package.json
11632
11745
  var package_default = {
11633
11746
  name: "@duyquangnvx/create-agent-cli",
11634
- version: "0.27.0",
11747
+ version: "0.28.0",
11635
11748
  description: "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
11636
11749
  type: "module",
11637
11750
  engines: { "node": ">=22.12.0" },