@duyquangnvx/create-agent-cli 0.27.0 → 0.29.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,36 +6996,785 @@ 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. */
7175
- function compareVersions(a, b) {
7176
- const left = VERSION.exec(a);
7177
- const right = VERSION.exec(b);
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 {
7704
+ list: withEffect("read"),
7705
+ read: withEffect("read"),
7706
+ act: withEffect("act"),
7707
+ serve: withEffect("serve"),
7708
+ write: withEffect("write"),
7709
+ destructive: withEffect("destructive")
7710
+ };
7711
+ }
7712
+ function withEffect(effect) {
7713
+ return (command) => ({
7714
+ ...command,
7715
+ effect
7716
+ });
7717
+ }
7718
+ const defineCommand$1 = defineCommandFor();
7719
+ //#endregion
7720
+ //#region ../agent-cli/dist/error-table.js
7721
+ /** The Error table a CLI declares, empty when it declares none. */
7722
+ function errorTableOf({ errors }) {
7723
+ return errors ?? {};
7724
+ }
7725
+ /** The `CliError` for a code of `Table`, as `cliError` and `ctx.fail` build it. */
7726
+ function cliErrorOf(code, init, options) {
7727
+ return new CliError({
7728
+ ...init,
7729
+ code
7730
+ }, options);
7731
+ }
7732
+ //#endregion
7733
+ //#region ../agent-cli/dist/version.js
7734
+ /** The bundler's stamp, absent when the CLI runs unbundled. Read per call, so a test can define it. */
7735
+ function buildStamp() {
7736
+ return {
7737
+ "sha": "0478338",
7738
+ "dirty": false,
7739
+ "builtAt": "2026-11-09T05:16:34.000Z"
7740
+ };
7741
+ }
7742
+ /** The first line of `--version`: the version, and the commit and time of a stamped build. */
7743
+ function versionLine({ version }) {
7744
+ const build = buildStamp();
7745
+ return build === void 0 ? version : `${version} (${build.sha}${build.dirty ? "+dirty" : ""}) built ${build.builtAt}`;
7746
+ }
7747
+ //#endregion
7748
+ //#region ../agent-cli/dist/built-ins/semver.js
7749
+ const VERSION = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
7750
+ const ENGINE = /^>=\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?$/;
7751
+ function isVersion(text) {
7752
+ return VERSION.test(text);
7753
+ }
7754
+ function compareIdentifiers(x, y) {
7755
+ const xNumeric = /^\d+$/.test(x);
7756
+ const yNumeric = /^\d+$/.test(y);
7757
+ if (xNumeric && yNumeric) return Number(x) - Number(y);
7758
+ if (xNumeric !== yNumeric) return xNumeric ? -1 : 1;
7759
+ if (x === y) return 0;
7760
+ return x < y ? -1 : 1;
7761
+ }
7762
+ function comparePrerelease(a, b) {
7763
+ if (a === b) return 0;
7764
+ if (a === void 0) return 1;
7765
+ if (b === void 0) return -1;
7766
+ const left = a.split(".");
7767
+ const right = b.split(".");
7768
+ for (let i = 0; i < Math.min(left.length, right.length); i += 1) {
7769
+ const difference = compareIdentifiers(left[i] ?? "", right[i] ?? "");
7770
+ if (difference !== 0) return difference;
7771
+ }
7772
+ return left.length - right.length;
7773
+ }
7774
+ /** Negative when `a` is older than `b`, positive when newer, 0 when the same release. */
7775
+ function compareVersions(a, b) {
7776
+ const left = VERSION.exec(a);
7777
+ const right = VERSION.exec(b);
7178
7778
  if (left === null || right === null) throw new Error(`cannot compare ${a} with ${b}: both must be X.Y.Z versions`);
7179
7779
  for (const part of [
7180
7780
  1,
@@ -7207,12 +7807,15 @@ function meetsMinimum(version, minimum) {
7207
7807
  }
7208
7808
  //#endregion
7209
7809
  //#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) {
7810
+ /** What a Command running npm may fail with, for it to declare in `errors`. */
7811
+ const NPM_ERRORS = [
7812
+ REGISTRY_UNREACHABLE,
7813
+ REGISTRY_AUTH,
7814
+ INSTALL_FAILED,
7815
+ NPM_FAILED,
7816
+ NO_LATEST
7817
+ ];
7818
+ function npmContext(cli, registry, ctx, { node, platform }, cwd) {
7216
7819
  return {
7217
7820
  cliName: cli.name,
7218
7821
  rerun: ctx.rerun(),
@@ -7220,7 +7823,9 @@ function npmContext(cli, registry, ctx, node, cwd) {
7220
7823
  cwd,
7221
7824
  env: ctx.env,
7222
7825
  signal: ctx.signal,
7223
- node
7826
+ node,
7827
+ platform,
7828
+ files: ctx.files
7224
7829
  };
7225
7830
  }
7226
7831
  const STDERR_TAIL_LINES = 10;
@@ -7262,9 +7867,10 @@ function setupCommandLine({ package: name, flags }) {
7262
7867
  "setup"
7263
7868
  ].join(" ");
7264
7869
  }
7265
- function npmOf(ctx, windows) {
7870
+ async function npmOf(ctx) {
7871
+ const windows = ctx.platform === "win32";
7266
7872
  const beside = ctx.node === void 0 ? void 0 : join(dirname(ctx.node), windows ? "npm.cmd" : "npm");
7267
- if (beside === void 0 || !existsSync(beside)) return {
7873
+ if (beside === void 0 || !await ctx.files.exists(beside)) return {
7268
7874
  path: "npm",
7269
7875
  env: ctx.env
7270
7876
  };
@@ -7281,8 +7887,7 @@ function npmOf(ctx, windows) {
7281
7887
  }
7282
7888
  };
7283
7889
  }
7284
- function spawnNpm(args, ctx, signal) {
7285
- const npm = npmOf(ctx, process.platform === "win32");
7890
+ function spawnNpm(npm, args, ctx, signal) {
7286
7891
  return spawnChild(npm.path, args, {
7287
7892
  cwd: ctx.cwd,
7288
7893
  env: npm.env,
@@ -7300,7 +7905,7 @@ function authHint(code, ctx) {
7300
7905
  if (code !== "E403" || !isAgent(ctx.env)) return login;
7301
7906
  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
7907
  }
7303
- function failureOf(call, answer, ctx) {
7908
+ function failureOf$1(call, answer, ctx) {
7304
7909
  const { label: registry, package: name, unreachableHint } = ctx.registry;
7305
7910
  const code = NPM_CODE.exec(answer.stderr)?.[1];
7306
7911
  const output = tail$1(answer.stderr);
@@ -7345,7 +7950,7 @@ function noLatest(ctx, message) {
7345
7950
  async function runNpm(call, args, ctx, signal = ctx.signal) {
7346
7951
  const cache = await mkdtemp(join(tmpdir(), `${ctx.cliName}-npm-`));
7347
7952
  try {
7348
- const answer = await spawnNpm([
7953
+ const answer = await spawnNpm(await npmOf(ctx), [
7349
7954
  ...args,
7350
7955
  `--cache=${join(cache, "cache")}`,
7351
7956
  "--logs-max=0"
@@ -7357,7 +7962,7 @@ async function runNpm(call, args, ctx, signal = ctx.signal) {
7357
7962
  hint: "install Node with npm, or put npm on PATH"
7358
7963
  }, { cause: error });
7359
7964
  });
7360
- if (answer.exitCode !== 0) throw failureOf(call, answer, ctx);
7965
+ if (answer.exitCode !== 0) throw failureOf$1(call, answer, ctx);
7361
7966
  return answer.stdout;
7362
7967
  } finally {
7363
7968
  await rm(cache, {
@@ -7407,6 +8012,30 @@ async function latestVersion(ctx) {
7407
8012
  return version;
7408
8013
  }
7409
8014
  //#endregion
8015
+ //#region ../agent-cli/dist/built-ins/read.js
8016
+ function isNotFound(error) {
8017
+ return error instanceof CliError && error.code === "NOT_FOUND";
8018
+ }
8019
+ const decoder = new TextDecoder();
8020
+ /** The text of the file at `path`, or `undefined` when nothing is there. */
8021
+ async function readIfAny(files, path) {
8022
+ try {
8023
+ return decoder.decode(await files.read(path));
8024
+ } catch (error) {
8025
+ if (isNotFound(error)) return void 0;
8026
+ throw error;
8027
+ }
8028
+ }
8029
+ /** The text of a file the CLI's own package ships, whose absence is the release's bug. */
8030
+ async function readRequired(files, path) {
8031
+ try {
8032
+ return decoder.decode(await files.read(path));
8033
+ } catch (error) {
8034
+ if (!isNotFound(error)) throw error;
8035
+ throw new Error(`the package is missing its own file ${path}`, { cause: error });
8036
+ }
8037
+ }
8038
+ //#endregion
7410
8039
  //#region ../agent-cli/dist/built-ins/project-root.js
7411
8040
  const MANAGERS = [
7412
8041
  "pnpm",
@@ -7431,22 +8060,14 @@ function ancestorsOf(dir) {
7431
8060
  const parent = dirname(dir);
7432
8061
  return parent === dir ? [dir] : [dir, ...ancestorsOf(parent)];
7433
8062
  }
7434
- async function exists(path) {
7435
- return await orMissing(stat(path)) !== void 0;
7436
- }
7437
8063
  /** Whether `path` lies strictly inside `dir`. */
7438
8064
  function isInside(dir, path) {
7439
8065
  const inner = relative(dir, path);
7440
8066
  return inner !== "" && !inner.startsWith("..") && !isAbsolute(inner);
7441
8067
  }
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
- }
8068
+ async function manifestIn(files, dir) {
8069
+ const text = await readIfAny(files, join(dir, "package.json"));
8070
+ if (text === void 0) return void 0;
7450
8071
  let manifest;
7451
8072
  try {
7452
8073
  manifest = JSON.parse(text);
@@ -7455,19 +8076,19 @@ async function manifestIn(dir) {
7455
8076
  }
7456
8077
  return manifestSchema$1.safeParse(manifest).data;
7457
8078
  }
7458
- async function declaredManager(dir) {
7459
- const name = (await manifestIn(dir))?.packageManager?.split("@")[0];
8079
+ async function declaredManager(files, dir) {
8080
+ const name = (await manifestIn(files, dir))?.packageManager?.split("@")[0];
7460
8081
  return MANAGERS.find((manager) => manager === name);
7461
8082
  }
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 {
8083
+ async function lockfileRootOf(files, cwd) {
8084
+ for (const dir of ancestorsOf(cwd)) for (const [file, kind] of LOCKFILES) if (await files.exists(join(dir, file))) return {
7464
8085
  dir,
7465
- manager: await declaredManager(dir) ?? kind
8086
+ manager: await declaredManager(files, dir) ?? kind
7466
8087
  };
7467
8088
  }
7468
8089
  /** 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;
8090
+ async function projectRootOf(files, cwd) {
8091
+ return (await lockfileRootOf(files, cwd))?.dir ?? cwd;
7471
8092
  }
7472
8093
  //#endregion
7473
8094
  //#region ../agent-cli/dist/built-ins/install/run-as.js
@@ -7490,29 +8111,30 @@ const LINES = {
7490
8111
  }
7491
8112
  };
7492
8113
  const packageSchema = object({ version: string() });
7493
- async function manifestListing(name, cwd, root) {
8114
+ async function manifestListing(files, name, cwd, root) {
7494
8115
  const dirs = ancestorsOf(cwd);
7495
8116
  const top = root === void 0 ? dirs.length : dirs.indexOf(root.dir) + 1;
7496
8117
  for (const dir of dirs.slice(0, top)) {
7497
- const manifest = await manifestIn(dir);
8118
+ const manifest = await manifestIn(files, dir);
7498
8119
  if (manifest?.dependencies?.[name] !== void 0 || manifest?.devDependencies?.[name] !== void 0) return join(dir, "package.json");
7499
8120
  }
7500
8121
  }
7501
- async function versionIn(manifest) {
8122
+ function versionIn(text) {
7502
8123
  try {
7503
- return packageSchema.parse(JSON.parse(await readFile(manifest, "utf8"))).version;
8124
+ return packageSchema.parse(JSON.parse(text)).version;
7504
8125
  } catch (error) {
7505
8126
  if (error instanceof SyntaxError || error instanceof ZodError) return { reason: messageOf(error) };
7506
8127
  throw error;
7507
8128
  }
7508
8129
  }
7509
- async function standingOf(install, bin, cwd, root) {
8130
+ async function standingOf(files, install, bin, cwd, root) {
7510
8131
  if (bin === void 0) return { kind: "source" };
7511
8132
  for (const projectDir of ancestorsOf(cwd)) {
7512
8133
  const folder = join(projectDir, "node_modules", install.package);
7513
8134
  const manifest = join(folder, "package.json");
7514
- if (!await exists(manifest)) continue;
7515
- const version = await versionIn(manifest);
8135
+ const text = await readIfAny(files, manifest);
8136
+ if (text === void 0) continue;
8137
+ const version = versionIn(text);
7516
8138
  if (typeof version !== "string") return {
7517
8139
  kind: "unreadable",
7518
8140
  manifest,
@@ -7520,14 +8142,14 @@ async function standingOf(install, bin, cwd, root) {
7520
8142
  };
7521
8143
  const copy = {
7522
8144
  projectDir,
7523
- realDir: await realpath(folder),
8145
+ realDir: await files.realPath(folder) ?? folder,
7524
8146
  version
7525
8147
  };
7526
8148
  if (!copy.realDir.split(sep).includes("node_modules")) return {
7527
8149
  kind: "linked",
7528
8150
  copy
7529
8151
  };
7530
- const runningFrom = await orMissing(realpath(bin)) ?? bin;
8152
+ const runningFrom = await files.realPath(bin) ?? bin;
7531
8153
  return isInside(copy.realDir, runningFrom) ? {
7532
8154
  kind: "installed",
7533
8155
  copy
@@ -7537,18 +8159,18 @@ async function standingOf(install, bin, cwd, root) {
7537
8159
  runningFrom
7538
8160
  };
7539
8161
  }
7540
- const manifest = await manifestListing(install.package, cwd, root);
8162
+ const manifest = await manifestListing(files, install.package, cwd, root);
7541
8163
  return manifest === void 0 ? { kind: "missing" } : {
7542
8164
  kind: "listed",
7543
8165
  manifest
7544
8166
  };
7545
8167
  }
7546
- async function surveyDependency(install, bin, cwd) {
7547
- const root = await lockfileRootOf(cwd);
8168
+ async function surveyDependency(install, bin, { files, cwd }) {
8169
+ const root = await lockfileRootOf(files, cwd);
7548
8170
  return {
7549
8171
  cwd,
7550
8172
  root,
7551
- standing: await standingOf(install, bin, cwd, root)
8173
+ standing: await standingOf(files, install, bin, cwd, root)
7552
8174
  };
7553
8175
  }
7554
8176
  function packagesOf(install) {
@@ -7618,19 +8240,19 @@ function adviceFor(install, root, latest) {
7618
8240
  if (lines !== void 0) return { fix: lines.upgrade(packages) };
7619
8241
  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
8242
  }
7621
- function checkOf$1(cli, install, runAs, { bin, node }) {
8243
+ function checkOf$1(cli, install, runAs, launch) {
7622
8244
  let survey;
7623
- const surveyed = (cwd) => {
7624
- survey ??= surveyDependency(install, bin, cwd);
8245
+ const surveyed = (ctx) => {
8246
+ survey ??= surveyDependency(install, launch.bin, ctx);
7625
8247
  return survey;
7626
8248
  };
7627
8249
  return {
7628
- install: async (ctx) => installFinding(cli, runAs, install, await surveyed(ctx.cwd)),
8250
+ install: async (ctx) => installFinding(cli, runAs, install, await surveyed(ctx)),
7629
8251
  release: async (ctx) => {
7630
- const { cwd, root, standing } = await surveyed(ctx.cwd);
8252
+ const { cwd, root, standing } = await surveyed(ctx);
7631
8253
  if (isUnchecked(standing)) return { skipped: skippedFor$1(cli, install, standing) };
7632
8254
  return {
7633
- npm: npmContext(cli, registryOf(install, root?.dir), ctx, node, root?.dir ?? cwd),
8255
+ npm: npmContext(cli, registryOf(install, root?.dir), ctx, launch, root?.dir ?? cwd),
7634
8256
  advise: (latest) => adviceFor(install, root, latest)
7635
8257
  };
7636
8258
  }
@@ -7642,222 +8264,22 @@ function dependencyInstall(cli, install) {
7642
8264
  return {
7643
8265
  runAs,
7644
8266
  builtIns: () => ({}),
7645
- exitClasses: [],
7646
8267
  check: (launch) => checkOf$1(cli, install, runAs, launch),
7647
8268
  notice: async () => {}
7648
8269
  };
7649
8270
  }
7650
8271
  //#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
8272
  //#region ../agent-cli/dist/built-ins/install/global-npm.js
7857
- const NOT_GLOBAL_INSTALL = "NOT_GLOBAL_INSTALL";
7858
8273
  /** 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");
8274
+ function globalModulesDir(prefix, platform) {
8275
+ return platform === "win32" ? join(prefix, "node_modules") : join(prefix, "lib", "node_modules");
8276
+ }
8277
+ /** Whether `path` itself is a symlink, which a link in a folder above it does not make it. */
8278
+ async function isLink(files, path) {
8279
+ const real = await files.realPath(path);
8280
+ if (real === void 0) return false;
8281
+ const folder = await files.realPath(dirname(path));
8282
+ return folder === void 0 || real !== join(folder, basename(path));
7861
8283
  }
7862
8284
  function notGlobal(ctx, message) {
7863
8285
  return runtimeError({
@@ -7874,11 +8296,11 @@ function notGlobal(ctx, message) {
7874
8296
  async function globalInstallOf(bin, ctx) {
7875
8297
  if (bin === void 0) throw notGlobal(ctx, `${ctx.cliName} runs from source, not from an npm install`);
7876
8298
  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;
8299
+ const packageDir = join(globalModulesDir(prefix, ctx.platform), ctx.registry.package);
8300
+ const installed = await ctx.files.realPath(packageDir);
8301
+ const here = await ctx.files.realPath(bin) ?? bin;
7880
8302
  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`);
8303
+ 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
8304
  return {
7883
8305
  prefix,
7884
8306
  packageDir
@@ -7907,25 +8329,32 @@ function globalRegistry(install) {
7907
8329
  };
7908
8330
  }
7909
8331
  /** 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);
8332
+ function globalNpm(cli, install, ctx, launch) {
8333
+ return npmContext(cli, globalRegistry(install), ctx, launch, ctx.cwd);
7912
8334
  }
7913
8335
  //#endregion
7914
8336
  //#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))));
8337
+ async function walk$1(files, dir, inner) {
8338
+ const names = await files.list(join(dir, inner));
8339
+ return (await Promise.all(names.map(async (name) => {
8340
+ const path = join(inner, name);
8341
+ const found = await files.stat(join(dir, path));
8342
+ if (found === void 0) return [];
8343
+ return found.folder ? walk$1(files, dir, path) : [path];
8344
+ }))).flat();
8345
+ }
8346
+ /** Every file under `dir`, relative to it and sorted; none when `dir` is no folder. */
8347
+ async function filesIn(files, dir) {
8348
+ if ((await files.stat(dir))?.folder !== true) return [];
8349
+ return (await walk$1(files, dir, "")).sort();
8350
+ }
8351
+ async function sameContent(files, left, right) {
8352
+ const [a, b] = await Promise.all([files.read(left), files.read(right)]);
8353
+ return Buffer.from(a).equals(b);
8354
+ }
8355
+ async function syncOf(files, source, copy) {
8356
+ const [shipped, present] = await Promise.all([filesIn(files, source), filesIn(files, copy)]);
8357
+ const differs = await Promise.all(shipped.map(async (file) => present.includes(file) && !await sameContent(files, join(source, file), join(copy, file))));
7929
8358
  return {
7930
8359
  source,
7931
8360
  copy,
@@ -7935,7 +8364,7 @@ async function syncOf(source, copy) {
7935
8364
  };
7936
8365
  }
7937
8366
  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)));
8367
+ 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
8368
  for (const file of sync.remove) await ctx.files.remove(join(sync.copy, file));
7940
8369
  }
7941
8370
  //#endregion
@@ -7956,9 +8385,9 @@ function homeOf(env) {
7956
8385
  return env.HOME ?? env.USERPROFILE ?? homedir();
7957
8386
  }
7958
8387
  /** 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) {
8388
+ async function homeCopiesOf(files, name, env) {
7960
8389
  const paths = SKILL_DIRS.map((dir) => join(homeOf(env), ...dir, name));
7961
- const reals = await Promise.all(paths.map(async (path) => orMissing(realpath(path))));
8390
+ const reals = await Promise.all(paths.map((path) => files.realPath(path)));
7962
8391
  return paths.filter((_path, index) => {
7963
8392
  const real = reals[index];
7964
8393
  return real === void 0 || reals.indexOf(real) === index;
@@ -7976,9 +8405,9 @@ const homeSyncSchema = object({
7976
8405
  async function syncHome(cli, packageDir, ctx) {
7977
8406
  if (cli.skill === void 0) return null;
7978
8407
  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)));
8408
+ if ((await filesIn(ctx.files, source)).length === 0) return null;
8409
+ const copies = await homeCopiesOf(ctx.files, cli.name, ctx.env);
8410
+ const syncs = await Promise.all(copies.map((copy) => syncOf(ctx.files, source, copy)));
7982
8411
  for (const sync of syncs) await applySync(sync, ctx);
7983
8412
  return {
7984
8413
  copies,
@@ -7995,19 +8424,25 @@ function renderHomeSync(name, sync) {
7995
8424
  const at = sync.copies.join(", ");
7996
8425
  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
8426
  }
7998
- async function homeCopyAt(path) {
8427
+ async function homeCopyAt(files, path) {
8428
+ let text;
7999
8429
  try {
8000
- return {
8001
- path,
8002
- state: "readable",
8003
- stated: skillVersionIn(await readFile(join(path, "SKILL.md"), "utf8"))
8004
- };
8430
+ text = await readIfAny(files, join(path, "SKILL.md"));
8005
8431
  } catch (error) {
8432
+ if (!(error instanceof CliError)) throw error;
8006
8433
  return {
8007
8434
  path,
8008
- state: isMissingFile(error) ? "missing" : "unreadable"
8435
+ state: "unreadable"
8009
8436
  };
8010
8437
  }
8438
+ return text === void 0 ? {
8439
+ path,
8440
+ state: "missing"
8441
+ } : {
8442
+ path,
8443
+ state: "readable",
8444
+ stated: skillVersionIn(text)
8445
+ };
8011
8446
  }
8012
8447
  function describeCopy(copy) {
8013
8448
  if (copy.state === "missing") return `no copy at ${copy.path}`;
@@ -8015,8 +8450,8 @@ function describeCopy(copy) {
8015
8450
  return copy.stated === void 0 ? copy.path : `${copy.path} (${statedVersionNote(copy.stated)})`;
8016
8451
  }
8017
8452
  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"))]);
8453
+ const paths = await homeCopiesOf(ctx.files, cli.name, ctx.env);
8454
+ 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
8455
  const message = copies.map(describeCopy).join("; ");
8021
8456
  const shipsStated = shipped !== void 0 && skillVersionIn(shipped) !== void 0;
8022
8457
  return copies.every((copy) => copy.state === "readable" && (copy.stated === void 0 || shipsStated)) ? {
@@ -8094,14 +8529,9 @@ function upgradeOf({ version, lines }) {
8094
8529
  * What each release after `from` up to `to` asks of the operator, oldest first, from the
8095
8530
  * CHANGELOG.md in `packageDir`; none when the package ships no CHANGELOG.
8096
8531
  */
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
- }
8532
+ async function upgradesOf(files, packageDir, from, to) {
8533
+ const changelog = await readIfAny(files, join(packageDir, "CHANGELOG.md"));
8534
+ if (changelog === void 0) return [];
8105
8535
  return releasesIn(changelog).filter(({ version }) => compareVersions(version, from) > 0 && compareVersions(version, to) <= 0).sort((a, b) => compareVersions(a.version, b.version)).flatMap(upgradeOf);
8106
8536
  }
8107
8537
  function renderUpgrade(upgrade) {
@@ -8109,7 +8539,6 @@ function renderUpgrade(upgrade) {
8109
8539
  }
8110
8540
  //#endregion
8111
8541
  //#region ../agent-cli/dist/built-ins/setup.js
8112
- const NOT_ON_PATH = "NOT_ON_PATH";
8113
8542
  const setupSchema = object({
8114
8543
  version: string(),
8115
8544
  prefix: string(),
@@ -8119,8 +8548,8 @@ const setupSchema = object({
8119
8548
  docs: string().nullable()
8120
8549
  });
8121
8550
  const installedManifest = object({ version: string() });
8122
- async function versionAt(packageDir) {
8123
- const text = await orMissing(readFile(join(packageDir, "package.json"), "utf8"));
8551
+ async function versionAt(files, packageDir) {
8552
+ const text = await readIfAny(files, join(packageDir, "package.json"));
8124
8553
  if (text === void 0) return null;
8125
8554
  try {
8126
8555
  const parsed = installedManifest.safeParse(JSON.parse(text));
@@ -8130,26 +8559,29 @@ async function versionAt(packageDir) {
8130
8559
  throw error;
8131
8560
  }
8132
8561
  }
8133
- function npmOfSetup(cli, install, ctx, node) {
8562
+ function npmOfSetup(cli, install, ctx, launch) {
8134
8563
  return {
8135
- ...globalNpm(cli, install, ctx, node),
8564
+ ...globalNpm(cli, install, ctx, launch),
8136
8565
  rerun: wholeLine(setupLineOf(install))
8137
8566
  };
8138
8567
  }
8139
8568
  function needsInstall({ from, latest }) {
8140
8569
  return from === null || standingOf$1(from, latest) === "behind";
8141
8570
  }
8142
- function binDirOf(prefix) {
8143
- return process.platform === "win32" ? prefix : join(prefix, "bin");
8571
+ function binDirOf(prefix, platform) {
8572
+ return platform === "win32" ? prefix : join(prefix, "bin");
8144
8573
  }
8145
- async function canonical(path) {
8146
- return normalize(await realpath(path).catch(() => path));
8574
+ async function canonical(files, path) {
8575
+ const real = await files.realPath(path).catch((error) => {
8576
+ if (!(error instanceof CliError) && errnoOf(error) === void 0) throw error;
8577
+ });
8578
+ return normalize(real ?? path);
8147
8579
  }
8148
- async function assertOnPath(cli, prefix, node, ctx) {
8149
- const binDir = binDirOf(prefix);
8150
- const wanted = await canonical(binDir);
8580
+ async function assertOnPath(cli, prefix, { node, platform }, ctx) {
8581
+ const binDir = binDirOf(prefix, platform);
8582
+ const wanted = await canonical(ctx.files, binDir);
8151
8583
  const entries = pathOf(ctx.env).split(delimiter).filter(Boolean);
8152
- if ((await Promise.all(entries.map(canonical))).includes(wanted)) return;
8584
+ if ((await Promise.all(entries.map((entry) => canonical(ctx.files, entry)))).includes(wanted)) return;
8153
8585
  throw runtimeError({
8154
8586
  code: NOT_ON_PATH,
8155
8587
  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,8 +8613,8 @@ function renderSetup(cli, install, setup) {
8181
8613
  ].join("\n");
8182
8614
  }
8183
8615
  /** 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 }) {
8185
- return defineCommand$1({
8616
+ function setupCommand(cli, install, launch) {
8617
+ return defineCommand$1.write({
8186
8618
  description: "install this CLI and its skill on this machine",
8187
8619
  examples: [{
8188
8620
  command: `${cli.name} setup`,
@@ -8191,13 +8623,17 @@ function setupCommand(cli, install, { node }) {
8191
8623
  command: `${cli.name} setup --dry-run`,
8192
8624
  note: "show what it would change"
8193
8625
  }],
8194
- effect: "write",
8626
+ errors: [
8627
+ NOT_GLOBAL_INSTALL,
8628
+ NOT_ON_PATH,
8629
+ ...NPM_ERRORS
8630
+ ],
8195
8631
  output: setupSchema,
8196
8632
  plan: async (_input, ctx) => {
8197
- const npm = npmOfSetup(cli, install, ctx, node);
8633
+ const npm = npmOfSetup(cli, install, ctx, launch);
8198
8634
  const prefix = await globalPrefix(npm);
8199
- const packageDir = join(globalModulesDir(prefix), install.package);
8200
- if ((await orMissing(lstat(packageDir)))?.isSymbolicLink() === true) throw runtimeError({
8635
+ const packageDir = join(globalModulesDir(prefix, launch.platform), install.package);
8636
+ if (await isLink(ctx.files, packageDir)) throw runtimeError({
8201
8637
  code: NOT_GLOBAL_INSTALL,
8202
8638
  message: `${packageDir} is a link (npm link), which an install would replace`,
8203
8639
  hint: "remove the link with npm unlink, then run the Next step",
@@ -8205,309 +8641,136 @@ function setupCommand(cli, install, { node }) {
8205
8641
  });
8206
8642
  return {
8207
8643
  package: install.package,
8208
- from: await versionAt(packageDir),
8644
+ from: await versionAt(ctx.files, packageDir),
8209
8645
  latest: await latestVersion(npm),
8210
8646
  prefix,
8211
8647
  packageDir,
8212
- copies: cli.skill === void 0 ? [] : await homeCopiesOf(cli.name, ctx.env)
8213
- };
8214
- },
8215
- apply: async (plan, ctx) => {
8216
- const { from, latest, prefix, packageDir } = plan;
8217
- const installs = needsInstall(plan);
8218
- 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)
8648
+ copies: cli.skill === void 0 ? [] : await homeCopiesOf(ctx.files, cli.name, ctx.env)
8302
8649
  };
8303
8650
  },
8304
8651
  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
8652
+ const { from, latest, prefix, packageDir } = plan;
8653
+ const installs = needsInstall(plan);
8654
+ const skill = await ctx.runToEnd(`installing ${plan.package}@${latest}`, async () => {
8655
+ if (installs) {
8656
+ ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
8657
+ await installGlobally(latest, npmOfSetup(cli, install, ctx, launch));
8658
+ }
8659
+ return syncHome(cli, packageDir, ctx);
8660
+ }).catch((error) => {
8661
+ throw withSetupLine(error, install);
8469
8662
  });
8470
- await mkdir(path, { mode });
8663
+ const setup = {
8664
+ version: installs || from === null ? latest : from,
8665
+ prefix,
8666
+ installed: installs,
8667
+ skill,
8668
+ upgrades: installs && from !== null ? await upgradesOf(ctx.files, packageDir, from, latest) : [],
8669
+ docs: cli.docs === void 0 ? null : join(packageDir, cli.docs)
8670
+ };
8671
+ ctx.deliver(setup);
8672
+ if (ctx.signal.aborted) throw new CliError({
8673
+ code: INTERRUPTED,
8674
+ message: `interrupted, but ${plan.package}@${setup.version} and its skill are already in place`,
8675
+ hint: "nothing to run again"
8676
+ });
8677
+ await assertOnPath(cli, prefix, launch, ctx);
8678
+ return setup;
8471
8679
  },
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
- };
8680
+ render: (setup) => renderSetup(cli, install, setup),
8681
+ 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")
8682
+ });
8485
8683
  }
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
8684
+ //#endregion
8685
+ //#region ../agent-cli/dist/built-ins/update.js
8686
+ const updateSchema = object({
8687
+ from: string(),
8688
+ latest: string(),
8689
+ installed: object({
8690
+ version: string(),
8691
+ prefix: string()
8692
+ }).nullable(),
8693
+ skill: homeSyncSchema.nullable(),
8694
+ upgrades: array(upgradeSchema)
8695
+ });
8696
+ function nothingToInstall({ from, latest }) {
8697
+ 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`;
8698
+ }
8699
+ function renderUpdate(cli, install, update) {
8700
+ const { installed } = update;
8701
+ const skill = renderHomeSync(cli.name, update.skill);
8702
+ return installed === null ? [nothingToInstall(update), ...skill].join("\n") : [
8703
+ `installed ${install.package}@${installed.version} with the npm under ${installed.prefix}, replacing ${update.from}`,
8704
+ ...skill,
8705
+ ...update.upgrades.map(renderUpgrade)
8706
+ ].join("\n");
8707
+ }
8708
+ function interruptedAfterInstall(plan) {
8709
+ return new CliError({
8710
+ code: INTERRUPTED,
8711
+ message: `interrupted, but ${plan.package}@${plan.latest} had already been installed with the npm under ${plan.prefix}, replacing ${plan.from}`,
8712
+ hint: "nothing to run again"
8713
+ });
8714
+ }
8715
+ /** The built-in `update`, for a CLI installed globally with npm. */
8716
+ function updateCommand(cli, install, launch) {
8717
+ return defineCommand$1.write({
8718
+ description: "install the latest version of this CLI from its registry",
8719
+ examples: [{
8720
+ command: `${cli.name} update`,
8721
+ note: "install the latest version and show what changed"
8722
+ }, {
8723
+ command: `${cli.name} update --dry-run`,
8724
+ note: "show the version it would install"
8725
+ }],
8726
+ errors: [NOT_GLOBAL_INSTALL, ...NPM_ERRORS],
8727
+ output: updateSchema,
8728
+ plan: async (_input, ctx) => {
8729
+ const npm = globalNpm(cli, install, ctx, launch);
8730
+ const copy = await globalInstallOf(launch.bin, npm);
8731
+ return {
8732
+ package: install.package,
8733
+ from: cli.version,
8734
+ latest: await latestVersion(npm),
8735
+ prefix: copy.prefix,
8736
+ packageDir: copy.packageDir,
8737
+ copies: cli.skill === void 0 ? [] : await homeCopiesOf(ctx.files, cli.name, ctx.env)
8738
+ };
8739
+ },
8740
+ apply: async (plan, ctx) => {
8741
+ const { from, latest, prefix } = plan;
8742
+ if (standingOf$1(from, latest) !== "behind") return {
8743
+ from,
8744
+ latest,
8745
+ installed: null,
8746
+ skill: await syncHome(cli, plan.packageDir, ctx),
8747
+ upgrades: []
8748
+ };
8749
+ ctx.signal.throwIfAborted();
8750
+ ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
8751
+ const skill = await ctx.runToEnd(`installing ${plan.package}@${latest}`, async () => {
8752
+ await installGlobally(latest, globalNpm(cli, install, ctx, launch));
8753
+ return syncHome(cli, plan.packageDir, ctx);
8499
8754
  });
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
- };
8755
+ const update = {
8756
+ from,
8757
+ latest,
8758
+ installed: {
8759
+ version: latest,
8760
+ prefix
8761
+ },
8762
+ skill,
8763
+ upgrades: await upgradesOf(ctx.files, plan.packageDir, from, latest)
8764
+ };
8765
+ if (ctx.signal.aborted) {
8766
+ ctx.deliver(update);
8767
+ throw interruptedAfterInstall(plan);
8768
+ }
8769
+ return update;
8770
+ },
8771
+ render: (update) => renderUpdate(cli, install, update),
8772
+ 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")
8773
+ });
8511
8774
  }
8512
8775
  //#endregion
8513
8776
  //#region ../agent-cli/dist/built-ins/install/release-check.js
@@ -8572,25 +8835,19 @@ function parsedCheck(text) {
8572
8835
  return;
8573
8836
  }
8574
8837
  }
8575
- async function lastCheck(file) {
8576
- const text = await orMissing(readFile(file, "utf8"));
8838
+ async function lastCheck(files, file) {
8839
+ const text = await readIfAny(files, file);
8577
8840
  return text === void 0 ? void 0 : parsedCheck(text);
8578
8841
  }
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) {
8842
+ async function startCheck(cli, install, file, last, launch, ctx) {
8586
8843
  const checkedAt = ctx.clock.now();
8587
8844
  const previous = last?.latest ?? "";
8588
- await writeCheck(file, {
8845
+ await ctx.files.write(file, JSON.stringify({
8589
8846
  checkedAt,
8590
8847
  latest: last?.latest
8591
- });
8592
- const npm = globalNpm(cli, install, ctx, node);
8593
- const { path, env } = npmOf(npm, process.platform === "win32");
8848
+ }));
8849
+ const npm = globalNpm(cli, install, ctx, launch);
8850
+ const { path, env } = await npmOf(npm);
8594
8851
  await ctx.spawnDetached(process.execPath, [
8595
8852
  "-e",
8596
8853
  CHECK_SCRIPT,
@@ -8623,7 +8880,7 @@ async function releaseCheck(cli, install, runAs, launch, ctx) {
8623
8880
  }
8624
8881
  async function checkAndTell(cli, install, runAs, launch, ctx) {
8625
8882
  const file = cacheFileOf(cli.name, ctx.env);
8626
- const last = await lastCheck(file);
8883
+ const last = await lastCheck(ctx.files, file);
8627
8884
  if (last?.error !== void 0) debugLine(cli, ctx, last.error);
8628
8885
  if (last === void 0 || ctx.clock.now() - last.checkedAt >= CHECK_INTERVAL_MS) await startCheck(cli, install, file, last, launch, ctx);
8629
8886
  const latest = last?.latest;
@@ -8632,14 +8889,14 @@ async function checkAndTell(cli, install, runAs, launch, ctx) {
8632
8889
  }
8633
8890
  //#endregion
8634
8891
  //#region ../agent-cli/dist/built-ins/install/global.js
8635
- function checkOf(cli, install, { bin, node }) {
8892
+ function checkOf(cli, install, launch) {
8636
8893
  return {
8637
8894
  install: async (ctx) => {
8638
- const npm = globalNpm(cli, install, ctx, node);
8895
+ const npm = globalNpm(cli, install, ctx, launch);
8639
8896
  try {
8640
8897
  return {
8641
8898
  state: "ok",
8642
- message: `a global npm install under ${(await globalInstallOf(bin, npm)).prefix}`
8899
+ message: `a global npm install under ${(await globalInstallOf(launch.bin, npm)).prefix}`
8643
8900
  };
8644
8901
  } catch (error) {
8645
8902
  if (error instanceof CliError && error.code === "NOT_GLOBAL_INSTALL") return {
@@ -8653,7 +8910,7 @@ function checkOf(cli, install, { bin, node }) {
8653
8910
  release: async (ctx) => {
8654
8911
  const fix = `${cli.name} update`;
8655
8912
  return {
8656
- npm: globalNpm(cli, install, ctx, node),
8913
+ npm: globalNpm(cli, install, ctx, launch),
8657
8914
  advise: () => ({ fix })
8658
8915
  };
8659
8916
  }
@@ -8667,7 +8924,6 @@ function globalInstall(cli, install) {
8667
8924
  setup: setupCommand(cli, install, launch),
8668
8925
  update: updateCommand(cli, install, launch)
8669
8926
  }),
8670
- exitClasses: ["source"],
8671
8927
  check: (launch) => checkOf(cli, install, launch),
8672
8928
  notice: (launch, ctx) => releaseCheck(cli, install, adapter.runAs, launch, ctx)
8673
8929
  };
@@ -8684,7 +8940,6 @@ function noInstall(cli) {
8684
8940
  return {
8685
8941
  runAs: runAsOf(cli),
8686
8942
  builtIns: () => ({}),
8687
- exitClasses: [],
8688
8943
  check: () => ({
8689
8944
  install: skipped,
8690
8945
  release: async () => ({ skipped: await skipped() })
@@ -8715,9 +8970,9 @@ function withSection({ name, text: section }, text) {
8715
8970
  if (text === "") return `${whole}\n`;
8716
8971
  return `${text}${text.endsWith("\n") ? "" : "\n"}\n${whole}\n`;
8717
8972
  }
8718
- async function agentsMdAt(agents, root) {
8973
+ async function agentsMdAt(files, agents, root) {
8719
8974
  const path = join(root, "AGENTS.md");
8720
- const existing = await orMissing(readFile(path, "utf8"));
8975
+ const existing = await readIfAny(files, path);
8721
8976
  if (existing === void 0) return {
8722
8977
  path,
8723
8978
  agents,
@@ -8734,9 +8989,9 @@ async function agentsMdAt(agents, root) {
8734
8989
  change: existing.includes(markersOf(agents.name).begin) ? "rewrite" : "add"
8735
8990
  };
8736
8991
  }
8737
- async function handEditsAt(root) {
8992
+ async function handEditsAt(files, root) {
8738
8993
  const claudeMd = join(root, "CLAUDE.md");
8739
- const text = await orMissing(readFile(claudeMd, "utf8"));
8994
+ const text = await readIfAny(files, claudeMd);
8740
8995
  if (text === void 0 || text.split(/\r?\n/).some((line) => line.trim() === "@AGENTS.md")) return [];
8741
8996
  return [`add a line \`@AGENTS.md\` to ${claudeMd}, so Claude Code reads AGENTS.md`];
8742
8997
  }
@@ -8746,8 +9001,8 @@ function agentsRow(agents, install) {
8746
9001
  name: "agents",
8747
9002
  advisory: true,
8748
9003
  run: async (ctx) => {
8749
- const root = await projectRootOf(ctx.cwd);
8750
- const { path, change } = await agentsMdAt(agents, root);
9004
+ const root = await projectRootOf(ctx.files, ctx.cwd);
9005
+ const { path, change } = await agentsMdAt(ctx.files, agents, root);
8751
9006
  const fix = `${install.runAs} skill install`;
8752
9007
  if (change === "rewrite") return {
8753
9008
  state: "problem",
@@ -8760,7 +9015,7 @@ function agentsRow(agents, install) {
8760
9015
  fix
8761
9016
  };
8762
9017
  const holds = `${path} holds the ${name} Agents section`;
8763
- const [edit] = await handEditsAt(root);
9018
+ const [edit] = await handEditsAt(ctx.files, root);
8764
9019
  return edit === void 0 ? {
8765
9020
  state: "ok",
8766
9021
  message: holds
@@ -8774,8 +9029,8 @@ function agentsRow(agents, install) {
8774
9029
  //#endregion
8775
9030
  //#region ../agent-cli/dist/built-ins/skill/install-command.js
8776
9031
  /** 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);
9032
+ async function skillCopyOf(files, cli, cwd) {
9033
+ const root = await projectRootOf(files, cwd);
8779
9034
  return {
8780
9035
  root,
8781
9036
  copy: join(root, ".claude", "skills", cli.name)
@@ -8791,12 +9046,14 @@ const installSchema = object({
8791
9046
  }).nullable(),
8792
9047
  handEdits: array(string())
8793
9048
  });
8794
- async function planOf(cli, folder, agents, cwd, packageRoot) {
8795
- const { root, copy } = await skillCopyOf(cli, cwd);
9049
+ async function planOf(cli, folder, agents, { files, cwd, packageRoot }) {
9050
+ const source = join(packageRoot, folder);
9051
+ await readRequired(files, join(source, "SKILL.md"));
9052
+ const { root, copy } = await skillCopyOf(files, cli, cwd);
8796
9053
  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)
9054
+ ...await syncOf(files, source, copy),
9055
+ agentsMd: agents === void 0 ? null : await agentsMdAt(files, agents, root),
9056
+ handEdits: agents === void 0 ? [] : await handEditsAt(files, root)
8800
9057
  };
8801
9058
  }
8802
9059
  const SECTION_VERBS = {
@@ -8828,7 +9085,7 @@ function renderInstalled(cli, data) {
8828
9085
  }
8829
9086
  /** The built-in `skill install`, for a CLI whose skill ships in its package. */
8830
9087
  function skillInstallCommand(cli, folder, agents) {
8831
- return defineCommand$1({
9088
+ return defineCommand$1.write({
8832
9089
  description: agents === void 0 ? "copy this CLI's skill into the project" : "copy this CLI's skill into the project and write its Agents section",
8833
9090
  examples: [{
8834
9091
  command: `${cli.name} skill install`,
@@ -8837,14 +9094,13 @@ function skillInstallCommand(cli, folder, agents) {
8837
9094
  command: `${cli.name} skill install --dry-run`,
8838
9095
  note: "show what it would change"
8839
9096
  }],
8840
- effect: "write",
8841
9097
  output: installSchema,
8842
- plan: (_input, ctx) => planOf(cli, folder, agents, ctx.cwd, ctx.packageRoot),
9098
+ plan: (_input, ctx) => planOf(cli, folder, agents, ctx),
8843
9099
  apply: async (plan, ctx) => {
8844
9100
  await applySync(plan, ctx);
8845
9101
  const { agentsMd } = plan;
8846
9102
  if (agentsMd !== null && agentsMd.change !== null) {
8847
- const existing = await orMissing(readFile(agentsMd.path, "utf8")) ?? "";
9103
+ const existing = await readIfAny(ctx.files, agentsMd.path) ?? "";
8848
9104
  await ctx.files.write(agentsMd.path, withSection(agentsMd.agents, existing));
8849
9105
  }
8850
9106
  return {
@@ -8866,13 +9122,13 @@ function skillInstallCommand(cli, folder, agents) {
8866
9122
  //#region ../agent-cli/dist/built-ins/skill/project.js
8867
9123
  async function personalCopyOf(name, copy, ctx) {
8868
9124
  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;
9125
+ const real = await ctx.files.realPath(home);
9126
+ return real === void 0 || real === await ctx.files.realPath(copy) ? void 0 : home;
8871
9127
  }
8872
9128
  /** The project root's copy against the package's, and any under home. */
8873
9129
  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")]);
9130
+ const { copy } = await skillCopyOf(ctx.files, cli, ctx.cwd);
9131
+ const [installed, shipped] = await Promise.all([readIfAny(ctx.files, join(copy, "SKILL.md")), readRequired(ctx.files, join(ctx.packageRoot, folder, "SKILL.md"))]);
8876
9132
  const stated = installed === void 0 ? void 0 : skillVersionIn(installed);
8877
9133
  const left = stated !== void 0 && skillVersionIn(shipped) === void 0;
8878
9134
  const found = left ? `${copy} (${statedVersionNote(stated)})` : copy;
@@ -9050,7 +9306,7 @@ function registryRow(cli, check) {
9050
9306
  function doctorChecks(cli, launch, scope) {
9051
9307
  const install = installOf(cli);
9052
9308
  const check = install.check(launch);
9053
- const tools = toolRows(cli.tools ?? [], cli.commands);
9309
+ const tools = toolRows(cli.tools ?? [], cli.commands, launch.platform);
9054
9310
  return {
9055
9311
  checks: [
9056
9312
  buildRow(cli),
@@ -9066,6 +9322,8 @@ function doctorChecks(cli, launch, scope) {
9066
9322
  tools: new Set(tools.map(({ name }) => name))
9067
9323
  };
9068
9324
  }
9325
+ //#endregion
9326
+ //#region ../agent-cli/dist/built-ins/doctor.js
9069
9327
  const DEFAULT_DEADLINE_MS = 1e4;
9070
9328
  const MS_PER_SECOND = 1e3;
9071
9329
  const MIN_LABEL_WIDTH = 8;
@@ -9136,12 +9394,12 @@ async function rowOf(check, ctx, { read, runAs, tools }) {
9136
9394
  check: check.name,
9137
9395
  ...finding
9138
9396
  },
9139
- advisory: check.advisory === true
9397
+ check
9140
9398
  };
9141
9399
  }
9142
9400
  function nextOf(checked, doctorLine) {
9143
9401
  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);
9402
+ const unfixed = checked.some(({ row, check }) => check.advisory === true && row.state === "problem" && row.fix === void 0);
9145
9403
  return [...new Set(unfixed ? [...fixes, doctorLine] : fixes)];
9146
9404
  }
9147
9405
  function render({ rows, next }, style) {
@@ -9170,9 +9428,14 @@ function failure(cliName, rerun, problems) {
9170
9428
  });
9171
9429
  return new AggregateError(errors);
9172
9430
  }
9431
+ function checkedRows(error, problems, table) {
9432
+ 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}`] : []);
9433
+ if (undeclared.length === 0) return error;
9434
+ return new Error(`doctor: ${undeclared.join("; ")}, which its errors do not declare`, { cause: error });
9435
+ }
9173
9436
  /** The built-in `doctor`: the fixed rows around the CLI's own, each under its own deadline. */
9174
9437
  function doctorCommand(cli, launch) {
9175
- return defineCommand$1({
9438
+ const doctor = defineCommand$1.read({
9176
9439
  description: "check the setup and say what fixes each problem",
9177
9440
  examples: [{
9178
9441
  command: `${cli.name} doctor`,
@@ -9181,7 +9444,6 @@ function doctorCommand(cli, launch) {
9181
9444
  command: `${cli.name} doctor --json`,
9182
9445
  note: "get the rows and fixes as data"
9183
9446
  }],
9184
- effect: "read",
9185
9447
  output: reportSchema,
9186
9448
  run: async (_input, ctx) => {
9187
9449
  const rowsScope = {
@@ -9201,15 +9463,20 @@ function doctorCommand(cli, launch) {
9201
9463
  ctx.deliver(report);
9202
9464
  ctx.signal.throwIfAborted();
9203
9465
  }
9204
- const problems = checked.filter(({ row, advisory }) => row.state === "problem" && !advisory).map(({ row }) => row);
9466
+ const problems = checked.filter(({ row, check }) => row.state === "problem" && check.advisory !== true);
9205
9467
  if (problems.length > 0) {
9206
9468
  ctx.deliver(report);
9207
- throw failure(cli.name, ctx.rerun(), problems);
9469
+ throw checkedRows(failure(cli.name, ctx.rerun(), problems.map(({ row }) => row)), problems, errorTableOf(cli));
9208
9470
  }
9209
9471
  return report;
9210
9472
  },
9211
9473
  render
9212
9474
  });
9475
+ const rowCodes = (cli.doctor ?? []).flatMap((row) => row.advisory === true ? [] : row.errors ?? []);
9476
+ return {
9477
+ ...doctor,
9478
+ errors: [.../* @__PURE__ */ new Set([DOCTOR_PROBLEMS, ...rowCodes])]
9479
+ };
9213
9480
  }
9214
9481
  //#endregion
9215
9482
  //#region ../agent-cli/dist/built-ins/commands.js
@@ -9309,19 +9576,6 @@ function specOf(commandLine, node, place) {
9309
9576
  return isCommand(node) ? commandSpec(commandLine, node, place) : group(commandLine, specsOf(commandLine, node, place));
9310
9577
  }
9311
9578
  //#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
9579
  //#region ../agent-cli/dist/exit-classes.js
9326
9580
  const SIGNAL_EXIT_CODES = {
9327
9581
  SIGINT: 130,
@@ -9334,95 +9588,38 @@ const EXIT_CODES = {
9334
9588
  wait: 75,
9335
9589
  bug: 70
9336
9590
  };
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
9591
  /** The codes of `errors` that the Runtime already declares, which a CLI cannot redeclare. */
9373
9592
  function runtimeCodesIn(errors) {
9374
9593
  return Object.keys(errors).filter(isRuntimeCode);
9375
9594
  }
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";
9595
+ /** The class of `code` in the Runtime's Error table or else in the CLI's `errors`. */
9596
+ function classOfCode(code, errors) {
9597
+ return isRuntimeCode(code) ? runtimeErrors[code].class : errors[code]?.class;
9384
9598
  }
9385
- function isWait(error, errors) {
9386
- return exitClassOfOne(error, errors) === "wait";
9599
+ function declaredCodesOf(node) {
9600
+ return node.kind === "command" ? node.command.errors ?? [] : Object.values(node.commands).flatMap(declaredCodesOf);
9387
9601
  }
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";
9602
+ function isDeclarable(exitClass) {
9603
+ return exitClass !== void 0 && exitClass !== "bug" && exitClass !== "interrupted";
9399
9604
  }
9400
9605
  /**
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.
9606
+ * The classes other than bug and interrupt that a run of `cli` can end in: those of the Runtime
9607
+ * codes any Command can meet, those of the codes its Commands under `root` declare, Built-ins
9608
+ * included, and those of its own table.
9403
9609
  */
9404
- function reachableExitClasses(cli) {
9405
- const runtime = [
9406
- "caller",
9407
- "operator",
9408
- "wait",
9409
- ...cli.commands === void 0 ? [] : installOf(cli).exitClasses
9610
+ function reachableExitClasses(cli, root) {
9611
+ const table = errorTableOf(cli);
9612
+ const classes = [
9613
+ ...anyCommandClasses,
9614
+ ...declaredCodesOf(root).map((code) => classOfCode(code, table)),
9615
+ ...Object.values(table).map((declaration) => declaration.class)
9410
9616
  ];
9411
- const own = Object.values(errorTableOf(cli)).map((declaration) => declaration.class);
9412
- return /* @__PURE__ */ new Set([...runtime, ...own]);
9617
+ return new Set(classes.filter(isDeclarable));
9413
9618
  }
9414
9619
  /** The signal named by an interrupt's abort reason; any other reason counts as Ctrl-C. */
9415
9620
  function stopSignalOf(reason) {
9416
9621
  return reason === "SIGTERM" ? "SIGTERM" : "SIGINT";
9417
9622
  }
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
9623
  //#endregion
9427
9624
  //#region ../agent-cli/dist/agent-cli.js
9428
9625
  function checkOwnCodes(errors) {
@@ -9443,7 +9640,7 @@ function checkDocs({ name, docs }) {
9443
9640
  if (docs === void 0) return;
9444
9641
  if (!(docs !== "" && !isAbsolute(docs) && !normalize(docs).startsWith(".."))) throw new Error(`${name} declares docs ${JSON.stringify(docs)}: name a folder inside its package, relative to the package root`);
9445
9642
  }
9446
- function checkCli(cli) {
9643
+ function checkDeclared(cli) {
9447
9644
  const table = errorTableOf(cli);
9448
9645
  checkDocs(cli);
9449
9646
  if (cli.commands === void 0) {
@@ -9454,9 +9651,11 @@ function checkCli(cli) {
9454
9651
  checkSkill(cli);
9455
9652
  const { commands } = cli;
9456
9653
  checkTree(commands, [], table);
9654
+ for (const row of cli.doctor ?? []) checkErrors(row.errors ?? [], `the ${row.name} row of doctor`, table);
9457
9655
  const taken = Object.keys(builtInCommands(cli, {
9458
9656
  bin: void 0,
9459
- node: void 0
9657
+ node: void 0,
9658
+ platform: process.platform
9460
9659
  })).filter((name) => commands[name] !== void 0);
9461
9660
  if (taken.length > 0) throw new Error(`${cli.name} defines ${taken.join(", ")}, a built-in Command it cannot replace`);
9462
9661
  return cli;
@@ -9514,16 +9713,17 @@ function resolved(cli) {
9514
9713
  }
9515
9714
  /**
9516
9715
  * Takes the CLI's ports factory, Project file and Error table once; the `defineCommand`,
9517
- * `defineCli` and `cliError` it returns are typed by them.
9716
+ * `defineDoctorCheck`, `defineCli` and `cliError` it returns are typed by them.
9518
9717
  */
9519
9718
  function agentCli(values = {}) {
9520
9719
  checkOwnCodes(errorTableOf(values));
9521
9720
  return {
9522
9721
  defineCommand: defineCommandFor(),
9523
- defineCli: (cli) => checkCli({
9722
+ defineCli: (cli) => checkDeclared({
9524
9723
  ...resolved(cli),
9525
9724
  ...values
9526
9725
  }),
9726
+ defineDoctorCheck: (row) => row,
9527
9727
  cliError: cliErrorOf
9528
9728
  };
9529
9729
  }
@@ -9844,37 +10044,6 @@ function route(root, invocation) {
9844
10044
  return walk(root, argv.filter((word, index) => !(index < options.length && word === JSON_FLAG.flag)), json, invocation);
9845
10045
  }
9846
10046
  //#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
10047
  //#region ../agent-cli/dist/command-line/shell-words.js
9879
10048
  const BLANK = /\s/;
9880
10049
  const SHELL_SYNTAX = /* @__PURE__ */ new Set([
@@ -9973,39 +10142,126 @@ function shellWords(line) {
9973
10142
  };
9974
10143
  }
9975
10144
  //#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)
10145
+ //#region ../agent-cli/dist/command-line/line-problem.js
10146
+ const STDIN_TEXT = "text from stdin";
10147
+ /**
10148
+ * Why `line` would not run a Command of `root`: it does not tokenize, start with its lead, route to
10149
+ * a Command or pass its schemas; `undefined` when it would.
10150
+ */
10151
+ async function lineProblem(root, { text, lead, runAs }) {
10152
+ const words = shellWords(text);
10153
+ if (!words.ok) return `it does not tokenize: ${words.reason}`;
10154
+ const leadWords = shellWords(lead);
10155
+ if (!leadWords.ok) return `its lead does not tokenize: ${leadWords.reason}`;
10156
+ if (leadWords.words.some((word, index) => words.words[index] !== word)) return `it does not start with \`${lead}\``;
10157
+ const invocation = {
10158
+ runAs,
10159
+ argv: words.words.slice(leadWords.words.length),
10160
+ flags: []
9985
10161
  };
10162
+ const routed = route(root, invocation);
10163
+ switch (routed.kind) {
10164
+ case "usage error": return routed.error.message;
10165
+ case "help":
10166
+ case "version": return "it names no Command";
10167
+ case "command": try {
10168
+ await parseCommand(routed, invocation, async () => STDIN_TEXT);
10169
+ return;
10170
+ } catch (error) {
10171
+ if (error instanceof UsageError) return error.message;
10172
+ throw error;
10173
+ }
10174
+ }
9986
10175
  }
10176
+ //#endregion
10177
+ //#region ../agent-cli/dist/check-next-steps.js
9987
10178
  /**
9988
10179
  * Why `step` would not run a Command of `root`, or `undefined` when it would. A line given whole is
9989
10180
  * not checked, since it may run another program.
9990
10181
  */
9991
- function nextStepProblem(root, runAs, step) {
10182
+ async function nextStepProblem(root, runAs, step) {
9992
10183
  const words = wordsOf(step);
9993
10184
  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
- }) : []);
10185
+ return lineProblem(root, {
10186
+ text: lineOf(words, runAs),
10187
+ lead: runAs,
10188
+ runAs
10189
+ });
10190
+ }
10191
+ /** The check that a failure's Next steps run Commands of `root`: a bug names each that would not. */
10192
+ function nextStepCheck(root, runAs) {
10193
+ return (error) => checkedNextSteps(error, root, runAs);
10194
+ }
10195
+ async function checkedNextSteps(error, root, runAs) {
10196
+ const problems = [];
10197
+ for (const leaf of errorsOf(error)) {
10198
+ if (!(leaf instanceof CliError)) continue;
10199
+ for (const step of leaf.next) {
10200
+ const problem = await nextStepProblem(root, runAs, step);
10201
+ if (problem !== void 0) problems.push(`\`${lineOf(step, runAs)}\`, a Next step of ${leaf.code}: ${problem}`);
10202
+ }
10203
+ }
10006
10204
  return problems.length === 0 ? error : new Error(`a Next step would not run: ${problems.join("; ")}`, { cause: error });
10007
10205
  }
10008
10206
  //#endregion
10207
+ //#region ../agent-cli/dist/failure.js
10208
+ /**
10209
+ * What a run that threw `error` reports: the interrupt and deadline rewrite, then the undeclared
10210
+ * code check, then the Next step check, each leaf classified, and the exit code they give.
10211
+ */
10212
+ async function failureOf(error, sources) {
10213
+ const { command, control, nextStepCheck } = sources;
10214
+ const rewritten = control === void 0 ? error : control.errorAfter(error);
10215
+ const declared = command === void 0 ? rewritten : checkedCodes(rewritten, command, sources);
10216
+ return failureFrom(classified(nextStepCheck === void 0 ? declared : await nextStepCheck(declared), sources.errors), sources.stop);
10217
+ }
10218
+ /** `failure` with `error`'s leaves after its own, which may change its exit code. */
10219
+ function withLeaves(failure, error, sources) {
10220
+ return failureFrom([...failure.leaves, ...classified(error, sources.errors)], sources.stop);
10221
+ }
10222
+ function classified(error, errors) {
10223
+ return errorsOf(error).map((leaf) => ({
10224
+ error: leaf,
10225
+ exitClass: exitClassOfOne(leaf, errors)
10226
+ }));
10227
+ }
10228
+ function failureFrom(leaves, stop) {
10229
+ const exitClass = exitClassOf(leaves);
10230
+ return {
10231
+ leaves,
10232
+ exitCode: exitClass === "interrupted" ? SIGNAL_EXIT_CODES[stopSignalOf(stop?.reason)] : EXIT_CODES[exitClass]
10233
+ };
10234
+ }
10235
+ function exitClassOfOne(error, errors) {
10236
+ if (!(error instanceof CliError)) return "bug";
10237
+ if (error instanceof UsageError) return "caller";
10238
+ return classOfCode(error.code, errors) ?? "bug";
10239
+ }
10240
+ const ACT_FIRST = [
10241
+ "caller",
10242
+ "operator",
10243
+ "source",
10244
+ "wait",
10245
+ "interrupted"
10246
+ ];
10247
+ function exitClassOf(leaves) {
10248
+ const classes = leaves.map((leaf) => leaf.exitClass);
10249
+ if (classes.includes("bug")) return "bug";
10250
+ return ACT_FIRST.find((exitClass) => classes.includes(exitClass)) ?? "bug";
10251
+ }
10252
+ /**
10253
+ * `error`, or a bug naming each code in it that the Command must declare in its `errors` and does
10254
+ * not: one of the Error table, or a Runtime code only some Commands reach.
10255
+ */
10256
+ function checkedCodes(error, { command, path }, { cliName, errors }) {
10257
+ const declared = new Set(command.errors ?? []);
10258
+ const undeclared = errorsOf(error).flatMap((leaf) => leaf instanceof CliError && mustDeclare(leaf.code, errors) && !declared.has(leaf.code) ? [leaf.code] : []);
10259
+ if (undeclared.length === 0) return error;
10260
+ const label = commandLabel(path, cliName).join(" ");
10261
+ const codes = [...new Set(undeclared)].join(", ");
10262
+ return new Error(`\`${label}\` threw ${codes}, which its errors do not declare`, { cause: error });
10263
+ }
10264
+ //#endregion
10009
10265
  //#region ../agent-cli/dist/help.js
10010
10266
  function table(rows) {
10011
10267
  const width = Math.max(...rows.map(([left]) => left.length));
@@ -10043,10 +10299,10 @@ const EXIT_MEANINGS = {
10043
10299
  source: "someone at the source acts",
10044
10300
  wait: "not done yet: do what the hint says, then run it again"
10045
10301
  };
10046
- function exitCodeRows(cli) {
10302
+ function exitCodeRows(cli, root) {
10047
10303
  return [
10048
10304
  ["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]),
10305
+ ...[...[...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
10306
  [Object.values(SIGNAL_EXIT_CODES).join(", "), "interrupted"]
10051
10307
  ];
10052
10308
  }
@@ -10064,7 +10320,7 @@ function examplesSection(examples, style) {
10064
10320
  }
10065
10321
  /** The CLI's own help: a listing or its single Command's help, ending with exit codes and examples. */
10066
10322
  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));
10323
+ 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
10324
  }
10069
10325
  function groupHelp(group, style) {
10070
10326
  return lines(...listingLines(group.commandLine, group, style));
@@ -10142,7 +10398,7 @@ function commandData(spec, table) {
10142
10398
  secrets: spec.command.secrets ?? [],
10143
10399
  errors: (spec.command.errors ?? []).map((code) => ({
10144
10400
  code,
10145
- class: table[code]?.class
10401
+ class: classOfCode(code, table)
10146
10402
  })),
10147
10403
  args: inputSchema(spec.command.args ?? object({})),
10148
10404
  options: optionsSchema(spec.options),
@@ -10175,7 +10431,10 @@ function errorsData(errors) {
10175
10431
  function publishedTable(table, root) {
10176
10432
  if (root.kind !== "command") return table;
10177
10433
  const declared = new Set(root.command.errors ?? []);
10178
- return Object.fromEntries(Object.entries(table).filter(([code]) => declared.has(code)));
10434
+ return Object.fromEntries(Object.entries({
10435
+ ...table,
10436
+ ...declaredRuntimeErrors
10437
+ }).filter(([code]) => declared.has(code)));
10179
10438
  }
10180
10439
  function cliHelpJson(cli, root) {
10181
10440
  const { name, version, description } = cli;
@@ -10225,15 +10484,20 @@ function noticeLine(text, json) {
10225
10484
  function stderrLine(key, text, json) {
10226
10485
  return json ? `${JSON.stringify({ [key]: text })}\n` : `${text}\n`;
10227
10486
  }
10228
- function errorText(error, format) {
10229
- const text = formatRuntimeError(error, {
10487
+ function errorText({ leaves }, format) {
10488
+ const text = formatRuntimeError(leaves, {
10230
10489
  ...format,
10231
- runAs: format.invocation.runAs,
10232
- isBug: (each) => isBug(each, format.errors),
10233
- isWait: (each) => isWait(each, format.errors)
10490
+ runAs: format.invocation.runAs
10234
10491
  });
10235
10492
  const { usageHelp, json } = format;
10236
- return usageHelp !== void 0 && !json && error instanceof UsageError ? `${text}\n${usageHelp}` : text;
10493
+ return usageHelp !== void 0 && !json && leaves.some((leaf) => leaf.error instanceof UsageError) ? `${text}\n${usageHelp}` : text;
10494
+ }
10495
+ function failedResult(failure, format) {
10496
+ return {
10497
+ stdout: "",
10498
+ stderr: errorText(failure, format),
10499
+ exitCode: failure.exitCode
10500
+ };
10237
10501
  }
10238
10502
  function stdoutOf({ value, render }, format, spill) {
10239
10503
  const text = printed(value, render, format);
@@ -10251,14 +10515,9 @@ const STOPPED_RESULT = {
10251
10515
  stdout: "",
10252
10516
  stderr: "",
10253
10517
  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
- };
10518
+ };
10519
+ async function errorResult(error, format) {
10520
+ return failedResult(await failureOf(error, format), format);
10262
10521
  }
10263
10522
  async function commandResult(settled, format, spill) {
10264
10523
  if (settled.ok) try {
@@ -10272,23 +10531,23 @@ async function commandResult(settled, format, spill) {
10272
10531
  }
10273
10532
  const { error, delivered } = settled;
10274
10533
  if (delivered === void 0) return errorResult(error, format);
10534
+ const failure = await failureOf(error, format);
10275
10535
  let outcome;
10276
10536
  try {
10277
10537
  outcome = delivered();
10278
10538
  } catch (rejected) {
10279
- return errorResult(new AggregateError([...errorsOf(error), rejected]), format);
10539
+ return failedResult(withLeaves(failure, rejected, format), format);
10280
10540
  }
10281
- const result = errorResult(error, format);
10541
+ const result = failedResult(failure, format);
10282
10542
  try {
10283
10543
  return {
10284
10544
  ...result,
10285
10545
  stdout: await stdoutOf(outcome, format, spill)
10286
10546
  };
10287
10547
  } catch (spillError) {
10288
- const both = new AggregateError([...errorsOf(error), spillError]);
10289
10548
  return {
10290
10549
  ...result,
10291
- stderr: errorText(both, format)
10550
+ stderr: errorText(withLeaves(failure, spillError, format), format)
10292
10551
  };
10293
10552
  }
10294
10553
  }
@@ -10352,107 +10611,6 @@ function rootSpec(cli, launch) {
10352
10611
  } : groupSpec(names, cli.commands, builtInCommands(cli, launch));
10353
10612
  }
10354
10613
  //#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
10614
  //#region ../agent-cli/dist/context/spill.js
10457
10615
  const STDOUT_BUDGET_CHARS = 3e4;
10458
10616
  const SPILL_RETENTION_MS = 6048e5;
@@ -11033,43 +11191,6 @@ function resolveOptions(options) {
11033
11191
  };
11034
11192
  }
11035
11193
  //#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
11194
  //#region ../agent-cli/dist/execute.js
11074
11195
  const PLAN_INDENT = " ";
11075
11196
  function outcomeOf(value, render) {
@@ -11117,8 +11238,8 @@ function serveContextOf(ctx, end) {
11117
11238
  const serveCtx = Object.create(ctx);
11118
11239
  return Object.assign(serveCtx, { end });
11119
11240
  }
11120
- async function execute(command, input, ctx, { dryRun, confirm, yes, page, commandLine, started, files, guardOutputs }) {
11121
- await checkTools(command.tools ?? [], ctx.env, ctx.rerun());
11241
+ async function execute(command, input, ctx, { flags, commandLine, started, outputs: runOutputs }) {
11242
+ await checkTools(command.tools ?? [], ctx, ctx.rerun());
11122
11243
  const { env, envFile, fileError } = ctx;
11123
11244
  checkSecrets(command.secrets ?? [], {
11124
11245
  env,
@@ -11129,7 +11250,7 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, page, comman
11129
11250
  const { render } = command;
11130
11251
  return outcomeOf(await command.run({
11131
11252
  ...input,
11132
- ...page
11253
+ ...flags.page
11133
11254
  }, ctx), (data, style) => render === void 0 ? encode(data.items) : render(data, style));
11134
11255
  }
11135
11256
  if (command.effect === "read" || command.effect === "act") return dataOutcome(command, await command.run(input, ctx), commandLine);
@@ -11144,32 +11265,28 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, page, comman
11144
11265
  return outcome;
11145
11266
  }
11146
11267
  const plan = await command.plan(input, ctx);
11147
- if (dryRun) return dryRunOutcome(command, plan, commandLine);
11268
+ if (ctx.dryRun) return dryRunOutcome(command, plan, commandLine);
11148
11269
  const outputs = command.outputs?.(plan);
11149
- await checkOutputs({
11150
- rerun: ctx.rerun,
11270
+ const { rerun, force } = ctx;
11271
+ if (outputs !== void 0) await runOutputs.refuseOverwrite(outputs, {
11272
+ rerun,
11151
11273
  commandLine,
11152
- force: ctx.force,
11153
- files: ctx.files
11154
- }, outputs);
11274
+ force
11275
+ });
11155
11276
  checkCost({
11156
- rerun: ctx.rerun,
11277
+ rerun,
11157
11278
  commandLine,
11158
- yes
11279
+ yes: flags.yes
11159
11280
  }, command.cost?.(plan));
11160
11281
  if (command.effect === "destructive") checkConfirmation({
11161
- rerun: ctx.rerun,
11282
+ rerun,
11162
11283
  commandLine,
11163
- given: confirm
11284
+ given: flags.confirm
11164
11285
  }, 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
- }
11286
+ if (outputs !== void 0) await runOutputs.claim(outputs, {
11287
+ rerun,
11288
+ force
11289
+ });
11173
11290
  return dataOutcome(command, await command.apply(plan, ctx), commandLine);
11174
11291
  }
11175
11292
  const manifestSchema = object({ bugs: union([string(), object({ url: string() })]).transform((value) => typeof value === "string" ? value : value.url).optional().catch(void 0) });
@@ -11185,16 +11302,7 @@ function manifestAt(packageRoot) {
11185
11302
  return parsed.success ? parsed.data : NO_MANIFEST;
11186
11303
  }
11187
11304
  //#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
- }
11305
+ //#region ../agent-cli/dist/run-scope.js
11198
11306
  function selfOf(name, bin) {
11199
11307
  return bin === void 0 ? [name] : [
11200
11308
  process.execPath,
@@ -11213,49 +11321,83 @@ function portsOf(cli, replacement) {
11213
11321
  if (replacement !== void 0) return () => replacement;
11214
11322
  return cli.ports ?? (() => ({}));
11215
11323
  }
11216
- function portsBaseOf(scope, signal) {
11217
- const { cli, options, packageRoot, progress, fileError, rerun, clock, files } = scope;
11218
- const { cwd, env } = options;
11324
+ function runScopeOf(cli, routed, settings, write, baseInvocation) {
11325
+ const { cwd, env } = settings;
11326
+ const clock = settings.clock ?? realClock;
11327
+ const platform = settings.platform ?? process.platform;
11328
+ const invocation = {
11329
+ ...baseInvocation,
11330
+ flags: knownFlags(routed.spec),
11331
+ stdinArg: stdinArgOf(routed)
11332
+ };
11333
+ const rerun = (change) => ({
11334
+ kind: "rerun",
11335
+ words: changedArgv(invocation, change),
11336
+ stdinArg: invocation.stdinArg
11337
+ });
11338
+ const fileError = fileErrorOf({
11339
+ rerun: rerun(),
11340
+ env,
11341
+ platform
11342
+ });
11343
+ const outputs = outputsOf(settings.files ?? realFiles(), {
11344
+ cwd,
11345
+ fileError
11346
+ });
11347
+ const packageRoot = packageRootOf(settings.bin, cwd);
11348
+ const progress = (message) => write.stderr(progressLine(message, routed.json));
11219
11349
  const envFile = envFileOf(cli.name, env);
11350
+ const spawning = {
11351
+ fileError,
11352
+ rerun: rerun()
11353
+ };
11220
11354
  return {
11221
- cwd,
11222
- env,
11223
- packageRoot,
11224
- progress,
11225
- secret: (name) => secretOf({
11226
- envFile,
11227
- env,
11228
- fileError
11229
- }, name),
11230
- envFile,
11355
+ invocation,
11356
+ rerun,
11231
11357
  fileError,
11232
- version: cli.version,
11233
- build: buildStamp(),
11234
- self: selfOf(cli.name, options.bin),
11235
- signal,
11236
11358
  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), {
11359
+ platform,
11360
+ outputs,
11361
+ progress,
11362
+ notice: (text) => write.stderr(noticeLine(text, routed.json)),
11363
+ packageRoot,
11364
+ ports: portsOf(cli, settings.ports),
11365
+ portsBase: (signal) => ({
11247
11366
  cwd,
11248
11367
  env,
11249
- signal
11250
- }, {
11368
+ platform,
11369
+ packageRoot,
11370
+ progress,
11371
+ secret: (name) => secretOf({
11372
+ envFile,
11373
+ env,
11374
+ fileError
11375
+ }, name),
11376
+ envFile,
11251
11377
  fileError,
11252
- rerun
11378
+ version: cli.version,
11379
+ build: buildStamp(),
11380
+ self: selfOf(cli.name, settings.bin),
11381
+ signal,
11382
+ clock,
11383
+ files: outputs.files,
11384
+ spawn: contextSpawn(settings.spawn ?? realSpawn, {
11385
+ cwd,
11386
+ env,
11387
+ signal
11388
+ }, spawning),
11389
+ spawnDetached: contextSpawnDetached(spawnDetachedOf(settings), {
11390
+ cwd,
11391
+ env,
11392
+ signal
11393
+ }, spawning)
11253
11394
  })
11254
11395
  };
11255
11396
  }
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]));
11397
+ function contextOf(scope, control, { command, flags, project, deliver }) {
11398
+ const portsBase = scope.portsBase(control.signal);
11399
+ const ports = once(() => scope.ports(portsBase));
11400
+ const declared = new Map((command.secrets ?? []).map((declaration) => [declaration.name, declaration]));
11259
11401
  function secret(name) {
11260
11402
  const declaration = declared.get(name);
11261
11403
  if (declaration === void 0) throw new Error(`ctx.secret("${name}") names a Secret the Command does not declare`);
@@ -11273,47 +11415,57 @@ function contextOf(portsBase, control, parts) {
11273
11415
  return ports();
11274
11416
  },
11275
11417
  project,
11276
- force,
11277
- dryRun,
11278
- rerun,
11418
+ force: flags.force,
11419
+ dryRun: flags.dryRun,
11420
+ rerun: scope.rerun,
11279
11421
  poll: (check, { intervalMs, signal: stop }) => poll(portsBase.clock, check, intervalMs, stop === void 0 ? control.signal : AbortSignal.any([control.signal, stop])),
11280
11422
  deliver,
11281
- notice
11423
+ notice: scope.notice
11424
+ };
11425
+ }
11426
+ /** `build` runs on the first call; later calls give its value or throw its error again. */
11427
+ function once(build) {
11428
+ let outcome;
11429
+ return () => {
11430
+ if (outcome === void 0) try {
11431
+ outcome = {
11432
+ ok: true,
11433
+ value: build()
11434
+ };
11435
+ } catch (error) {
11436
+ outcome = {
11437
+ ok: false,
11438
+ error
11439
+ };
11440
+ }
11441
+ if (outcome.ok) return outcome.value;
11442
+ throw outcome.error;
11443
+ };
11444
+ }
11445
+ //#endregion
11446
+ //#region ../agent-cli/dist/run-command.js
11447
+ function stdinNeverEndedHint(callerLine) {
11448
+ return `stdin never ended: pipe the text into \`${callerLine}\`, or redirect a file into it`;
11449
+ }
11450
+ function runDeadlineAdvice(resumed, pipedLine) {
11451
+ return resumed === void 0 ? { hint: stdinNeverEndedHint(pipedLine) } : {
11452
+ hint: "run the Next step to continue the work",
11453
+ next: [resumed]
11282
11454
  };
11283
11455
  }
11284
- /** Leaves the Side task running, so the caller writes the result before it waits for it. */
11456
+ /**
11457
+ * Leaves the Side task running, so the caller writes the result before it waits for it.
11458
+ *
11459
+ * A gate that needs no `ctx` (operator, parse, Project file) runs here; one that does (tools,
11460
+ * Secrets, the Plan's) runs in `execute`, after the Notices, so a failing run still delivers them.
11461
+ */
11285
11462
  async function runCommand(cli, routed, options, write, baseFormat) {
11286
11463
  const { command, commandLine, builtIn } = routed.spec;
11287
11464
  const { cwd, env } = options;
11288
- const clock = options.clock ?? realClock;
11289
- const files = options.files ?? realFiles();
11290
11465
  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
- });
11466
+ const scope = runScopeOf(cli, routed, options, write, baseFormat.invocation);
11467
+ const { invocation, rerun: rerunOf, fileError, clock, outputs, progress, platform } = scope;
11305
11468
  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
11469
  const pipedLine = commandLineOf({
11318
11470
  ...invocation,
11319
11471
  stdinArg: void 0
@@ -11330,10 +11482,18 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11330
11482
  advice: () => runDeadlineAdvice(resumed, pipedLine)
11331
11483
  }
11332
11484
  });
11333
- const failed = (error, finishSideTask) => {
11485
+ const format = {
11486
+ ...baseFormat,
11487
+ command: routed.spec,
11488
+ control,
11489
+ bugs: () => manifestAt(scope.packageRoot).bugs,
11490
+ json: routed.json,
11491
+ invocation
11492
+ };
11493
+ const failed = async (error, finishSideTask) => {
11334
11494
  control.close();
11335
11495
  return {
11336
- result: errorResult(error, format),
11496
+ result: await errorResult(error, format),
11337
11497
  finishSideTask
11338
11498
  };
11339
11499
  };
@@ -11352,24 +11512,9 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11352
11512
  }
11353
11513
  const { input, flags } = parsed;
11354
11514
  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
11515
  const finishSideTask = startSideTask(builtIn ? void 0 : cli.sideTask?.bind(cli), (sideSignal) => {
11371
- const sideBase = portsBaseOf(scope, sideSignal);
11372
- const sidePorts = once(() => ports(sideBase));
11516
+ const sideBase = scope.portsBase(sideSignal);
11517
+ const sidePorts = once(() => scope.ports(sideBase));
11373
11518
  return {
11374
11519
  ...sideBase,
11375
11520
  command: routed.spec.path,
@@ -11394,14 +11539,10 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11394
11539
  return failed(error, finishSideTask);
11395
11540
  }
11396
11541
  let delivered;
11397
- const ctx = contextOf(portsBase, control, {
11398
- ports: once(() => ports(portsBase)),
11542
+ const ctx = contextOf(scope, control, {
11543
+ command,
11544
+ flags,
11399
11545
  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
11546
  deliver: (data) => {
11406
11547
  delivered = () => dataOutcome(command, data, commandLine);
11407
11548
  }
@@ -11420,19 +11561,16 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11420
11561
  const outcome = await control.settle(noticesOf(cli, {
11421
11562
  builtIn,
11422
11563
  bin: options.bin,
11423
- node: options.node
11564
+ node: options.node,
11565
+ platform
11424
11566
  }, ctx).then(() => execute(command, input, ctx, {
11425
- dryRun: flags.dryRun,
11426
- confirm: flags.confirm,
11427
- yes: flags.yes,
11428
- page: flags.page,
11567
+ flags,
11429
11568
  commandLine,
11430
11569
  started: async (outcome) => {
11431
11570
  write.stdout(await startedOutput(outcome, format, spill));
11432
11571
  served = true;
11433
11572
  },
11434
- files,
11435
- guardOutputs: outputs.guard
11573
+ outputs
11436
11574
  })));
11437
11575
  if (served) return {
11438
11576
  result: STOPPED_RESULT,
@@ -11449,7 +11587,7 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11449
11587
  };
11450
11588
  settled = {
11451
11589
  ok: false,
11452
- error: checkedCodes(control.errorAfter(error), routed.spec, cli),
11590
+ error,
11453
11591
  delivered
11454
11592
  };
11455
11593
  }
@@ -11461,25 +11599,6 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11461
11599
  finishSideTask
11462
11600
  };
11463
11601
  }
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
11602
  async function noticesOf(cli, launch, ctx) {
11484
11603
  await installOf(cli).notice(launch, ctx);
11485
11604
  }
@@ -11538,7 +11657,8 @@ async function routeAndRun(cli, argv, options, write, stopNotice) {
11538
11657
  const runAs = runAsOf(cli);
11539
11658
  const root = rootSpec(cli, {
11540
11659
  bin: options.bin,
11541
- node: options.node
11660
+ node: options.node,
11661
+ platform: options.platform ?? process.platform
11542
11662
  });
11543
11663
  const format = {
11544
11664
  cliName: cli.name,
@@ -11552,7 +11672,7 @@ async function routeAndRun(cli, argv, options, write, stopNotice) {
11552
11672
  json: false,
11553
11673
  errors: errorTableOf(cli),
11554
11674
  stop: options.signal,
11555
- ...options.checkNextSteps === true ? { checkNextStep: (step) => nextStepProblem(root, runAs, step) } : {}
11675
+ ...options.checkNextSteps === true ? { nextStepCheck: nextStepCheck(root, runAs) } : {}
11556
11676
  };
11557
11677
  const routed = route(root, format.invocation);
11558
11678
  const misused = (node) => isAgent(options.env) ? {
@@ -11631,7 +11751,7 @@ async function runProcess(cli) {
11631
11751
  //#region package.json
11632
11752
  var package_default = {
11633
11753
  name: "@duyquangnvx/create-agent-cli",
11634
- version: "0.27.0",
11754
+ version: "0.29.0",
11635
11755
  description: "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
11636
11756
  type: "module",
11637
11757
  engines: { "node": ">=22.12.0" },
@@ -11918,13 +12038,12 @@ async function ensureFree(path) {
11918
12038
  //#region src/bin.ts
11919
12039
  await runProcess(defineCli({
11920
12040
  manifest: package_default,
11921
- command: defineCommand({
12041
+ command: defineCommand.write({
11922
12042
  description: "Create a new Agent CLI project in ./<name>",
11923
12043
  examples: [{
11924
12044
  command: "create-agent-cli weather --description \"Weather forecasts\"",
11925
12045
  note: "create ./weather, install dependencies and make the first commit"
11926
12046
  }],
11927
- effect: "write",
11928
12047
  errors: ["STEP_FAILED"],
11929
12048
  args: object({ name: string().regex(PACKAGE_NAME, "must be a lowercase npm package name, such as weather or @me/weather").describe("package name; its unscoped part names the bin and the directory") }),
11930
12049
  options: object({