@duyquangnvx/create-agent-cli 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
2
3
  import { spawn } from "node:child_process";
3
4
  import { constants, homedir, tmpdir } from "node:os";
4
5
  import { parseArgs, parseEnv } from "node:util";
5
6
  import { constants as constants$1, existsSync, readFileSync, realpathSync } from "node:fs";
6
7
  import { access, chmod, link, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
7
- import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
8
8
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
9
9
  import { fileURLToPath } from "node:url";
10
10
  //#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/util.js
@@ -6033,11 +6033,15 @@ function printedDetail(error, format) {
6033
6033
  const value = Reflect.get(details, key);
6034
6034
  return typeof value === "string" ? value : void 0;
6035
6035
  }
6036
+ function labelOf(error, { isBug, isWait, style }) {
6037
+ if (isWait(error)) return style.yellow("wait:");
6038
+ return style.red(isBug(error) ? "internal error:" : "error:");
6039
+ }
6036
6040
  function errorLines(error, format) {
6037
6041
  const fields = fieldsOf$1(error, format);
6038
6042
  const blocks = [printedDetail(error, format), debugTrace(error, format)].filter((block) => block !== void 0);
6039
6043
  const { style } = format;
6040
- const label = style.red(format.isBug(error) ? "internal error:" : "error:");
6044
+ const label = labelOf(error, format);
6041
6045
  const between = blocks.map((block) => `${block}\n`).join("");
6042
6046
  const runs = (fields.next ?? []).map((line) => `${style.cyan("run:")} ${line}\n`).join("");
6043
6047
  return `${label} ${fields.message}\n${between}${style.cyan("hint:")} ${fields.hint}\n${runs}`;
@@ -7085,9 +7089,9 @@ function secretRows(secrets, commands) {
7085
7089
  /** The bundler's stamp, absent when the CLI runs unbundled. Read per call, so a test can define it. */
7086
7090
  function buildStamp() {
7087
7091
  return {
7088
- "sha": "71583a7",
7092
+ "sha": "8dffdc4",
7089
7093
  "dirty": false,
7090
- "builtAt": "2026-11-03T06:40:47.000Z"
7094
+ "builtAt": "2026-11-05T10:26:41.000Z"
7091
7095
  };
7092
7096
  }
7093
7097
  /** The first line of `--version`: the version, and the commit and time of a stamped build. */
@@ -7237,6 +7241,16 @@ function installCommandLine(registry, version = "latest") {
7237
7241
  ...registry.flags
7238
7242
  ].join(" ");
7239
7243
  }
7244
+ /** The line that sets a Global install up, run before the CLI is on the machine. */
7245
+ function setupCommandLine({ package: name, flags }) {
7246
+ return [
7247
+ "npx",
7248
+ "-y",
7249
+ ...flags,
7250
+ `${name}@latest`,
7251
+ "setup"
7252
+ ].join(" ");
7253
+ }
7240
7254
  function npmOf(ctx, windows) {
7241
7255
  const beside = ctx.node === void 0 ? void 0 : join(dirname(ctx.node), windows ? "npm.cmd" : "npm");
7242
7256
  if (beside === void 0 || !existsSync(beside)) return {
@@ -7280,13 +7294,13 @@ function failureOf(call, answer, ctx) {
7280
7294
  const code = NPM_CODE.exec(answer.stderr)?.[1];
7281
7295
  const output = tail$1(answer.stderr);
7282
7296
  const next = [ctx.rerun];
7283
- if (code !== void 0 && NETWORK_CODES.has(code)) return new CliError({
7297
+ if (code !== void 0 && NETWORK_CODES.has(code)) return runtimeError({
7284
7298
  code: REGISTRY_UNREACHABLE,
7285
7299
  message: `npm could not reach ${registry} (${code})${output}`,
7286
7300
  hint: `${unreachableHint}, then ${RUN_AGAIN}`,
7287
7301
  next
7288
7302
  });
7289
- if (code !== void 0 && AUTH_CODES.has(code)) return new CliError({
7303
+ if (code !== void 0 && AUTH_CODES.has(code)) return runtimeError({
7290
7304
  code: REGISTRY_AUTH,
7291
7305
  message: `${registry} refused npm's credentials (${code})${output}`,
7292
7306
  hint: authHint(code, ctx),
@@ -7299,7 +7313,7 @@ function failureOf(call, answer, ctx) {
7299
7313
  hint: `fix what npm reports, then ${RUN_AGAIN} with the first Next step, or install it yourself with the second`,
7300
7314
  next: [...next, wholeLine(installCommandLine(ctx.registry))]
7301
7315
  });
7302
- return new CliError({
7316
+ return runtimeError({
7303
7317
  code: NPM_FAILED,
7304
7318
  message: `npm ${call} failed with exit code ${answer.exitCode}${output}`,
7305
7319
  hint: `fix what npm reports, then ${RUN_AGAIN}`,
@@ -7623,183 +7637,6 @@ function dependencyInstall(cli, install) {
7623
7637
  };
7624
7638
  }
7625
7639
  //#endregion
7626
- //#region ../agent-cli/dist/built-ins/skill/hub.js
7627
- const SKILL_DIRS = [[".claude", "skills"], [".agents", "skills"]];
7628
- const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
7629
- const METADATA_VERSION = /^metadata:[ \t]*\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+version:[ \t]*["']?([^"'\s]+)["']?[ \t]*$/m;
7630
- /** The version a SKILL.md's frontmatter `metadata.version` states. */
7631
- function skillVersionIn(skill) {
7632
- const frontmatter = FRONTMATTER.exec(skill)?.[1];
7633
- return frontmatter === void 0 ? void 0 : METADATA_VERSION.exec(`${frontmatter}\n`)?.[1];
7634
- }
7635
- function homeOf(env) {
7636
- return env.HOME ?? env.USERPROFILE ?? homedir();
7637
- }
7638
- async function copyAt(path, version) {
7639
- let skill;
7640
- try {
7641
- skill = await readFile(join(path, "SKILL.md"), "utf8");
7642
- } catch (error) {
7643
- if (isMissingFile(error)) return;
7644
- return {
7645
- path,
7646
- version: void 0,
7647
- state: "unreadable"
7648
- };
7649
- }
7650
- const stated = skillVersionIn(skill);
7651
- if (stated === void 0 || !isVersion(stated) || !isVersion(version)) return {
7652
- path,
7653
- version: stated,
7654
- state: "unversioned"
7655
- };
7656
- return {
7657
- path,
7658
- version: stated,
7659
- state: standingOf$1(stated, version)
7660
- };
7661
- }
7662
- async function skillCopies(cliName, version, { cwd, env }) {
7663
- const paths = [cwd, homeOf(env)].flatMap((root) => SKILL_DIRS.map((dir) => join(root, ...dir, cliName)));
7664
- const seen = /* @__PURE__ */ new Set();
7665
- const copies = [];
7666
- for (const path of paths) {
7667
- const copy = await copyAt(path, version);
7668
- const real = copy === void 0 ? void 0 : await realpath(path);
7669
- if (copy !== void 0 && real !== void 0 && !seen.has(real)) {
7670
- seen.add(real);
7671
- copies.push(copy);
7672
- }
7673
- }
7674
- return copies;
7675
- }
7676
- const has = (copies, wanted) => copies.some(({ state }) => state === wanted);
7677
- function addLineOf(cliName, from) {
7678
- return "add" in from ? from.add : `npx -y skills add ${from.source} --skill ${cliName} -g`;
7679
- }
7680
- function fixFor(cliName, copies, from) {
7681
- const update = `npx -y skills update ${cliName}`;
7682
- const add = from === void 0 ? void 0 : addLineOf(cliName, from);
7683
- if (copies.length === 0) return add;
7684
- if (has(copies, "unversioned") && add !== void 0) return has(copies, "behind") ? `${update} && ${add}` : add;
7685
- return has(copies, "behind") || has(copies, "unversioned") ? update : void 0;
7686
- }
7687
- const SKILL_STATE_NOTES = {
7688
- current: "the same as this build",
7689
- behind: "older than this build",
7690
- ahead: "newer than this build",
7691
- unversioned: "states no metadata.version",
7692
- unreadable: "could not be read"
7693
- };
7694
- function describeCopy({ path, version, state }) {
7695
- return `${path}${version === void 0 ? "" : ` ${version}`} (${SKILL_STATE_NOTES[state]})`;
7696
- }
7697
- /** A skill on a Skill Hub, which `from` says how to install; without it, no line adds it. */
7698
- function hubSkill(cli, from) {
7699
- const findingOf = (copies) => {
7700
- const fix = fixFor(cli.name, copies, from);
7701
- const message = copies.length === 0 ? `no copy of the ${cli.name} skill is installed` : copies.map(describeCopy).join("; ");
7702
- return fix === void 0 ? {
7703
- state: "ok",
7704
- message
7705
- } : {
7706
- state: "ok",
7707
- message,
7708
- fix
7709
- };
7710
- };
7711
- return {
7712
- kind: "hub",
7713
- builtIns: {},
7714
- rows: [{
7715
- name: "skill",
7716
- advisory: true,
7717
- run: async (ctx) => findingOf(await skillCopies(cli.name, cli.version, ctx))
7718
- }],
7719
- notice: async () => {},
7720
- differing: async (place) => (await skillCopies(cli.name, cli.version, place)).filter(({ state }) => state !== "current" && state !== "unreadable").map(describeCopy),
7721
- nextAt: async (version, ctx) => {
7722
- const copies = await skillCopies(cli.name, version, ctx);
7723
- const fix = fixFor(cli.name, copies, from);
7724
- return fix === void 0 ? [] : [fix];
7725
- }
7726
- };
7727
- }
7728
- //#endregion
7729
- //#region ../agent-cli/dist/built-ins/agents-md.js
7730
- function markersOf(name) {
7731
- return {
7732
- begin: `<!-- BEGIN:${name} -->`,
7733
- end: `<!-- END:${name} -->`
7734
- };
7735
- }
7736
- /** `text` with the CLI's Agents section between its markers, appended when they are missing; nothing else changes. */
7737
- function withSection({ name, text: section }, text) {
7738
- const { begin, end } = markersOf(name);
7739
- const whole = `${begin}\n${section}\n${end}`;
7740
- const start = text.indexOf(begin);
7741
- const stop = start === -1 ? -1 : text.indexOf(end, start);
7742
- if (stop !== -1) return `${text.slice(0, start)}${whole}${text.slice(stop + end.length)}`;
7743
- if (text === "") return `${whole}\n`;
7744
- return `${text}${text.endsWith("\n") ? "" : "\n"}\n${whole}\n`;
7745
- }
7746
- async function agentsMdAt(agents, root) {
7747
- const path = join(root, "AGENTS.md");
7748
- const existing = await orMissing(readFile(path, "utf8"));
7749
- if (existing === void 0) return {
7750
- path,
7751
- agents,
7752
- change: "create"
7753
- };
7754
- if (withSection(agents, existing) === existing) return {
7755
- path,
7756
- agents,
7757
- change: null
7758
- };
7759
- return {
7760
- path,
7761
- agents,
7762
- change: existing.includes(markersOf(agents.name).begin) ? "rewrite" : "add"
7763
- };
7764
- }
7765
- async function handEditsAt(root) {
7766
- const claudeMd = join(root, "CLAUDE.md");
7767
- const text = await orMissing(readFile(claudeMd, "utf8"));
7768
- if (text === void 0 || text.split(/\r?\n/).some((line) => line.trim() === "@AGENTS.md")) return [];
7769
- return [`add a line \`@AGENTS.md\` to ${claudeMd}, so Claude Code reads AGENTS.md`];
7770
- }
7771
- function agentsRow(agents, install) {
7772
- const { name } = agents;
7773
- return {
7774
- name: "agents",
7775
- advisory: true,
7776
- run: async (ctx) => {
7777
- const root = await projectRootOf(ctx.cwd);
7778
- const { path, change } = await agentsMdAt(agents, root);
7779
- const fix = `${install.runAs} skill install`;
7780
- if (change === "rewrite") return {
7781
- state: "problem",
7782
- message: `${path} holds a ${name} Agents section other than the one ${fix} writes`,
7783
- fix
7784
- };
7785
- if (change !== null) return {
7786
- state: "problem",
7787
- message: `${path} holds no ${name} Agents section, so sessions in this repo do not know ${name}`,
7788
- fix
7789
- };
7790
- const holds = `${path} holds the ${name} Agents section`;
7791
- const [edit] = await handEditsAt(root);
7792
- return edit === void 0 ? {
7793
- state: "ok",
7794
- message: holds
7795
- } : {
7796
- state: "problem",
7797
- message: `${holds}; ${edit}`
7798
- };
7799
- }
7800
- };
7801
- }
7802
- //#endregion
7803
7640
  //#region ../agent-cli/dist/context/errno.js
7804
7641
  const PERMISSION_DENIED = "PERMISSION_DENIED";
7805
7642
  const DISK_FULL = "DISK_FULL";
@@ -7862,565 +7699,84 @@ function classifyErrno(error, context) {
7862
7699
  }
7863
7700
  }
7864
7701
  //#endregion
7865
- //#region ../agent-cli/dist/context/files.js
7866
- const TEMP_FILE = /^\..+\.agent-cli-(\d+)\.tmp$/;
7867
- /** Where the Runtime writes `path` before moving it into place; a dead writer's is swept. */
7868
- function tempPathOf(path) {
7869
- return join(dirname(path), `.${basename(path)}.agent-cli-${process.pid}.tmp`);
7702
+ //#region ../agent-cli/dist/context/interrupts.js
7703
+ const INTERRUPTED = "INTERRUPTED";
7704
+ const DEADLINE_EXCEEDED = "DEADLINE_EXCEEDED";
7705
+ /** How long an interrupted Command may take to settle before the run ends without it. */
7706
+ const GRACE_MS = 3e3;
7707
+ const GRACE_OVER = Symbol("grace over");
7708
+ function interruptedError(rerun) {
7709
+ return new CliError({
7710
+ code: INTERRUPTED,
7711
+ message: "interrupted before it finished",
7712
+ hint: "to finish it, run the Next step, unless it was stopped on purpose; if a tool timeout stopped it, give it longer",
7713
+ next: [rerun]
7714
+ });
7870
7715
  }
7871
- function isAlive(pid) {
7872
- try {
7873
- process.kill(pid, 0);
7874
- return true;
7875
- } catch (error) {
7876
- return errnoOf(error) === "EPERM";
7877
- }
7716
+ const BOUND_WORDS = {
7717
+ deadline: "deadline",
7718
+ ignoreStop: "timeout"
7719
+ };
7720
+ function waitHint(bound) {
7721
+ 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";
7878
7722
  }
7879
- async function sweep(folder) {
7880
- const dead = (await readdir(folder)).filter((name) => {
7881
- const pid = TEMP_FILE.exec(name)?.[1];
7882
- return pid !== void 0 && !isAlive(Number(pid));
7723
+ function deadlineError(commandLine, ms, advice, bound = "deadline") {
7724
+ return new CliError({
7725
+ code: DEADLINE_EXCEEDED,
7726
+ message: `${commandLine} ran past its ${ms} ms ${BOUND_WORDS[bound]}`,
7727
+ ...advice
7883
7728
  });
7884
- await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
7885
- }
7886
- /** The file a symlink at `path` points to, so a replacing write keeps the link. */
7887
- async function targetOf$1(path) {
7888
- try {
7889
- return await realpath(path);
7890
- } catch (error) {
7891
- if (isMissingFile(error)) return path;
7892
- throw error;
7893
- }
7894
7729
  }
7895
- async function writeWithMode(path, data, mode) {
7896
- if (mode === void 0) {
7897
- await writeFile(path, data);
7898
- return;
7730
+ /** One Command run's cancellation: its signal, its deadlines and the steps it must finish. */
7731
+ var RunControl = class {
7732
+ signal;
7733
+ #controller = new AbortController();
7734
+ #commandLine;
7735
+ #rerun;
7736
+ #outer;
7737
+ #onOuterAbort = () => this.#controller.abort(interruptedError(this.#rerun));
7738
+ /** Each running must-finish step, tracked until it settles, with its name. */
7739
+ #steps = /* @__PURE__ */ new Map();
7740
+ #progress;
7741
+ #clock;
7742
+ #disposed = new AbortController();
7743
+ /** The reasons this run's deadlines aborted with, to tell them in a Command's error. */
7744
+ #abortReasons = /* @__PURE__ */ new WeakSet();
7745
+ #graceCancel = new AbortController();
7746
+ #endGrace = () => {};
7747
+ #graceOver = new Promise((resolve) => {
7748
+ this.#endGrace = resolve;
7749
+ });
7750
+ constructor({ commandLine, rerun, outer, clock, progress, runDeadline }) {
7751
+ this.#commandLine = commandLine;
7752
+ this.#rerun = rerun;
7753
+ this.#outer = outer;
7754
+ this.#clock = clock;
7755
+ this.#progress = progress;
7756
+ this.signal = this.#controller.signal;
7757
+ this.signal.addEventListener("abort", () => this.#onStop(), { once: true });
7758
+ if (outer?.aborted === true) this.#onOuterAbort();
7759
+ else outer?.addEventListener("abort", this.#onOuterAbort, { once: true });
7760
+ if (runDeadline !== void 0) {
7761
+ const { ms, advice } = runDeadline;
7762
+ after(clock, ms, this.#disposed.signal, () => this.#controller.abort(deadlineError(commandLine, ms, advice())));
7763
+ }
7899
7764
  }
7900
- await writeFile(path, data, { mode });
7901
- await chmod(path, mode);
7902
- }
7903
- /** What `link` answers on a file system without hard links, such as exFAT. */
7904
- const NO_HARD_LINKS = /* @__PURE__ */ new Set([
7905
- "EPERM",
7906
- "ENOTSUP",
7907
- "EXDEV"
7908
- ]);
7909
- /** A link fails atomically when `path` is taken, so no reader ever sees half a file there. */
7910
- async function linkNew(temp, path, data, mode) {
7911
- try {
7912
- await link(temp, path);
7913
- } catch (error) {
7914
- if (!NO_HARD_LINKS.has(errnoOf(error))) throw error;
7915
- await createNew(path, data, mode);
7765
+ deadline(ms, options = {}) {
7766
+ return options.ignoreStop === true ? this.#abortsAfter(ms, "ignoreStop") : AbortSignal.any([this.signal, this.#abortsAfter(ms, "deadline")]);
7916
7767
  }
7917
- }
7918
- /** A file this call created and could not finish is removed, so a rerun does not find half of it. */
7919
- async function createNew(path, data, mode) {
7920
- const handle = await open(path, "wx", mode);
7921
- try {
7922
- await handle.writeFile(data);
7923
- if (mode !== void 0) await handle.chmod(mode);
7924
- } catch (error) {
7925
- await handle.close();
7926
- await rm(path, { force: true });
7927
- throw error;
7928
- }
7929
- await handle.close();
7930
- }
7931
- const RENAME_ATTEMPTS = 10;
7932
- const RENAME_BACKOFF_MS = 10;
7933
- const HELD_BY_ANOTHER_PROGRAM = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
7934
- async function renameRetrying(from, to) {
7935
- for (let attempt = 1;; attempt++) try {
7936
- await rename(from, to);
7937
- return;
7938
- } catch (error) {
7939
- if (!(process.platform === "win32" && HELD_BY_ANOTHER_PROGRAM.has(errnoOf(error))) || attempt === RENAME_ATTEMPTS) throw error;
7940
- await setTimeout$1(attempt * RENAME_BACKOFF_MS);
7941
- }
7942
- }
7943
- /** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
7944
- function realFiles() {
7945
- const swept = /* @__PURE__ */ new Set();
7946
- return {
7947
- exists: async (path) => {
7948
- try {
7949
- await access(path);
7950
- return true;
7951
- } catch (error) {
7952
- if (isNothingThere(error)) return false;
7953
- throw error;
7954
- }
7955
- },
7956
- write: async (path, data, options = {}) => {
7957
- await mkdir(dirname(path), { recursive: true });
7958
- const target = options.exclusive === true ? path : await targetOf$1(path);
7959
- const folder = dirname(target);
7960
- if (!swept.has(folder)) {
7961
- await sweep(folder);
7962
- swept.add(folder);
7963
- }
7964
- const temp = tempPathOf(target);
7965
- try {
7966
- await writeWithMode(temp, data, options.mode);
7967
- if (options.exclusive === true) {
7968
- await linkNew(temp, target, data, options.mode);
7969
- await rm(temp);
7970
- } else await renameRetrying(temp, target);
7971
- } catch (error) {
7972
- await rm(temp, { force: true });
7973
- throw error;
7974
- }
7975
- },
7976
- read: (path) => readFile(path),
7977
- list: (folder) => readdir(folder),
7978
- remove: (path) => rm(path, {
7979
- recursive: true,
7980
- force: true
7981
- }),
7982
- mkdir: async (path, options) => {
7983
- const mode = options?.mode;
7984
- if (options?.exclusive !== true) {
7985
- await mkdir(path, {
7986
- recursive: true,
7987
- mode
7988
- });
7989
- return;
7990
- }
7991
- await mkdir(dirname(path), {
7992
- recursive: true,
7993
- mode
7994
- });
7995
- await mkdir(path, { mode });
7996
- },
7997
- stat: async (path) => {
7998
- try {
7999
- const { size, dev, ino } = await stat(path, { bigint: true });
8000
- return {
8001
- size: Number(size),
8002
- id: `${dev}:${ino}`
8003
- };
8004
- } catch (error) {
8005
- if (isNothingThere(error)) return void 0;
8006
- throw error;
8007
- }
8008
- }
8009
- };
8010
- }
8011
- /**
8012
- * `files` resolving paths against `cwd`, its failures classified by errno; `list` drops the
8013
- * Runtime's temp files and sorts.
8014
- */
8015
- function classifiedFiles(files, { cwd, fileError }) {
8016
- async function at(access, path, work) {
8017
- const target = resolve(cwd, path);
8018
- try {
8019
- return await work(target);
8020
- } catch (error) {
8021
- throw fileError(error, {
8022
- path: target,
8023
- access
8024
- });
8025
- }
8026
- }
8027
- return {
8028
- exists: (path) => at("read", path, (target) => files.exists(target)),
8029
- write: (path, data, options) => at("write", path, (target) => files.write(target, data, options)),
8030
- read: (path) => at("read", path, (target) => files.read(target)),
8031
- list: (folder) => at("read", folder, async (target) => (await files.list(target)).filter((name) => !TEMP_FILE.test(name)).sort()),
8032
- remove: (path) => at("write", path, (target) => files.remove(target)),
8033
- mkdir: (path, options) => at("write", path, (target) => files.mkdir(target, options)),
8034
- stat: (path) => at("read", path, (target) => files.stat(target))
8035
- };
8036
- }
8037
- //#endregion
8038
- //#region ../agent-cli/dist/context/outputs.js
8039
- const OUTPUT_EXISTS = "OUTPUT_EXISTS";
8040
- /** Refuses a run that would overwrite existing outputs unless the caller passed `--force`. */
8041
- async function checkOutputs({ rerun, commandLine, force, files }, outputs) {
8042
- if (outputs === void 0 || force) return;
8043
- const found = await Promise.all(outputs.map((path) => files.exists(path)));
8044
- const existing = outputs.filter((_path, index) => found[index]);
8045
- if (existing.length === 0) return;
8046
- const them = existing.length === 1 ? "it" : "them";
8047
- throw new CliError({
8048
- code: OUTPUT_EXISTS,
8049
- message: `\`${commandLine}\` would overwrite ${existing.join(", ")}`,
8050
- hint: `once the user agrees to replace ${them}, run the Next step`,
8051
- next: [rerun({ add: [FORCE_FLAG.flag] })]
8052
- });
8053
- }
8054
- /** Guards the writes to declared outputs of `files`, whose paths resolve against `cwd`. */
8055
- function outputsGuard(files, cwd) {
8056
- const unwritten = /* @__PURE__ */ new Set();
8057
- let forceStep = [];
8058
- const exclusiveWrite = async (path, data, options) => {
8059
- const target = resolve(cwd, path);
8060
- unwritten.delete(target);
8061
- try {
8062
- await files.write(path, data, {
8063
- ...options,
8064
- exclusive: true
8065
- });
8066
- } catch (error) {
8067
- unwritten.add(target);
8068
- if (!(error instanceof CliError) || error.code !== "ALREADY_EXISTS") throw error;
8069
- throw new CliError({
8070
- code: ALREADY_EXISTS,
8071
- message: error.message,
8072
- hint: "once the user agrees to replace it, run the Next step",
8073
- next: [forceStep]
8074
- }, { cause: error });
8075
- }
8076
- };
8077
- return {
8078
- files: {
8079
- ...files,
8080
- write: (path, data, options) => unwritten.has(resolve(cwd, path)) ? exclusiveWrite(path, data, options) : files.write(path, data, options)
8081
- },
8082
- guard: (outputs, step) => {
8083
- forceStep = step;
8084
- for (const path of outputs) unwritten.add(resolve(cwd, path));
8085
- }
8086
- };
8087
- }
8088
- /**
8089
- * Creates each output folder and a temp file in it, so a sandbox denial fails before `apply`
8090
- * spends anything. The temp file's name is the Runtime's, so a crashed run's leftover is swept.
8091
- */
8092
- async function probeOutputs({ files, cwd, fileError }, outputs) {
8093
- const folders = new Set(outputs.map((path) => dirname(resolve(cwd, path))));
8094
- await Promise.all([...folders].map(async (folder) => {
8095
- const probe = tempPathOf(join(folder, "outputs-probe"));
8096
- try {
8097
- await files.mkdir(folder);
8098
- await files.write(probe, "");
8099
- await files.remove(probe);
8100
- } catch (error) {
8101
- throw fileError(error, {
8102
- path: folder,
8103
- access: "write"
8104
- });
8105
- }
8106
- }));
8107
- }
8108
- //#endregion
8109
- //#region ../agent-cli/dist/built-ins/skill/install-command.js
8110
- /** The project root and the Skill copy under it, as `skill install` writes it. */
8111
- async function skillCopyOf(cli, cwd) {
8112
- const root = await projectRootOf(cwd);
8113
- return {
8114
- root,
8115
- copy: join(root, ".claude", "skills", cli.name)
8116
- };
8117
- }
8118
- /** Every file under `dir`, relative to it and sorted; none when `dir` is missing. */
8119
- async function filesIn(dir) {
8120
- return (await orMissing(readdir(dir, {
8121
- recursive: true,
8122
- withFileTypes: true
8123
- })) ?? []).filter((entry) => entry.isFile()).map((entry) => relative(dir, join(entry.parentPath, entry.name))).sort();
8124
- }
8125
- /** The versions of the package's skill in `source` and of its copy in `copy`. */
8126
- async function skillVersionsOf(source, copy) {
8127
- const [shipped, installed] = await Promise.all([readFile(join(source, "SKILL.md"), "utf8"), orMissing(readFile(join(copy, "SKILL.md"), "utf8"))]);
8128
- const packageVersion = skillVersionIn(shipped) ?? null;
8129
- const copyVersion = installed === void 0 ? void 0 : skillVersionIn(installed) ?? null;
8130
- return {
8131
- packageVersion,
8132
- copyVersion,
8133
- same: copyVersion === packageVersion
8134
- };
8135
- }
8136
- const installSchema = object({
8137
- copy: string(),
8138
- written: array(string()),
8139
- removed: array(string()),
8140
- agentsMd: object({
8141
- path: string(),
8142
- changed: boolean()
8143
- }).nullable(),
8144
- handEdits: array(string())
8145
- });
8146
- async function sameContent(left, right) {
8147
- const [a, b] = await Promise.all([readFile(left), readFile(right)]);
8148
- return a.equals(b);
8149
- }
8150
- async function planOf(cli, folder, agents, cwd, packageRoot) {
8151
- const source = join(packageRoot, folder);
8152
- const { root, copy } = await skillCopyOf(cli, cwd);
8153
- const [shipped, present] = await Promise.all([filesIn(source), filesIn(copy)]);
8154
- const differs = await Promise.all(shipped.map(async (file) => present.includes(file) && !await sameContent(join(source, file), join(copy, file))));
8155
- return {
8156
- source,
8157
- copy,
8158
- write: shipped.filter((file) => !present.includes(file)),
8159
- replace: shipped.filter((_file, index) => differs[index]),
8160
- remove: present.filter((file) => !shipped.includes(file)),
8161
- sameVersion: (await skillVersionsOf(source, copy)).same,
8162
- agentsMd: agents === void 0 ? null : await agentsMdAt(agents, root),
8163
- handEdits: agents === void 0 ? [] : await handEditsAt(root)
8164
- };
8165
- }
8166
- const SECTION_VERBS = {
8167
- create: "create",
8168
- add: "add the Agents section to",
8169
- rewrite: "rewrite the Agents section in"
8170
- };
8171
- function renderPlan(cli, plan) {
8172
- const files = [
8173
- ...plan.write.map((file) => ` write ${file}`),
8174
- ...plan.replace.map((file) => ` replace ${file}`),
8175
- ...plan.remove.map((file) => ` remove ${file}`)
8176
- ];
8177
- const { agentsMd } = plan;
8178
- return [
8179
- files.length === 0 ? `the ${cli.name} skill at ${plan.copy} already matches the package` : `would copy the ${cli.name} skill to ${plan.copy}:`,
8180
- ...files,
8181
- ...agentsMd === null ? [] : [agentsMd.change === null ? `${agentsMd.path} already holds the ${cli.name} Agents section` : `would ${SECTION_VERBS[agentsMd.change]} ${agentsMd.path}:\n${agentsMd.agents.text}`],
8182
- ...plan.handEdits.map((edit) => `by hand: ${edit}`)
8183
- ].join("\n");
8184
- }
8185
- function renderInstalled(cli, data) {
8186
- const { written, removed, agentsMd } = data;
8187
- return [
8188
- written.length + removed.length === 0 ? `the ${cli.name} skill at ${data.copy} already matches the package` : `copied the ${cli.name} skill to ${data.copy}: wrote ${written.length} files, removed ${removed.length}`,
8189
- ...agentsMd === null ? [] : [agentsMd.changed ? `wrote the ${cli.name} Agents section in ${agentsMd.path}` : `${agentsMd.path} already holds the ${cli.name} Agents section`],
8190
- ...data.handEdits.map((edit) => `by hand: ${edit}`)
8191
- ].join("\n");
8192
- }
8193
- function editedRemovals(plan, ctx) {
8194
- const them = plan.remove.length === 1 ? "it" : "them";
8195
- return new CliError({
8196
- code: OUTPUT_EXISTS,
8197
- message: `skill install would remove ${plan.remove.map((file) => join(plan.copy, file)).join(", ")}`,
8198
- hint: `once the user agrees to remove ${them}, run the Next step`,
8199
- next: [ctx.rerun({ add: [FORCE_FLAG.flag] })]
8200
- });
8201
- }
8202
- /** The built-in `skill install`, for a CLI whose skill ships in its package. */
8203
- function skillInstallCommand(cli, folder, agents) {
8204
- return defineCommand$1({
8205
- 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",
8206
- examples: [{
8207
- command: `${cli.name} skill install`,
8208
- note: "install the skill this version ships"
8209
- }, {
8210
- command: `${cli.name} skill install --dry-run`,
8211
- note: "show what it would change"
8212
- }],
8213
- effect: "write",
8214
- output: installSchema,
8215
- force: "replace or remove hand-edited files of a skill copy at the package's version",
8216
- plan: (_input, ctx) => planOf(cli, folder, agents, ctx.cwd, ctx.packageRoot),
8217
- outputs: (plan) => plan.sameVersion ? [...plan.write, ...plan.replace].map((file) => join(plan.copy, file)) : [],
8218
- apply: async (plan, ctx) => {
8219
- if (plan.sameVersion && plan.remove.length > 0 && !ctx.force) throw editedRemovals(plan, ctx);
8220
- for (const file of [...plan.write, ...plan.replace]) await ctx.files.write(join(plan.copy, file), await readFile(join(plan.source, file)));
8221
- for (const file of plan.remove) await ctx.files.remove(join(plan.copy, file));
8222
- const { agentsMd } = plan;
8223
- if (agentsMd !== null && agentsMd.change !== null) {
8224
- const existing = await orMissing(readFile(agentsMd.path, "utf8")) ?? "";
8225
- await ctx.files.write(agentsMd.path, withSection(agentsMd.agents, existing));
8226
- }
8227
- return {
8228
- copy: plan.copy,
8229
- written: [...plan.write, ...plan.replace].sort(),
8230
- removed: [...plan.remove],
8231
- agentsMd: agentsMd === null ? null : {
8232
- path: agentsMd.path,
8233
- changed: agentsMd.change !== null
8234
- },
8235
- handEdits: [...plan.handEdits]
8236
- };
8237
- },
8238
- render: (data) => renderInstalled(cli, data),
8239
- renderPlan: (plan) => renderPlan(cli, plan)
8240
- });
8241
- }
8242
- //#endregion
8243
- //#region ../agent-cli/dist/built-ins/skill/packaged.js
8244
- async function personalCopyOf(name, copy, ctx) {
8245
- const home = join(homeOf(ctx.env), ".claude", "skills", name);
8246
- const real = await orMissing(realpath(home));
8247
- return real === void 0 || real === await orMissing(realpath(copy)) ? void 0 : home;
8248
- }
8249
- /** What the `skill` row of a Packaged skill carries under `--json` beside its message. */
8250
- const packagedSkillDetailsSchema = object({
8251
- copy: string(),
8252
- copyVersion: string().nullable(),
8253
- packageVersion: string().nullable(),
8254
- /** A personal copy, which Claude Code prefers over the project's. */
8255
- home: string().optional()
8256
- });
8257
- function versionLabel(version) {
8258
- return version ?? "(no metadata.version)";
8259
- }
8260
- function compared(name, { copy, copyVersion, packageVersion }, same) {
8261
- const stated = `${copy} ${versionLabel(copyVersion)}`;
8262
- return same ? `${stated}, the version ${name} ships` : `${stated} differs from the ${versionLabel(packageVersion)} that ${name} ships`;
8263
- }
8264
- /** The project root's copy against the package's, and any under home. */
8265
- async function findingOf(cli, folder, install, ctx) {
8266
- const { copy } = await skillCopyOf(cli, ctx.cwd);
8267
- const versions = await skillVersionsOf(join(ctx.packageRoot, folder), copy);
8268
- const { packageVersion } = versions;
8269
- const copyVersion = versions.copyVersion ?? null;
8270
- const home = await personalCopyOf(cli.name, copy, ctx);
8271
- const skill = {
8272
- copy,
8273
- copyVersion,
8274
- packageVersion,
8275
- ...home === void 0 ? {} : { home }
8276
- };
8277
- const head = versions.copyVersion === void 0 ? `no copy of the ${cli.name} skill at ${copy}` : compared(cli.name, skill, versions.same);
8278
- const message = home === void 0 ? head : `${head}; remove ${home}: Claude Code prefers a personal skill over the project's`;
8279
- if (!versions.same) return {
8280
- state: "problem",
8281
- message,
8282
- fix: `${install.runAs} skill install`,
8283
- skill
8284
- };
8285
- return home === void 0 ? {
8286
- state: "ok",
8287
- message,
8288
- skill
8289
- } : {
8290
- state: "problem",
8291
- message,
8292
- skill
8293
- };
8294
- }
8295
- async function notice(cli, folder, install, { builtIn, bin }, ctx) {
8296
- if (builtIn || bin === void 0 || cli.commands === void 0) return;
8297
- let finding;
8298
- try {
8299
- finding = await findingOf(cli, folder, install, ctx);
8300
- } catch (error) {
8301
- if (errnoOf(error) === void 0) throw error;
8302
- ctx.notice(`could not check the ${cli.name} skill: ${messageOf(error)}`);
8303
- return;
8304
- }
8305
- if (finding.state !== "problem") return;
8306
- ctx.notice(finding.fix === void 0 ? finding.message : `${finding.message}; run \`${finding.fix}\``);
8307
- }
8308
- /** A skill shipped in the package at `packaged`, which `skill install` copies to the project root. */
8309
- function packagedSkill(cli, { packaged: folder, agents: text }, install) {
8310
- const agents = text === void 0 ? void 0 : {
8311
- name: cli.name,
8312
- text
8313
- };
8314
- return {
8315
- kind: "packaged",
8316
- builtIns: { skill: { install: skillInstallCommand(cli, folder, agents) } },
8317
- rows: [{
8318
- name: "skill",
8319
- advisory: true,
8320
- run: (ctx) => findingOf(cli, folder, install, ctx)
8321
- }, ...agents === void 0 ? [] : [agentsRow(agents, install)]],
8322
- notice: (launch, ctx) => notice(cli, folder, install, launch, ctx),
8323
- differing: async ({ cwd, packageRoot }) => {
8324
- const { copy } = await skillCopyOf(cli, cwd);
8325
- const { copyVersion, packageVersion, same } = await skillVersionsOf(join(packageRoot, folder), copy);
8326
- return copyVersion === void 0 || same ? [] : [compared(cli.name, {
8327
- copy,
8328
- copyVersion,
8329
- packageVersion
8330
- }, false)];
8331
- },
8332
- nextAt: async (_version, ctx) => {
8333
- const { fix } = await findingOf(cli, folder, install, ctx);
8334
- return fix === void 0 ? [] : [fix];
8335
- }
8336
- };
8337
- }
8338
- //#endregion
8339
- //#region ../agent-cli/dist/built-ins/skill/skill.js
8340
- /** A Packaged skill, or else a Skill Hub's, also for a CLI that declares no `skill`. */
8341
- function skillOf$1(cli, install) {
8342
- const { skill } = cli;
8343
- return skill !== void 0 && "packaged" in skill ? packagedSkill(cli, skill, install) : hubSkill(cli, skill);
8344
- }
8345
- //#endregion
8346
- //#region ../agent-cli/dist/context/interrupts.js
8347
- const INTERRUPTED = "INTERRUPTED";
8348
- const DEADLINE_EXCEEDED = "DEADLINE_EXCEEDED";
8349
- /** How long an interrupted Command may take to settle before the run ends without it. */
8350
- const GRACE_MS = 3e3;
8351
- const GRACE_OVER = Symbol("grace over");
8352
- function interruptedError(rerun) {
8353
- return new CliError({
8354
- code: INTERRUPTED,
8355
- message: "interrupted before it finished",
8356
- hint: "to finish it, run the Next step, unless it was stopped on purpose; if a tool timeout stopped it, give it longer",
8357
- next: [rerun]
8358
- });
8359
- }
8360
- const BOUND_WORDS = {
8361
- deadline: "deadline",
8362
- ignoreStop: "timeout"
8363
- };
8364
- function waitHint(bound) {
8365
- 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";
8366
- }
8367
- function deadlineError(commandLine, ms, advice, bound = "deadline") {
8368
- return new CliError({
8369
- code: DEADLINE_EXCEEDED,
8370
- message: `${commandLine} ran past its ${ms} ms ${BOUND_WORDS[bound]}`,
8371
- ...advice
8372
- });
8373
- }
8374
- /** One Command run's cancellation: its signal, its deadlines and the steps it must finish. */
8375
- var RunControl = class {
8376
- signal;
8377
- #controller = new AbortController();
8378
- #commandLine;
8379
- #rerun;
8380
- #outer;
8381
- #onOuterAbort = () => this.#controller.abort(interruptedError(this.#rerun));
8382
- /** Each running must-finish step, tracked until it settles, with its name. */
8383
- #steps = /* @__PURE__ */ new Map();
8384
- #progress;
8385
- #clock;
8386
- #disposed = new AbortController();
8387
- /** The reasons this run's deadlines aborted with, to tell them in a Command's error. */
8388
- #abortReasons = /* @__PURE__ */ new WeakSet();
8389
- #graceCancel = new AbortController();
8390
- #endGrace = () => {};
8391
- #graceOver = new Promise((resolve) => {
8392
- this.#endGrace = resolve;
8393
- });
8394
- constructor({ commandLine, rerun, outer, clock, progress, runDeadline }) {
8395
- this.#commandLine = commandLine;
8396
- this.#rerun = rerun;
8397
- this.#outer = outer;
8398
- this.#clock = clock;
8399
- this.#progress = progress;
8400
- this.signal = this.#controller.signal;
8401
- this.signal.addEventListener("abort", () => this.#onStop(), { once: true });
8402
- if (outer?.aborted === true) this.#onOuterAbort();
8403
- else outer?.addEventListener("abort", this.#onOuterAbort, { once: true });
8404
- if (runDeadline !== void 0) {
8405
- const { ms, advice } = runDeadline;
8406
- after(clock, ms, this.#disposed.signal, () => this.#controller.abort(deadlineError(commandLine, ms, advice())));
8407
- }
8408
- }
8409
- deadline(ms, options = {}) {
8410
- return options.ignoreStop === true ? this.#abortsAfter(ms, "ignoreStop") : AbortSignal.any([this.signal, this.#abortsAfter(ms, "deadline")]);
8411
- }
8412
- #abortsAfter(ms, bound) {
8413
- const controller = new AbortController();
8414
- after(this.#clock, ms, this.#disposed.signal, () => {
8415
- const advice = {
8416
- hint: waitHint(bound),
8417
- next: [this.#rerun]
8418
- };
8419
- const reason = deadlineError(this.#commandLine, ms, advice, bound);
8420
- this.#abortReasons.add(reason);
8421
- controller.abort(reason);
8422
- });
8423
- return controller.signal;
7768
+ #abortsAfter(ms, bound) {
7769
+ const controller = new AbortController();
7770
+ after(this.#clock, ms, this.#disposed.signal, () => {
7771
+ const advice = {
7772
+ hint: waitHint(bound),
7773
+ next: [this.#rerun]
7774
+ };
7775
+ const reason = deadlineError(this.#commandLine, ms, advice, bound);
7776
+ this.#abortReasons.add(reason);
7777
+ controller.abort(reason);
7778
+ });
7779
+ return controller.signal;
8424
7780
  }
8425
7781
  async runToEnd(name, work) {
8426
7782
  this.#graceCancel.abort();
@@ -8492,52 +7848,185 @@ const NOT_GLOBAL_INSTALL = "NOT_GLOBAL_INSTALL";
8492
7848
  function globalModulesDir(prefix) {
8493
7849
  return process.platform === "win32" ? join(prefix, "node_modules") : join(prefix, "lib", "node_modules");
8494
7850
  }
8495
- function notGlobal(ctx, message) {
8496
- return runtimeError({
8497
- code: NOT_GLOBAL_INSTALL,
8498
- message,
8499
- hint: "install it with npm by running the Next step, so its update Command can replace it",
8500
- next: [wholeLine(installCommandLine(ctx.registry))]
7851
+ function notGlobal(ctx, message) {
7852
+ return runtimeError({
7853
+ code: NOT_GLOBAL_INSTALL,
7854
+ message,
7855
+ hint: "install it by running the Next step, so its update Command can replace it",
7856
+ next: [wholeLine(setupCommandLine(ctx.registry))]
7857
+ });
7858
+ }
7859
+ /**
7860
+ * The global npm install this copy runs from, found by asking npm for its global prefix.
7861
+ * `bin` is the script that started the CLI; throws `NOT_GLOBAL_INSTALL` when it is not inside that install.
7862
+ */
7863
+ async function globalInstallOf(bin, ctx) {
7864
+ if (bin === void 0) throw notGlobal(ctx, `${ctx.cliName} runs from source, not from an npm install`);
7865
+ const prefix = await globalPrefix(ctx);
7866
+ const packageDir = join(globalModulesDir(prefix), ctx.registry.package);
7867
+ const installed = await orMissing(realpath(packageDir));
7868
+ const here = await orMissing(realpath(bin)) ?? bin;
7869
+ if (installed === void 0 || !isInside(installed, here)) throw notGlobal(ctx, `${ctx.cliName} runs from ${here}, not from npm's global install at ${packageDir}`);
7870
+ if ((await lstat(packageDir)).isSymbolicLink()) throw notGlobal(ctx, `${ctx.cliName} runs from a link to ${installed} (npm link), not from an npm install`);
7871
+ return {
7872
+ prefix,
7873
+ packageDir
7874
+ };
7875
+ }
7876
+ const NPMJS = "https://registry.npmjs.org/";
7877
+ /** The flag that points npm at the CLI's registry; none for npmjs, npm's default. */
7878
+ function registryFlags({ package: name, registry }) {
7879
+ if (registry === NPMJS) return [];
7880
+ const scope = scopeOf(name);
7881
+ return [scope === void 0 ? `--registry=${registry}` : `--${scope}:registry=${registry}`];
7882
+ }
7883
+ /** The line that sets a Global install up, run before the CLI is on the machine. */
7884
+ function setupLineOf(install) {
7885
+ return setupCommandLine(globalRegistry(install));
7886
+ }
7887
+ function globalRegistry(install) {
7888
+ const { package: name, registry } = install;
7889
+ return {
7890
+ package: name,
7891
+ label: registry,
7892
+ flags: registryFlags(install),
7893
+ unreachableHint: "check the network or VPN",
7894
+ loginHint: `log in to ${registry} (\`npm login --registry=${registry}\`) or set its token in ~/.npmrc`,
7895
+ missingHint: `check that ${name} is published on ${registry}`
7896
+ };
7897
+ }
7898
+ /** npm for a Global install, run in the working directory. */
7899
+ function globalNpm(cli, install, ctx, node) {
7900
+ return npmContext(cli, globalRegistry(install), ctx, node, ctx.cwd);
7901
+ }
7902
+ //#endregion
7903
+ //#region ../agent-cli/dist/built-ins/skill/sync.js
7904
+ /** Every file under `dir`, relative to it and sorted; none when `dir` is missing. */
7905
+ async function filesIn(dir) {
7906
+ return (await orMissing(readdir(dir, {
7907
+ recursive: true,
7908
+ withFileTypes: true
7909
+ })) ?? []).filter((entry) => entry.isFile()).map((entry) => relative(dir, join(entry.parentPath, entry.name))).sort();
7910
+ }
7911
+ async function sameContent(left, right) {
7912
+ const [a, b] = await Promise.all([readFile(left), readFile(right)]);
7913
+ return a.equals(b);
7914
+ }
7915
+ async function syncOf(source, copy) {
7916
+ const [shipped, present] = await Promise.all([filesIn(source), filesIn(copy)]);
7917
+ const differs = await Promise.all(shipped.map(async (file) => present.includes(file) && !await sameContent(join(source, file), join(copy, file))));
7918
+ return {
7919
+ source,
7920
+ copy,
7921
+ write: shipped.filter((file) => !present.includes(file)),
7922
+ replace: shipped.filter((_file, index) => differs[index]),
7923
+ remove: present.filter((file) => !shipped.includes(file))
7924
+ };
7925
+ }
7926
+ async function applySync(sync, ctx) {
7927
+ for (const file of [...sync.write, ...sync.replace]) await ctx.files.write(join(sync.copy, file), await readFile(join(sync.source, file)));
7928
+ for (const file of sync.remove) await ctx.files.remove(join(sync.copy, file));
7929
+ }
7930
+ //#endregion
7931
+ //#region ../agent-cli/dist/built-ins/skill/home.js
7932
+ const SKILL_DIRS = [[".claude", "skills"], [".agents", "skills"]];
7933
+ const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
7934
+ const METADATA_VERSION = /^metadata:[ \t]*\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+version:[ \t]*["']?([^"'\s]+)["']?[ \t]*$/m;
7935
+ /** The version a SKILL.md's frontmatter `metadata.version` states. */
7936
+ function skillVersionIn(skill) {
7937
+ const frontmatter = FRONTMATTER.exec(skill)?.[1];
7938
+ return frontmatter === void 0 ? void 0 : METADATA_VERSION.exec(`${frontmatter}\n`)?.[1];
7939
+ }
7940
+ /** How the row words a copy that states a version, which only a skill from before CLI docs does. */
7941
+ function statedVersionNote(version) {
7942
+ return `states metadata.version ${version}: an older skill that CLI docs replace`;
7943
+ }
7944
+ function homeOf(env) {
7945
+ return env.HOME ?? env.USERPROFILE ?? homedir();
7946
+ }
7947
+ /** The home folders of the Skill copy; the skills installer may have made one a link to the other. */
7948
+ async function homeCopiesOf(name, env) {
7949
+ const paths = SKILL_DIRS.map((dir) => join(homeOf(env), ...dir, name));
7950
+ const reals = await Promise.all(paths.map(async (path) => orMissing(realpath(path))));
7951
+ return paths.filter((_path, index) => {
7952
+ const real = reals[index];
7953
+ return real === void 0 || reals.indexOf(real) === index;
8501
7954
  });
8502
7955
  }
7956
+ const homeSyncSchema = object({
7957
+ copies: array(string()),
7958
+ written: array(string()),
7959
+ removed: array(string())
7960
+ });
8503
7961
  /**
8504
- * The global npm install this copy runs from, found by asking npm for its global prefix.
8505
- * `bin` is the script that started the CLI; throws `NOT_GLOBAL_INSTALL` when it is not inside that install.
7962
+ * Makes each home Skill copy match the folder in the package at `packageDir`. Null when the CLI
7963
+ * declares no skill, or that package ships none, which must not empty a copy.
8506
7964
  */
8507
- async function globalInstallOf(bin, ctx) {
8508
- if (bin === void 0) throw notGlobal(ctx, `${ctx.cliName} runs from source, not from an npm install`);
8509
- const prefix = await globalPrefix(ctx);
8510
- const packageDir = join(globalModulesDir(prefix), ctx.registry.package);
8511
- const installed = await orMissing(realpath(packageDir));
8512
- const here = await orMissing(realpath(bin)) ?? bin;
8513
- if (installed === void 0 || !isInside(installed, here)) throw notGlobal(ctx, `${ctx.cliName} runs from ${here}, not from npm's global install at ${packageDir}`);
8514
- if ((await lstat(packageDir)).isSymbolicLink()) throw notGlobal(ctx, `${ctx.cliName} runs from a link to ${installed} (npm link), not from an npm install`);
7965
+ async function syncHome(cli, packageDir, ctx) {
7966
+ if (cli.skill === void 0) return null;
7967
+ const source = join(packageDir, cli.skill.folder);
7968
+ if ((await filesIn(source)).length === 0) return null;
7969
+ const copies = await homeCopiesOf(cli.name, ctx.env);
7970
+ const syncs = await Promise.all(copies.map((copy) => syncOf(source, copy)));
7971
+ for (const sync of syncs) await applySync(sync, ctx);
8515
7972
  return {
8516
- prefix,
8517
- packageDir
7973
+ copies,
7974
+ written: syncs.flatMap((sync) => [...sync.write, ...sync.replace]),
7975
+ removed: syncs.flatMap((sync) => sync.remove)
8518
7976
  };
8519
7977
  }
8520
- const NPMJS = "https://registry.npmjs.org/";
8521
- /** The flag that points npm at the CLI's registry; none for npmjs, npm's default. */
8522
- function registryFlags({ package: name, registry }) {
8523
- if (registry === NPMJS) return [];
8524
- const scope = scopeOf(name);
8525
- return [scope === void 0 ? `--registry=${registry}` : `--${scope}:registry=${registry}`];
7978
+ /** What a dry run says of the copies, since the release's files may not be on disk yet. */
7979
+ function renderPlannedSync(name, copies) {
7980
+ return copies.length === 0 ? [] : [`would make the ${name} skill at ${copies.join(", ")} match the release`];
8526
7981
  }
8527
- function globalRegistry(install) {
8528
- const { package: name, registry } = install;
8529
- return {
8530
- package: name,
8531
- label: registry,
8532
- flags: registryFlags(install),
8533
- unreachableHint: "check the network or VPN",
8534
- loginHint: `log in to ${registry} (\`npm login --registry=${registry}\`) or set its token in ~/.npmrc`,
8535
- missingHint: `check that ${name} is published on ${registry}`
7982
+ function renderHomeSync(name, sync) {
7983
+ if (sync === null) return [];
7984
+ const at = sync.copies.join(", ");
7985
+ 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}`];
7986
+ }
7987
+ async function homeCopyAt(path) {
7988
+ try {
7989
+ return {
7990
+ path,
7991
+ state: "readable",
7992
+ stated: skillVersionIn(await readFile(join(path, "SKILL.md"), "utf8"))
7993
+ };
7994
+ } catch (error) {
7995
+ return {
7996
+ path,
7997
+ state: isMissingFile(error) ? "missing" : "unreadable"
7998
+ };
7999
+ }
8000
+ }
8001
+ function describeCopy(copy) {
8002
+ if (copy.state === "missing") return `no copy at ${copy.path}`;
8003
+ if (copy.state !== "readable") return `${copy.path} (could not be read)`;
8004
+ return copy.stated === void 0 ? copy.path : `${copy.path} (${statedVersionNote(copy.stated)})`;
8005
+ }
8006
+ async function findingOf$1(cli, folder, install, ctx) {
8007
+ const paths = await homeCopiesOf(cli.name, ctx.env);
8008
+ const [copies, shipped] = await Promise.all([Promise.all(paths.map(homeCopyAt)), orMissing(readFile(join(ctx.packageRoot, folder, "SKILL.md"), "utf8"))]);
8009
+ const message = copies.map(describeCopy).join("; ");
8010
+ const shipsStated = shipped !== void 0 && skillVersionIn(shipped) !== void 0;
8011
+ return copies.every((copy) => copy.state === "readable" && (copy.stated === void 0 || shipsStated)) ? {
8012
+ state: "ok",
8013
+ message
8014
+ } : {
8015
+ state: "problem",
8016
+ message,
8017
+ fix: `${install.runAs} setup`
8536
8018
  };
8537
8019
  }
8538
- /** npm for a Global install, run in the working directory. */
8539
- function globalNpm(cli, install, ctx, node) {
8540
- return npmContext(cli, globalRegistry(install), ctx, node, ctx.cwd);
8020
+ /** A skill the package ships, which `setup` and `update` copy under home. */
8021
+ function homeSkill(cli, { folder }, install) {
8022
+ return {
8023
+ builtIns: {},
8024
+ rows: [{
8025
+ name: "skill",
8026
+ advisory: true,
8027
+ run: (ctx) => findingOf$1(cli, folder, install, ctx)
8028
+ }]
8029
+ };
8541
8030
  }
8542
8031
  //#endregion
8543
8032
  //#region ../agent-cli/dist/built-ins/upgrades.js
@@ -8604,6 +8093,147 @@ async function upgradesOf(packageDir, from, to) {
8604
8093
  }
8605
8094
  return releasesIn(changelog).filter(({ version }) => compareVersions(version, from) > 0 && compareVersions(version, to) <= 0).sort((a, b) => compareVersions(a.version, b.version)).flatMap(upgradeOf);
8606
8095
  }
8096
+ function renderUpgrade(upgrade) {
8097
+ return "steps" in upgrade ? [`\nupgrade steps for ${upgrade.version}:`, ...upgrade.steps.map((step) => `- ${step}`)].join("\n") : `\nchanges in ${upgrade.version}:\n${upgrade.notes}`;
8098
+ }
8099
+ //#endregion
8100
+ //#region ../agent-cli/dist/built-ins/setup.js
8101
+ const NOT_ON_PATH = "NOT_ON_PATH";
8102
+ const setupSchema = object({
8103
+ version: string(),
8104
+ prefix: string(),
8105
+ installed: boolean(),
8106
+ skill: homeSyncSchema.nullable(),
8107
+ upgrades: array(upgradeSchema),
8108
+ docs: string().nullable()
8109
+ });
8110
+ const installedManifest = object({ version: string() });
8111
+ async function versionAt(packageDir) {
8112
+ const text = await orMissing(readFile(join(packageDir, "package.json"), "utf8"));
8113
+ if (text === void 0) return null;
8114
+ try {
8115
+ const parsed = installedManifest.safeParse(JSON.parse(text));
8116
+ return parsed.success ? parsed.data.version : null;
8117
+ } catch (error) {
8118
+ if (error instanceof SyntaxError) return null;
8119
+ throw error;
8120
+ }
8121
+ }
8122
+ function npmOfSetup(cli, install, ctx, node) {
8123
+ return {
8124
+ ...globalNpm(cli, install, ctx, node),
8125
+ rerun: wholeLine(setupLineOf(install))
8126
+ };
8127
+ }
8128
+ function needsInstall({ from, latest }) {
8129
+ return from === null || standingOf$1(from, latest) === "behind";
8130
+ }
8131
+ function binDirOf(prefix) {
8132
+ return process.platform === "win32" ? prefix : join(prefix, "bin");
8133
+ }
8134
+ async function canonical(path) {
8135
+ return normalize(await realpath(path).catch(() => path));
8136
+ }
8137
+ async function assertOnPath(cli, prefix, node, ctx) {
8138
+ const binDir = binDirOf(prefix);
8139
+ const wanted = await canonical(binDir);
8140
+ const entries = pathOf(ctx.env).split(delimiter).filter(Boolean);
8141
+ if ((await Promise.all(entries.map(canonical))).includes(wanted)) return;
8142
+ throw runtimeError({
8143
+ code: NOT_ON_PATH,
8144
+ message: `${cli.name} is installed in ${binDir}, by the npm of ${node ?? "the Node on PATH"}, a folder not on this shell's PATH`,
8145
+ hint: `add ${binDir} to PATH, or ask your user which Node their terminal runs and run this under it`,
8146
+ next: []
8147
+ });
8148
+ }
8149
+ const REFUSED = /* @__PURE__ */ new Set([
8150
+ PERMISSION_DENIED,
8151
+ DISK_FULL,
8152
+ INSTALL_FAILED
8153
+ ]);
8154
+ function withSetupLine(error, install) {
8155
+ if (!(error instanceof CliError) || !REFUSED.has(error.code)) return error;
8156
+ return runtimeError({
8157
+ code: error.code,
8158
+ message: error.message,
8159
+ hint: error.code === "INSTALL_FAILED" ? "fix what npm reports, then run the Next step; if a sandbox refused it, ask your user to type ! then that line" : error.hint,
8160
+ next: [wholeLine(setupLineOf(install))]
8161
+ }, { cause: error });
8162
+ }
8163
+ function renderSetup(cli, install, setup) {
8164
+ const { version, prefix, docs } = setup;
8165
+ return [
8166
+ setup.installed ? `installed ${install.package}@${version} with the npm under ${prefix}` : `${install.package}@${version} is already installed under ${prefix}`,
8167
+ ...renderHomeSync(cli.name, setup.skill),
8168
+ ...setup.upgrades.map(renderUpgrade),
8169
+ ...docs === null ? [] : [`docs ${docs}`, `Read that folder before using ${cli.name}.`]
8170
+ ].join("\n");
8171
+ }
8172
+ /** The built-in `setup`, which runs from any copy, such as npx's, so it never asks to be the global one. */
8173
+ function setupCommand(cli, install, { node }) {
8174
+ return defineCommand$1({
8175
+ description: "install this CLI and its skill on this machine",
8176
+ examples: [{
8177
+ command: `${cli.name} setup`,
8178
+ note: "install the latest version and the skill"
8179
+ }, {
8180
+ command: `${cli.name} setup --dry-run`,
8181
+ note: "show what it would change"
8182
+ }],
8183
+ effect: "write",
8184
+ output: setupSchema,
8185
+ plan: async (_input, ctx) => {
8186
+ const npm = npmOfSetup(cli, install, ctx, node);
8187
+ const prefix = await globalPrefix(npm);
8188
+ const packageDir = join(globalModulesDir(prefix), install.package);
8189
+ if ((await orMissing(lstat(packageDir)))?.isSymbolicLink() === true) throw runtimeError({
8190
+ code: NOT_GLOBAL_INSTALL,
8191
+ message: `${packageDir} is a link (npm link), which an install would replace`,
8192
+ hint: "remove the link with npm unlink, then run the Next step",
8193
+ next: [wholeLine(setupLineOf(install))]
8194
+ });
8195
+ return {
8196
+ package: install.package,
8197
+ from: await versionAt(packageDir),
8198
+ latest: await latestVersion(npm),
8199
+ prefix,
8200
+ packageDir,
8201
+ copies: cli.skill === void 0 ? [] : await homeCopiesOf(cli.name, ctx.env)
8202
+ };
8203
+ },
8204
+ apply: async (plan, ctx) => {
8205
+ const { from, latest, prefix, packageDir } = plan;
8206
+ const installs = needsInstall(plan);
8207
+ const skill = await ctx.runToEnd(`installing ${plan.package}@${latest}`, async () => {
8208
+ if (installs) {
8209
+ ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
8210
+ await installGlobally(latest, npmOfSetup(cli, install, ctx, node));
8211
+ }
8212
+ return syncHome(cli, packageDir, ctx);
8213
+ }).catch((error) => {
8214
+ throw withSetupLine(error, install);
8215
+ });
8216
+ const setup = {
8217
+ version: installs || from === null ? latest : from,
8218
+ prefix,
8219
+ installed: installs,
8220
+ skill,
8221
+ upgrades: installs && from !== null ? await upgradesOf(packageDir, from, latest) : [],
8222
+ docs: cli.docs === void 0 ? null : join(packageDir, cli.docs)
8223
+ };
8224
+ ctx.deliver(setup);
8225
+ if (ctx.signal.aborted) throw new CliError({
8226
+ code: INTERRUPTED,
8227
+ message: `interrupted, but ${plan.package}@${setup.version} and its skill are already in place`,
8228
+ hint: "nothing to run again"
8229
+ });
8230
+ await assertOnPath(cli, prefix, node, ctx);
8231
+ return setup;
8232
+ },
8233
+ render: (setup) => renderSetup(cli, install, setup),
8234
+ 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")
8235
+ });
8236
+ }
8607
8237
  //#endregion
8608
8238
  //#region ../agent-cli/dist/built-ins/update.js
8609
8239
  const updateSchema = object({
@@ -8613,34 +8243,30 @@ const updateSchema = object({
8613
8243
  version: string(),
8614
8244
  prefix: string()
8615
8245
  }).nullable(),
8616
- upgrades: array(upgradeSchema),
8617
- next: array(string())
8246
+ skill: homeSyncSchema.nullable(),
8247
+ upgrades: array(upgradeSchema)
8618
8248
  });
8619
8249
  function nothingToInstall({ from, latest }) {
8620
8250
  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`;
8621
8251
  }
8622
- function renderUpgrade(upgrade) {
8623
- return "steps" in upgrade ? [`\nupgrade steps for ${upgrade.version}:`, ...upgrade.steps.map((step) => `- ${step}`)].join("\n") : `\nchanges in ${upgrade.version}:\n${upgrade.notes}`;
8624
- }
8625
8252
  function renderUpdate(cli, install, update) {
8626
8253
  const { installed } = update;
8627
- const head = installed === null ? [nothingToInstall(update)] : [`installed ${install.package}@${installed.version} with the npm under ${installed.prefix}, replacing ${update.from}`, ...update.upgrades.map(renderUpgrade)];
8628
- const next = update.next.map((command) => `next: ${command}, so the ${cli.name} skill describes ${update.latest}`);
8629
- return (next.length === 0 ? head : [
8630
- ...head,
8631
- "",
8632
- ...next
8633
- ]).join("\n");
8254
+ const skill = renderHomeSync(cli.name, update.skill);
8255
+ return installed === null ? [nothingToInstall(update), ...skill].join("\n") : [
8256
+ `installed ${install.package}@${installed.version} with the npm under ${installed.prefix}, replacing ${update.from}`,
8257
+ ...skill,
8258
+ ...update.upgrades.map(renderUpgrade)
8259
+ ].join("\n");
8634
8260
  }
8635
8261
  function interruptedAfterInstall(plan) {
8636
8262
  return new CliError({
8637
8263
  code: INTERRUPTED,
8638
8264
  message: `interrupted, but ${plan.package}@${plan.latest} had already been installed with the npm under ${plan.prefix}, replacing ${plan.from}`,
8639
- hint: "nothing to run again: run the commands under next"
8265
+ hint: "nothing to run again"
8640
8266
  });
8641
8267
  }
8642
8268
  /** The built-in `update`, for a CLI installed globally with npm. */
8643
- function updateCommand(cli, install, skill, { bin, node }) {
8269
+ function updateCommand(cli, install, { bin, node }) {
8644
8270
  return defineCommand$1({
8645
8271
  description: "install the latest version of this CLI from its registry",
8646
8272
  examples: [{
@@ -8660,40 +8286,217 @@ function updateCommand(cli, install, skill, { bin, node }) {
8660
8286
  from: cli.version,
8661
8287
  latest: await latestVersion(npm),
8662
8288
  prefix: copy.prefix,
8663
- packageDir: copy.packageDir
8289
+ packageDir: copy.packageDir,
8290
+ copies: cli.skill === void 0 ? [] : await homeCopiesOf(cli.name, ctx.env)
8291
+ };
8292
+ },
8293
+ apply: async (plan, ctx) => {
8294
+ const { from, latest, prefix } = plan;
8295
+ if (standingOf$1(from, latest) !== "behind") return {
8296
+ from,
8297
+ latest,
8298
+ installed: null,
8299
+ skill: await syncHome(cli, plan.packageDir, ctx),
8300
+ upgrades: []
8301
+ };
8302
+ ctx.signal.throwIfAborted();
8303
+ ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
8304
+ const skill = await ctx.runToEnd(`installing ${plan.package}@${latest}`, async () => {
8305
+ await installGlobally(latest, globalNpm(cli, install, ctx, node));
8306
+ return syncHome(cli, plan.packageDir, ctx);
8307
+ });
8308
+ const update = {
8309
+ from,
8310
+ latest,
8311
+ installed: {
8312
+ version: latest,
8313
+ prefix
8314
+ },
8315
+ skill,
8316
+ upgrades: await upgradesOf(plan.packageDir, from, latest)
8664
8317
  };
8318
+ if (ctx.signal.aborted) {
8319
+ ctx.deliver(update);
8320
+ throw interruptedAfterInstall(plan);
8321
+ }
8322
+ return update;
8323
+ },
8324
+ render: (update) => renderUpdate(cli, install, update),
8325
+ 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")
8326
+ });
8327
+ }
8328
+ //#endregion
8329
+ //#region ../agent-cli/dist/context/files.js
8330
+ const TEMP_FILE = /^\..+\.agent-cli-(\d+)\.tmp$/;
8331
+ /** Where the Runtime writes `path` before moving it into place; a dead writer's is swept. */
8332
+ function tempPathOf(path) {
8333
+ return join(dirname(path), `.${basename(path)}.agent-cli-${process.pid}.tmp`);
8334
+ }
8335
+ function isAlive(pid) {
8336
+ try {
8337
+ process.kill(pid, 0);
8338
+ return true;
8339
+ } catch (error) {
8340
+ return errnoOf(error) === "EPERM";
8341
+ }
8342
+ }
8343
+ async function sweep(folder) {
8344
+ const dead = (await readdir(folder)).filter((name) => {
8345
+ const pid = TEMP_FILE.exec(name)?.[1];
8346
+ return pid !== void 0 && !isAlive(Number(pid));
8347
+ });
8348
+ await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
8349
+ }
8350
+ /** The file a symlink at `path` points to, so a replacing write keeps the link. */
8351
+ async function targetOf$1(path) {
8352
+ try {
8353
+ return await realpath(path);
8354
+ } catch (error) {
8355
+ if (isMissingFile(error)) return path;
8356
+ throw error;
8357
+ }
8358
+ }
8359
+ async function writeWithMode(path, data, mode) {
8360
+ if (mode === void 0) {
8361
+ await writeFile(path, data);
8362
+ return;
8363
+ }
8364
+ await writeFile(path, data, { mode });
8365
+ await chmod(path, mode);
8366
+ }
8367
+ /** What `link` answers on a file system without hard links, such as exFAT. */
8368
+ const NO_HARD_LINKS = /* @__PURE__ */ new Set([
8369
+ "EPERM",
8370
+ "ENOTSUP",
8371
+ "EXDEV"
8372
+ ]);
8373
+ /** A link fails atomically when `path` is taken, so no reader ever sees half a file there. */
8374
+ async function linkNew(temp, path, data, mode) {
8375
+ try {
8376
+ await link(temp, path);
8377
+ } catch (error) {
8378
+ if (!NO_HARD_LINKS.has(errnoOf(error))) throw error;
8379
+ await createNew(path, data, mode);
8380
+ }
8381
+ }
8382
+ /** A file this call created and could not finish is removed, so a rerun does not find half of it. */
8383
+ async function createNew(path, data, mode) {
8384
+ const handle = await open(path, "wx", mode);
8385
+ try {
8386
+ await handle.writeFile(data);
8387
+ if (mode !== void 0) await handle.chmod(mode);
8388
+ } catch (error) {
8389
+ await handle.close();
8390
+ await rm(path, { force: true });
8391
+ throw error;
8392
+ }
8393
+ await handle.close();
8394
+ }
8395
+ const RENAME_ATTEMPTS = 10;
8396
+ const RENAME_BACKOFF_MS = 10;
8397
+ const HELD_BY_ANOTHER_PROGRAM = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
8398
+ async function renameRetrying(from, to) {
8399
+ for (let attempt = 1;; attempt++) try {
8400
+ await rename(from, to);
8401
+ return;
8402
+ } catch (error) {
8403
+ if (!(process.platform === "win32" && HELD_BY_ANOTHER_PROGRAM.has(errnoOf(error))) || attempt === RENAME_ATTEMPTS) throw error;
8404
+ await setTimeout$1(attempt * RENAME_BACKOFF_MS);
8405
+ }
8406
+ }
8407
+ /** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
8408
+ function realFiles() {
8409
+ const swept = /* @__PURE__ */ new Set();
8410
+ return {
8411
+ exists: async (path) => {
8412
+ try {
8413
+ await access(path);
8414
+ return true;
8415
+ } catch (error) {
8416
+ if (isNothingThere(error)) return false;
8417
+ throw error;
8418
+ }
8419
+ },
8420
+ write: async (path, data, options = {}) => {
8421
+ await mkdir(dirname(path), { recursive: true });
8422
+ const target = options.exclusive === true ? path : await targetOf$1(path);
8423
+ const folder = dirname(target);
8424
+ if (!swept.has(folder)) {
8425
+ await sweep(folder);
8426
+ swept.add(folder);
8427
+ }
8428
+ const temp = tempPathOf(target);
8429
+ try {
8430
+ await writeWithMode(temp, data, options.mode);
8431
+ if (options.exclusive === true) {
8432
+ await linkNew(temp, target, data, options.mode);
8433
+ await rm(temp);
8434
+ } else await renameRetrying(temp, target);
8435
+ } catch (error) {
8436
+ await rm(temp, { force: true });
8437
+ throw error;
8438
+ }
8665
8439
  },
8666
- apply: async (plan, ctx) => {
8667
- const { from, latest, prefix } = plan;
8668
- if (standingOf$1(from, latest) !== "behind") return {
8669
- from,
8670
- latest,
8671
- installed: null,
8672
- upgrades: [],
8673
- next: await skill.nextAt(latest, ctx)
8674
- };
8675
- ctx.signal.throwIfAborted();
8676
- ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
8677
- await ctx.runToEnd(`installing ${plan.package}@${latest}`, () => installGlobally(latest, globalNpm(cli, install, ctx, node)));
8678
- const update = {
8679
- from,
8680
- latest,
8681
- installed: {
8682
- version: latest,
8683
- prefix
8684
- },
8685
- upgrades: await upgradesOf(plan.packageDir, from, latest),
8686
- next: await skill.nextAt(latest, ctx)
8687
- };
8688
- if (ctx.signal.aborted) {
8689
- ctx.deliver(update);
8690
- throw interruptedAfterInstall(plan);
8440
+ read: (path) => readFile(path),
8441
+ list: (folder) => readdir(folder),
8442
+ remove: (path) => rm(path, {
8443
+ recursive: true,
8444
+ force: true
8445
+ }),
8446
+ mkdir: async (path, options) => {
8447
+ const mode = options?.mode;
8448
+ if (options?.exclusive !== true) {
8449
+ await mkdir(path, {
8450
+ recursive: true,
8451
+ mode
8452
+ });
8453
+ return;
8691
8454
  }
8692
- return update;
8455
+ await mkdir(dirname(path), {
8456
+ recursive: true,
8457
+ mode
8458
+ });
8459
+ await mkdir(path, { mode });
8693
8460
  },
8694
- render: (update) => renderUpdate(cli, install, update),
8695
- 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)
8696
- });
8461
+ stat: async (path) => {
8462
+ try {
8463
+ const { size, dev, ino } = await stat(path, { bigint: true });
8464
+ return {
8465
+ size: Number(size),
8466
+ id: `${dev}:${ino}`
8467
+ };
8468
+ } catch (error) {
8469
+ if (isNothingThere(error)) return void 0;
8470
+ throw error;
8471
+ }
8472
+ }
8473
+ };
8474
+ }
8475
+ /**
8476
+ * `files` resolving paths against `cwd`, its failures classified by errno; `list` drops the
8477
+ * Runtime's temp files and sorts.
8478
+ */
8479
+ function classifiedFiles(files, { cwd, fileError }) {
8480
+ async function at(access, path, work) {
8481
+ const target = resolve(cwd, path);
8482
+ try {
8483
+ return await work(target);
8484
+ } catch (error) {
8485
+ throw fileError(error, {
8486
+ path: target,
8487
+ access
8488
+ });
8489
+ }
8490
+ }
8491
+ return {
8492
+ exists: (path) => at("read", path, (target) => files.exists(target)),
8493
+ write: (path, data, options) => at("write", path, (target) => files.write(target, data, options)),
8494
+ read: (path) => at("read", path, (target) => files.read(target)),
8495
+ list: (folder) => at("read", folder, async (target) => (await files.list(target)).filter((name) => !TEMP_FILE.test(name)).sort()),
8496
+ remove: (path) => at("write", path, (target) => files.remove(target)),
8497
+ mkdir: (path, options) => at("write", path, (target) => files.mkdir(target, options)),
8498
+ stat: (path) => at("read", path, (target) => files.stat(target))
8499
+ };
8697
8500
  }
8698
8501
  //#endregion
8699
8502
  //#region ../agent-cli/dist/built-ins/install/release-check.js
@@ -8807,78 +8610,310 @@ async function releaseCheck(cli, install, runAs, launch, ctx) {
8807
8610
  debugLine(cli, ctx, messageOf(error));
8808
8611
  }
8809
8612
  }
8810
- async function checkAndTell(cli, install, runAs, launch, ctx) {
8811
- const file = cacheFileOf(cli.name, ctx.env);
8812
- const last = await lastCheck(file);
8813
- if (last?.error !== void 0) debugLine(cli, ctx, last.error);
8814
- if (last === void 0 || ctx.clock.now() - last.checkedAt >= CHECK_INTERVAL_MS) await startCheck(cli, install, file, last, launch, ctx);
8815
- const latest = last?.latest;
8816
- if (latest === void 0 || !isVersion(latest) || compareVersions(latest, cli.version) <= 0) return;
8817
- ctx.notice(`${cli.name} ${latest} is out, this is ${cli.version}; run \`${runAs} update\``);
8613
+ async function checkAndTell(cli, install, runAs, launch, ctx) {
8614
+ const file = cacheFileOf(cli.name, ctx.env);
8615
+ const last = await lastCheck(file);
8616
+ if (last?.error !== void 0) debugLine(cli, ctx, last.error);
8617
+ if (last === void 0 || ctx.clock.now() - last.checkedAt >= CHECK_INTERVAL_MS) await startCheck(cli, install, file, last, launch, ctx);
8618
+ const latest = last?.latest;
8619
+ if (latest === void 0 || !isVersion(latest) || compareVersions(latest, cli.version) <= 0) return;
8620
+ ctx.notice(`${cli.name} ${latest} is out, this is ${cli.version}; run \`${runAs} update\``);
8621
+ }
8622
+ //#endregion
8623
+ //#region ../agent-cli/dist/built-ins/install/global.js
8624
+ function checkOf(cli, install, { bin, node }) {
8625
+ return {
8626
+ install: async (ctx) => {
8627
+ const npm = globalNpm(cli, install, ctx, node);
8628
+ try {
8629
+ return {
8630
+ state: "ok",
8631
+ message: `a global npm install under ${(await globalInstallOf(bin, npm)).prefix}`
8632
+ };
8633
+ } catch (error) {
8634
+ if (error instanceof CliError && error.code === "NOT_GLOBAL_INSTALL") return {
8635
+ state: "problem",
8636
+ message: error.message,
8637
+ fix: setupLineOf(install)
8638
+ };
8639
+ throw error;
8640
+ }
8641
+ },
8642
+ release: async (ctx) => {
8643
+ const fix = `${cli.name} update`;
8644
+ return {
8645
+ npm: globalNpm(cli, install, ctx, node),
8646
+ advise: () => ({ fix })
8647
+ };
8648
+ }
8649
+ };
8650
+ }
8651
+ /** The Install of a CLI installed once per machine with npm, which `setup` puts there and `update` replaces. */
8652
+ function globalInstall(cli, install) {
8653
+ const adapter = {
8654
+ runAs: runAsOf(cli),
8655
+ builtIns: (launch) => ({
8656
+ setup: setupCommand(cli, install, launch),
8657
+ update: updateCommand(cli, install, launch)
8658
+ }),
8659
+ exitClasses: ["source"],
8660
+ check: (launch) => checkOf(cli, install, launch),
8661
+ notice: (launch, ctx) => releaseCheck(cli, install, adapter.runAs, launch, ctx)
8662
+ };
8663
+ return adapter;
8664
+ }
8665
+ //#endregion
8666
+ //#region ../agent-cli/dist/built-ins/install/install.js
8667
+ const noNotice = async () => {};
8668
+ function noInstall(cli) {
8669
+ const skipped = async () => ({
8670
+ state: "skipped",
8671
+ message: `not checked: ${cli.name} declares no install`
8672
+ });
8673
+ return {
8674
+ runAs: runAsOf(cli),
8675
+ builtIns: () => ({}),
8676
+ exitClasses: [],
8677
+ check: () => ({
8678
+ install: skipped,
8679
+ release: async () => ({ skipped: await skipped() })
8680
+ }),
8681
+ notice: noNotice
8682
+ };
8683
+ }
8684
+ function installOf(cli) {
8685
+ const { install } = cli;
8686
+ if (install === void 0) return noInstall(cli);
8687
+ return install.mode === "global" ? globalInstall(cli, install) : dependencyInstall(cli, install);
8688
+ }
8689
+ //#endregion
8690
+ //#region ../agent-cli/dist/built-ins/agents-md.js
8691
+ function markersOf(name) {
8692
+ return {
8693
+ begin: `<!-- BEGIN:${name} -->`,
8694
+ end: `<!-- END:${name} -->`
8695
+ };
8696
+ }
8697
+ /** `text` with the CLI's Agents section between its markers, appended when they are missing; nothing else changes. */
8698
+ function withSection({ name, text: section }, text) {
8699
+ const { begin, end } = markersOf(name);
8700
+ const whole = `${begin}\n${section}\n${end}`;
8701
+ const start = text.indexOf(begin);
8702
+ const stop = start === -1 ? -1 : text.indexOf(end, start);
8703
+ if (stop !== -1) return `${text.slice(0, start)}${whole}${text.slice(stop + end.length)}`;
8704
+ if (text === "") return `${whole}\n`;
8705
+ return `${text}${text.endsWith("\n") ? "" : "\n"}\n${whole}\n`;
8706
+ }
8707
+ async function agentsMdAt(agents, root) {
8708
+ const path = join(root, "AGENTS.md");
8709
+ const existing = await orMissing(readFile(path, "utf8"));
8710
+ if (existing === void 0) return {
8711
+ path,
8712
+ agents,
8713
+ change: "create"
8714
+ };
8715
+ if (withSection(agents, existing) === existing) return {
8716
+ path,
8717
+ agents,
8718
+ change: null
8719
+ };
8720
+ return {
8721
+ path,
8722
+ agents,
8723
+ change: existing.includes(markersOf(agents.name).begin) ? "rewrite" : "add"
8724
+ };
8725
+ }
8726
+ async function handEditsAt(root) {
8727
+ const claudeMd = join(root, "CLAUDE.md");
8728
+ const text = await orMissing(readFile(claudeMd, "utf8"));
8729
+ if (text === void 0 || text.split(/\r?\n/).some((line) => line.trim() === "@AGENTS.md")) return [];
8730
+ return [`add a line \`@AGENTS.md\` to ${claudeMd}, so Claude Code reads AGENTS.md`];
8731
+ }
8732
+ function agentsRow(agents, install) {
8733
+ const { name } = agents;
8734
+ return {
8735
+ name: "agents",
8736
+ advisory: true,
8737
+ run: async (ctx) => {
8738
+ const root = await projectRootOf(ctx.cwd);
8739
+ const { path, change } = await agentsMdAt(agents, root);
8740
+ const fix = `${install.runAs} skill install`;
8741
+ if (change === "rewrite") return {
8742
+ state: "problem",
8743
+ message: `${path} holds a ${name} Agents section other than the one ${fix} writes`,
8744
+ fix
8745
+ };
8746
+ if (change !== null) return {
8747
+ state: "problem",
8748
+ message: `${path} holds no ${name} Agents section, so sessions in this repo do not know ${name}`,
8749
+ fix
8750
+ };
8751
+ const holds = `${path} holds the ${name} Agents section`;
8752
+ const [edit] = await handEditsAt(root);
8753
+ return edit === void 0 ? {
8754
+ state: "ok",
8755
+ message: holds
8756
+ } : {
8757
+ state: "problem",
8758
+ message: `${holds}; ${edit}`
8759
+ };
8760
+ }
8761
+ };
8762
+ }
8763
+ //#endregion
8764
+ //#region ../agent-cli/dist/built-ins/skill/install-command.js
8765
+ /** The project root and the Skill copy under it, as `skill install` writes it. */
8766
+ async function skillCopyOf(cli, cwd) {
8767
+ const root = await projectRootOf(cwd);
8768
+ return {
8769
+ root,
8770
+ copy: join(root, ".claude", "skills", cli.name)
8771
+ };
8772
+ }
8773
+ const installSchema = object({
8774
+ copy: string(),
8775
+ written: array(string()),
8776
+ removed: array(string()),
8777
+ agentsMd: object({
8778
+ path: string(),
8779
+ changed: boolean()
8780
+ }).nullable(),
8781
+ handEdits: array(string())
8782
+ });
8783
+ async function planOf(cli, folder, agents, cwd, packageRoot) {
8784
+ const { root, copy } = await skillCopyOf(cli, cwd);
8785
+ return {
8786
+ ...await syncOf(join(packageRoot, folder), copy),
8787
+ agentsMd: agents === void 0 ? null : await agentsMdAt(agents, root),
8788
+ handEdits: agents === void 0 ? [] : await handEditsAt(root)
8789
+ };
8790
+ }
8791
+ const SECTION_VERBS = {
8792
+ create: "create",
8793
+ add: "add the Agents section to",
8794
+ rewrite: "rewrite the Agents section in"
8795
+ };
8796
+ function renderPlan(cli, plan) {
8797
+ const files = [
8798
+ ...plan.write.map((file) => ` write ${file}`),
8799
+ ...plan.replace.map((file) => ` replace ${file}`),
8800
+ ...plan.remove.map((file) => ` remove ${file}`)
8801
+ ];
8802
+ const { agentsMd } = plan;
8803
+ return [
8804
+ files.length === 0 ? `the ${cli.name} skill at ${plan.copy} already matches the package` : `would copy the ${cli.name} skill to ${plan.copy}:`,
8805
+ ...files,
8806
+ ...agentsMd === null ? [] : [agentsMd.change === null ? `${agentsMd.path} already holds the ${cli.name} Agents section` : `would ${SECTION_VERBS[agentsMd.change]} ${agentsMd.path}:\n${agentsMd.agents.text}`],
8807
+ ...plan.handEdits.map((edit) => `by hand: ${edit}`)
8808
+ ].join("\n");
8818
8809
  }
8819
- //#endregion
8820
- //#region ../agent-cli/dist/built-ins/install/global.js
8821
- function checkOf(cli, install, { bin, node }) {
8822
- return {
8823
- install: async (ctx) => {
8824
- const npm = globalNpm(cli, install, ctx, node);
8825
- try {
8826
- return {
8827
- state: "ok",
8828
- message: `a global npm install under ${(await globalInstallOf(bin, npm)).prefix}`
8829
- };
8830
- } catch (error) {
8831
- if (error instanceof CliError && error.code === "NOT_GLOBAL_INSTALL") return {
8832
- state: "problem",
8833
- message: error.message,
8834
- fix: installCommandLine(npm.registry)
8835
- };
8836
- throw error;
8810
+ function renderInstalled(cli, data) {
8811
+ const { written, removed, agentsMd } = data;
8812
+ return [
8813
+ written.length + removed.length === 0 ? `the ${cli.name} skill at ${data.copy} already matches the package` : `copied the ${cli.name} skill to ${data.copy}: wrote ${written.length} files, removed ${removed.length}`,
8814
+ ...agentsMd === null ? [] : [agentsMd.changed ? `wrote the ${cli.name} Agents section in ${agentsMd.path}` : `${agentsMd.path} already holds the ${cli.name} Agents section`],
8815
+ ...data.handEdits.map((edit) => `by hand: ${edit}`)
8816
+ ].join("\n");
8817
+ }
8818
+ /** The built-in `skill install`, for a CLI whose skill ships in its package. */
8819
+ function skillInstallCommand(cli, folder, agents) {
8820
+ return defineCommand$1({
8821
+ 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",
8822
+ examples: [{
8823
+ command: `${cli.name} skill install`,
8824
+ note: "install the skill this version ships"
8825
+ }, {
8826
+ command: `${cli.name} skill install --dry-run`,
8827
+ note: "show what it would change"
8828
+ }],
8829
+ effect: "write",
8830
+ output: installSchema,
8831
+ plan: (_input, ctx) => planOf(cli, folder, agents, ctx.cwd, ctx.packageRoot),
8832
+ apply: async (plan, ctx) => {
8833
+ await applySync(plan, ctx);
8834
+ const { agentsMd } = plan;
8835
+ if (agentsMd !== null && agentsMd.change !== null) {
8836
+ const existing = await orMissing(readFile(agentsMd.path, "utf8")) ?? "";
8837
+ await ctx.files.write(agentsMd.path, withSection(agentsMd.agents, existing));
8837
8838
  }
8838
- },
8839
- release: async (ctx) => {
8840
- const fix = `${cli.name} update`;
8841
8839
  return {
8842
- npm: globalNpm(cli, install, ctx, node),
8843
- advise: () => ({ fix })
8840
+ copy: plan.copy,
8841
+ written: [...plan.write, ...plan.replace].sort(),
8842
+ removed: [...plan.remove],
8843
+ agentsMd: agentsMd === null ? null : {
8844
+ path: agentsMd.path,
8845
+ changed: agentsMd.change !== null
8846
+ },
8847
+ handEdits: [...plan.handEdits]
8844
8848
  };
8845
- }
8846
- };
8849
+ },
8850
+ render: (data) => renderInstalled(cli, data),
8851
+ renderPlan: (plan) => renderPlan(cli, plan)
8852
+ });
8847
8853
  }
8848
- /** The Install of a CLI installed once per machine with npm, which `update` replaces. */
8849
- function globalInstall(cli, install) {
8850
- const adapter = {
8851
- runAs: runAsOf(cli),
8852
- builtIns: (launch) => ({ update: updateCommand(cli, install, skillOf$1(cli, adapter), launch) }),
8853
- exitClasses: ["source"],
8854
- check: (launch) => checkOf(cli, install, launch),
8855
- notice: (launch, ctx) => releaseCheck(cli, install, adapter.runAs, launch, ctx)
8854
+ //#endregion
8855
+ //#region ../agent-cli/dist/built-ins/skill/project.js
8856
+ async function personalCopyOf(name, copy, ctx) {
8857
+ const home = join(homeOf(ctx.env), ".claude", "skills", name);
8858
+ const real = await orMissing(realpath(home));
8859
+ return real === void 0 || real === await orMissing(realpath(copy)) ? void 0 : home;
8860
+ }
8861
+ /** The project root's copy against the package's, and any under home. */
8862
+ async function findingOf(cli, folder, install, ctx) {
8863
+ const { copy } = await skillCopyOf(cli, ctx.cwd);
8864
+ const [installed, shipped] = await Promise.all([orMissing(readFile(join(copy, "SKILL.md"), "utf8")), readFile(join(ctx.packageRoot, folder, "SKILL.md"), "utf8")]);
8865
+ const stated = installed === void 0 ? void 0 : skillVersionIn(installed);
8866
+ const left = stated !== void 0 && skillVersionIn(shipped) === void 0;
8867
+ const found = left ? `${copy} (${statedVersionNote(stated)})` : copy;
8868
+ const head = installed === void 0 ? `no copy of the ${cli.name} skill at ${copy}` : found;
8869
+ const home = await personalCopyOf(cli.name, copy, ctx);
8870
+ const message = home === void 0 ? head : `${head}; remove ${home}: Claude Code prefers a personal skill over the project's`;
8871
+ if (installed === void 0 || left) return {
8872
+ state: "problem",
8873
+ message,
8874
+ fix: `${install.runAs} skill install`
8875
+ };
8876
+ return home === void 0 ? {
8877
+ state: "ok",
8878
+ message
8879
+ } : {
8880
+ state: "problem",
8881
+ message
8856
8882
  };
8857
- return adapter;
8858
8883
  }
8859
- //#endregion
8860
- //#region ../agent-cli/dist/built-ins/install/install.js
8861
- const noNotice = async () => {};
8862
- function noInstall(cli) {
8863
- const skipped = async () => ({
8864
- state: "skipped",
8865
- message: `not checked: ${cli.name} declares no install`
8866
- });
8884
+ /** A skill the package ships in `folder`, which `skill install` copies to the project root. */
8885
+ function projectSkill(cli, { folder, agents: text }, install) {
8886
+ const agents = text === void 0 ? void 0 : {
8887
+ name: cli.name,
8888
+ text
8889
+ };
8867
8890
  return {
8868
- runAs: runAsOf(cli),
8869
- builtIns: () => ({}),
8870
- exitClasses: [],
8871
- check: () => ({
8872
- install: skipped,
8873
- release: async () => ({ skipped: await skipped() })
8874
- }),
8875
- notice: noNotice
8891
+ builtIns: { skill: { install: skillInstallCommand(cli, folder, agents) } },
8892
+ rows: [{
8893
+ name: "skill",
8894
+ advisory: true,
8895
+ run: (ctx) => findingOf(cli, folder, install, ctx)
8896
+ }, ...agents === void 0 ? [] : [agentsRow(agents, install)]]
8876
8897
  };
8877
8898
  }
8878
- function installOf(cli) {
8879
- const { install } = cli;
8880
- if (install === void 0) return noInstall(cli);
8881
- return install.mode === "global" ? globalInstall(cli, install) : dependencyInstall(cli, install);
8899
+ //#endregion
8900
+ //#region ../agent-cli/dist/built-ins/skill/skill.js
8901
+ const noSkill = ({ name }) => ({
8902
+ builtIns: {},
8903
+ rows: [{
8904
+ name: "skill",
8905
+ advisory: true,
8906
+ run: async () => ({
8907
+ state: "skipped",
8908
+ message: `not checked: ${name} declares no skill`
8909
+ })
8910
+ }]
8911
+ });
8912
+ /** A Global install's Skill copy goes under home; any other goes under the project. */
8913
+ function skillOf(cli, install) {
8914
+ const { skill } = cli;
8915
+ if (skill === void 0) return noSkill(cli);
8916
+ return cli.install?.mode === "global" ? homeSkill(cli, skill, install) : projectSkill(cli, skill, install);
8882
8917
  }
8883
8918
  //#endregion
8884
8919
  //#region ../agent-cli/dist/built-ins/doctor-rows.js
@@ -9015,7 +9050,7 @@ function doctorChecks(cli, launch, scope) {
9015
9050
  ...secretRows(cli.secrets ?? [], cli.commands),
9016
9051
  installRow(check),
9017
9052
  registryRow(cli, check),
9018
- ...skillOf$1(cli, install).rows
9053
+ ...skillOf(cli, install).rows
9019
9054
  ],
9020
9055
  tools: new Set(tools.map(({ name }) => name))
9021
9056
  };
@@ -9033,8 +9068,7 @@ const reportSchema = object({
9033
9068
  ]),
9034
9069
  message: string(),
9035
9070
  fix: string().optional(),
9036
- code: string().optional(),
9037
- skill: packagedSkillDetailsSchema.optional()
9071
+ code: string().optional()
9038
9072
  })),
9039
9073
  next: array(string())
9040
9074
  });
@@ -9169,15 +9203,15 @@ function doctorCommand(cli, launch) {
9169
9203
  //#endregion
9170
9204
  //#region ../agent-cli/dist/built-ins/commands.js
9171
9205
  /**
9172
- * The Commands the Runtime adds to a multi-Command CLI: `update` only for a Global install, and
9173
- * `skill install` only for a Packaged skill.
9206
+ * The Commands the Runtime adds to a multi-Command CLI: `setup` and `update` only for a Global
9207
+ * install, and `skill install` only for a skill whose copy goes under the project.
9174
9208
  */
9175
9209
  function builtInCommands(cli, launch) {
9176
9210
  const install = installOf(cli);
9177
9211
  return {
9178
9212
  doctor: doctorCommand(cli, launch),
9179
9213
  ...install.builtIns(launch),
9180
- ...skillOf$1(cli, install).builtIns
9214
+ ...skillOf(cli, install).builtIns
9181
9215
  };
9182
9216
  }
9183
9217
  //#endregion
@@ -9314,6 +9348,7 @@ const RUNTIME_EXIT_CLASSES = {
9314
9348
  PROJECT_INVALID: "operator",
9315
9349
  DOCTOR_PROBLEMS: "operator",
9316
9350
  NOT_GLOBAL_INSTALL: "operator",
9351
+ NOT_ON_PATH: "operator",
9317
9352
  REGISTRY_UNREACHABLE: "operator",
9318
9353
  REGISTRY_AUTH: "operator",
9319
9354
  INSTALL_FAILED: "operator",
@@ -9336,6 +9371,9 @@ function exitClassOfOne(error, errors) {
9336
9371
  function isBug(error, errors) {
9337
9372
  return exitClassOfOne(error, errors) === "bug";
9338
9373
  }
9374
+ function isWait(error, errors) {
9375
+ return exitClassOfOne(error, errors) === "wait";
9376
+ }
9339
9377
  const ACT_FIRST = [
9340
9378
  "caller",
9341
9379
  "operator",
@@ -9387,20 +9425,22 @@ function checkTree(commands, path, table) {
9387
9425
  else checkTree(node, at, table);
9388
9426
  }
9389
9427
  }
9390
- function checkSkillAdd({ name, skill }) {
9391
- if (skill === void 0 || !("add" in skill)) return;
9392
- const declares = `${name} declares skill.add ${JSON.stringify(skill.add)}`;
9393
- if (skill.add.trim() === "") throw new Error(`${declares}, which is empty`);
9394
- if (/[\r\n]/.test(skill.add)) throw new Error(`${declares}, which holds a line break`);
9428
+ function checkSkill({ name, skill, install }) {
9429
+ if (skill?.agents !== void 0 && install?.mode === "global") throw new Error(`${name} declares an Agents section on a Global install, which has no repo to write it to`);
9430
+ }
9431
+ function checkDocs({ name, docs }) {
9432
+ if (docs === void 0) return;
9433
+ 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`);
9395
9434
  }
9396
9435
  function checkCli(cli) {
9397
9436
  const table = errorTableOf(cli);
9398
- checkSkillAdd(cli);
9437
+ checkDocs(cli);
9399
9438
  if (cli.commands === void 0) {
9400
9439
  checkCommand(cli.command, commandLabel([], cli.name).join(" "), table);
9401
- if (skillOf$1(cli, installOf(cli)).kind === "packaged") throw new Error(`${cli.name} declares a Packaged skill, which only a CLI with several Commands can install`);
9440
+ if (cli.skill !== void 0) throw new Error(`${cli.name} declares a skill, which only a CLI with several Commands can install`);
9402
9441
  return cli;
9403
9442
  }
9443
+ checkSkill(cli);
9404
9444
  const { commands } = cli;
9405
9445
  checkTree(commands, [], table);
9406
9446
  const taken = Object.keys(builtInCommands(cli, {
@@ -9504,30 +9544,6 @@ function startSideTask(sideTask, runWith, { clock, failed }) {
9504
9544
  };
9505
9545
  }
9506
9546
  //#endregion
9507
- //#region ../agent-cli/dist/built-ins/skill/usage-hint.js
9508
- const SKILL_USAGE_CODES = /* @__PURE__ */ new Set([
9509
- UNKNOWN_OPTION,
9510
- UNKNOWN_COMMAND,
9511
- INVALID_INPUT
9512
- ]);
9513
- /** A usage error whose hint names each Skill copy that differs from this build, since one may have named the input. */
9514
- async function withDifferingSkill(error, cli, place, runAs) {
9515
- if (!(error instanceof UsageError) || !SKILL_USAGE_CODES.has(error.code)) return error;
9516
- let differing;
9517
- try {
9518
- differing = await skillOf$1(cli, installOf(cli)).differing(place);
9519
- } catch (lookup) {
9520
- if (errnoOf(lookup) === void 0) throw lookup;
9521
- return error;
9522
- }
9523
- if (differing.length === 0) return error;
9524
- return new UsageError({
9525
- code: error.code,
9526
- message: error.message,
9527
- hint: `${error.hint}; the ${cli.name} skill may name what this build does not know: ${differing.join("; ")}; run \`${runAs} doctor\` to see how to update it`
9528
- });
9529
- }
9530
- //#endregion
9531
9547
  //#region ../agent-cli/dist/command-line/suggest.js
9532
9548
  const MAX_DISTANCE = 3;
9533
9549
  const MIN_SIMILARITY = .4;
@@ -10014,7 +10030,7 @@ const EXIT_MEANINGS = {
10014
10030
  caller: "the caller fixes its command",
10015
10031
  operator: "the operator acts on their machine",
10016
10032
  source: "someone at the source acts",
10017
- wait: "nobody acts: wait, then run it again"
10033
+ wait: "not done yet: do what the hint says, then run it again"
10018
10034
  };
10019
10035
  function exitCodeRows(cli) {
10020
10036
  return [
@@ -10198,7 +10214,8 @@ function errorText(error, format) {
10198
10214
  const text = formatRuntimeError(error, {
10199
10215
  ...format,
10200
10216
  runAs: format.invocation.runAs,
10201
- isBug: (each) => isBug(each, format.errors)
10217
+ isBug: (each) => isBug(each, format.errors),
10218
+ isWait: (each) => isWait(each, format.errors)
10202
10219
  });
10203
10220
  const { usageHelp, json } = format;
10204
10221
  return usageHelp !== void 0 && !json && error instanceof UsageError ? `${text}\n${usageHelp}` : text;
@@ -10279,7 +10296,11 @@ function helpResult(node, root, cli, { style, json }) {
10279
10296
  }
10280
10297
  function versionResult(cli, packageRoot) {
10281
10298
  return {
10282
- stdout: `${(buildStamp() === void 0 ? [versionLine(cli)] : [versionLine(cli), `changelog ${join(packageRoot, "CHANGELOG.md")}`]).join("\n")}\n`,
10299
+ stdout: `${[
10300
+ versionLine(cli),
10301
+ ...buildStamp() === void 0 ? [] : [`changelog ${join(packageRoot, "CHANGELOG.md")}`],
10302
+ ...cli.docs === void 0 ? [] : [`docs ${join(packageRoot, cli.docs)}`]
10303
+ ].join("\n")}\n`,
10283
10304
  stderr: "",
10284
10305
  exitCode: SUCCESS_EXIT_CODE
10285
10306
  };
@@ -10346,6 +10367,77 @@ function checkOperator({ operator, commandLine }, env, rerun) {
10346
10367
  });
10347
10368
  }
10348
10369
  //#endregion
10370
+ //#region ../agent-cli/dist/context/outputs.js
10371
+ const OUTPUT_EXISTS = "OUTPUT_EXISTS";
10372
+ /** Refuses a run that would overwrite existing outputs unless the caller passed `--force`. */
10373
+ async function checkOutputs({ rerun, commandLine, force, files }, outputs) {
10374
+ if (outputs === void 0 || force) return;
10375
+ const found = await Promise.all(outputs.map((path) => files.exists(path)));
10376
+ const existing = outputs.filter((_path, index) => found[index]);
10377
+ if (existing.length === 0) return;
10378
+ const them = existing.length === 1 ? "it" : "them";
10379
+ throw new CliError({
10380
+ code: OUTPUT_EXISTS,
10381
+ message: `\`${commandLine}\` would overwrite ${existing.join(", ")}`,
10382
+ hint: `once the user agrees to replace ${them}, run the Next step`,
10383
+ next: [rerun({ add: [FORCE_FLAG.flag] })]
10384
+ });
10385
+ }
10386
+ /** Guards the writes to declared outputs of `files`, whose paths resolve against `cwd`. */
10387
+ function outputsGuard(files, cwd) {
10388
+ const unwritten = /* @__PURE__ */ new Set();
10389
+ let forceStep = [];
10390
+ const exclusiveWrite = async (path, data, options) => {
10391
+ const target = resolve(cwd, path);
10392
+ unwritten.delete(target);
10393
+ try {
10394
+ await files.write(path, data, {
10395
+ ...options,
10396
+ exclusive: true
10397
+ });
10398
+ } catch (error) {
10399
+ unwritten.add(target);
10400
+ if (!(error instanceof CliError) || error.code !== "ALREADY_EXISTS") throw error;
10401
+ throw new CliError({
10402
+ code: ALREADY_EXISTS,
10403
+ message: error.message,
10404
+ hint: "once the user agrees to replace it, run the Next step",
10405
+ next: [forceStep]
10406
+ }, { cause: error });
10407
+ }
10408
+ };
10409
+ return {
10410
+ files: {
10411
+ ...files,
10412
+ write: (path, data, options) => unwritten.has(resolve(cwd, path)) ? exclusiveWrite(path, data, options) : files.write(path, data, options)
10413
+ },
10414
+ guard: (outputs, step) => {
10415
+ forceStep = step;
10416
+ for (const path of outputs) unwritten.add(resolve(cwd, path));
10417
+ }
10418
+ };
10419
+ }
10420
+ /**
10421
+ * Creates each output folder and a temp file in it, so a sandbox denial fails before `apply`
10422
+ * spends anything. The temp file's name is the Runtime's, so a crashed run's leftover is swept.
10423
+ */
10424
+ async function probeOutputs({ files, cwd, fileError }, outputs) {
10425
+ const folders = new Set(outputs.map((path) => dirname(resolve(cwd, path))));
10426
+ await Promise.all([...folders].map(async (folder) => {
10427
+ const probe = tempPathOf(join(folder, "outputs-probe"));
10428
+ try {
10429
+ await files.mkdir(folder);
10430
+ await files.write(probe, "");
10431
+ await files.remove(probe);
10432
+ } catch (error) {
10433
+ throw fileError(error, {
10434
+ path: folder,
10435
+ access: "write"
10436
+ });
10437
+ }
10438
+ }));
10439
+ }
10440
+ //#endregion
10349
10441
  //#region ../agent-cli/dist/context/spill.js
10350
10442
  const STDOUT_BUDGET_CHARS = 3e4;
10351
10443
  const SPILL_RETENTION_MS = 6048e5;
@@ -11238,11 +11330,7 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11238
11330
  });
11239
11331
  });
11240
11332
  } catch (error) {
11241
- return failed(await withDifferingSkill(error, cli, {
11242
- cwd,
11243
- env,
11244
- packageRoot
11245
- }, invocation.runAs), noSideTask);
11333
+ return failed(error, noSideTask);
11246
11334
  }
11247
11335
  const { input, flags } = parsed;
11248
11336
  resumed = command.deadline?.resume(input, rerunOf);
@@ -11374,9 +11462,7 @@ function once(build) {
11374
11462
  };
11375
11463
  }
11376
11464
  async function noticesOf(cli, launch, ctx) {
11377
- const install = installOf(cli);
11378
- await skillOf$1(cli, install).notice(launch, ctx);
11379
- await install.notice(launch, ctx);
11465
+ await installOf(cli).notice(launch, ctx);
11380
11466
  }
11381
11467
  //#endregion
11382
11468
  //#region ../agent-cli/dist/style.js
@@ -11460,16 +11546,10 @@ async function routeAndRun(cli, argv, options, write, stopNotice) {
11460
11546
  ...format,
11461
11547
  json: routed.json
11462
11548
  }));
11463
- case "usage error": {
11464
- const place = {
11465
- ...options,
11466
- packageRoot: packageRootOf(options.bin, options.cwd)
11467
- };
11468
- return ranAlone(errorResult(await withDifferingSkill(routed.error, cli, place, runAs), {
11469
- ...misused(routed.node),
11470
- json: routed.json
11471
- }));
11472
- }
11549
+ case "usage error": return ranAlone(errorResult(routed.error, {
11550
+ ...misused(routed.node),
11551
+ json: routed.json
11552
+ }));
11473
11553
  case "command": return whileStopping(options.signal, stopNotice === void 0 ? void 0 : () => write.stderr(stopLine(stopNotice, routed.json)), () => runCommand(cli, routed, options, write, misused(routed.spec)));
11474
11554
  }
11475
11555
  }
@@ -11532,7 +11612,7 @@ async function runProcess(cli) {
11532
11612
  //#region package.json
11533
11613
  var package_default = {
11534
11614
  name: "@duyquangnvx/create-agent-cli",
11535
- version: "0.23.0",
11615
+ version: "0.25.0",
11536
11616
  description: "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
11537
11617
  type: "module",
11538
11618
  engines: { "node": ">=22.12.0" },
@@ -11581,80 +11661,35 @@ function fill(text, values, quoted) {
11581
11661
  //#region src/install-mode.ts
11582
11662
  const PARTIALS_DIR = fileURLToPath(new URL("../partials/", import.meta.url));
11583
11663
  const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
11584
- const COMMANDS_ANCHOR = " commands: {";
11585
- const GLOBAL_INSTALL = ` install: "global",\n`;
11586
- const NO_SKILL_HUB = "<!-- Set `skill.source` in `defineCli` and `skillHub: { git }` in `scripts/release.ts` to the git URL of the Skill Hub each release commits the skill to, or set `skill.add` to the whole line that installs the skill and `skillHub: { api }` to the API Skill Hub each release publishes the skill to. Then put here what `node dist/bin.js doctor` gives in its `skill` row: the line of its fix, after `! `. -->";
11587
- function skillHubOf({ git, api, add }) {
11588
- if (git !== void 0 && api !== void 0) throw new UsageError({
11589
- code: "SKILL_HOMES_CONFLICT",
11590
- message: "--skill-hub-git and --skill-hub-api each name the Skill Hub the skill goes to",
11591
- hint: "pass one of them"
11592
- });
11593
- if (api !== void 0 && add === void 0) throw new UsageError({
11594
- code: "SKILL_ADD_MISSING",
11595
- message: "--skill-hub-api needs --skill-add, the whole line that installs the skill from the hub",
11596
- hint: "add --skill-add with the hub's install line for the skill"
11597
- });
11598
- if (add !== void 0 && api === void 0) throw new UsageError({
11599
- code: "SKILL_HUB_API_MISSING",
11600
- message: "--skill-add is the install line of an API Skill Hub, which --skill-hub-api names",
11601
- hint: "add --skill-hub-api with the hub's URL"
11602
- });
11603
- if (add !== void 0 && (add.trim() === "" || /[\r\n]/.test(add))) throw new UsageError({
11604
- code: "SKILL_ADD_INVALID",
11605
- message: `--skill-add ${JSON.stringify(add)} is not one command line`,
11606
- hint: "pass the hub's install line for the skill, on one line"
11607
- });
11608
- if (git !== void 0) return {
11609
- kind: "git",
11610
- url: git
11611
- };
11612
- return api === void 0 || add === void 0 ? void 0 : {
11613
- kind: "api",
11614
- url: api,
11615
- add
11616
- };
11617
- }
11618
- function installModeOf(flags, dependency, binName) {
11619
- const skillHub = skillHubOf(flags);
11620
- if (skillHub !== void 0 && dependency) throw new UsageError({
11621
- code: "SKILL_HOMES_CONFLICT",
11622
- message: `--skill-hub-${skillHub.kind} and --dependency each name where the skill comes from: a Dependency install ships it inside the package`,
11623
- hint: `pass --dependency for a CLI each project pins, or --skill-hub-${skillHub.kind} for a global one whose skill each release puts on a Skill Hub`
11624
- });
11625
- if ((skillHub !== void 0 || dependency) && !SKILL_NAME.test(binName)) throw new UsageError({
11664
+ const TEMPLATE_REGISTRY = "https://registry.npmjs.org/";
11665
+ function installModeOf(dependency, binName) {
11666
+ if (!SKILL_NAME.test(binName)) throw new UsageError({
11626
11667
  code: "SKILL_NAME_INVALID",
11627
11668
  message: `${binName} cannot name a skill, whose names are lowercase letters, digits and single hyphens`,
11628
- hint: `choose a name such as my-cli, or leave out ${dependency ? "--dependency" : `--skill-hub-${skillHub?.kind}`}`
11669
+ hint: "choose a name such as my-cli"
11629
11670
  });
11630
- return dependency ? { mode: "dependency" } : {
11631
- mode: "global",
11632
- skillHub
11633
- };
11634
- }
11635
- function skillLineOf(skillHub, binName) {
11636
- if (skillHub === void 0) return NO_SKILL_HUB;
11637
- return skillHub.kind === "git" ? `\`! npx -y skills add ${skillHub.url} --skill ${binName} -g\`` : `\`! ${skillHub.add}\``;
11671
+ return dependency ? "dependency" : "global";
11638
11672
  }
11639
11673
  async function partialsOf(mode, values) {
11640
11674
  const partial = async (name) => fill(await readFile(join(PARTIALS_DIR, mode, `${name}.md`), "utf8"), values, false).trimEnd();
11641
- const [installLines, skillCheck, installSteps, updateSteps, sandbox] = await Promise.all([
11675
+ const [installLines, skillCheck, installSteps, updateSteps, sandbox, whenMissing] = await Promise.all([
11642
11676
  partial("installLines"),
11643
11677
  partial("skillCheck"),
11644
11678
  partial("installSteps"),
11645
11679
  partial("updateSteps"),
11646
- partial("sandbox")
11680
+ partial("sandbox"),
11681
+ partial("whenMissing")
11647
11682
  ]);
11648
11683
  return {
11649
11684
  installLines,
11650
11685
  skillCheck,
11651
11686
  installSteps,
11652
11687
  updateSteps,
11653
- sandbox
11688
+ sandbox,
11689
+ whenMissing
11654
11690
  };
11655
11691
  }
11656
- async function globalSetup(skillHub, packageName, binName) {
11657
- const skillLine = skillLineOf(skillHub, binName);
11692
+ async function globalSetup(packageName, binName) {
11658
11693
  const runAs = runAsOf({ name: binName });
11659
11694
  return {
11660
11695
  values: {
@@ -11664,24 +11699,16 @@ async function globalSetup(skillHub, packageName, binName) {
11664
11699
  packageName,
11665
11700
  binName,
11666
11701
  runAs,
11667
- skillLine
11702
+ setupLine: setupLineOf({
11703
+ mode: "global",
11704
+ package: packageName,
11705
+ registry: TEMPLATE_REGISTRY
11706
+ })
11668
11707
  })
11669
11708
  },
11670
- edits: skillHub === void 0 ? [] : [{
11671
- file: "src/cli.ts",
11672
- find: COMMANDS_ANCHOR,
11673
- replace: ` skill: ${skillOf(skillHub)},\n${COMMANDS_ANCHOR}`
11674
- }, {
11675
- file: "scripts/release.ts",
11676
- find: " smoke: {",
11677
- replace: ` skillHub: { ${skillHub.kind}: ${JSON.stringify(skillHub.url)} },\n smoke: {`
11678
- }],
11679
- files: []
11709
+ edits: []
11680
11710
  };
11681
11711
  }
11682
- function skillOf(skillHub) {
11683
- return skillHub.kind === "git" ? `{ source: ${JSON.stringify(skillHub.url)} }` : `{ add: ${JSON.stringify(skillHub.add)} }`;
11684
- }
11685
11712
  async function dependencySetup(packageName, binName) {
11686
11713
  const runAs = runAsOf({
11687
11714
  name: binName,
@@ -11702,18 +11729,13 @@ async function dependencySetup(packageName, binName) {
11702
11729
  },
11703
11730
  edits: [{
11704
11731
  file: "src/cli.ts",
11705
- find: GLOBAL_INSTALL,
11732
+ find: ` install: "global",\n`,
11706
11733
  replace: ` install: "dependency",\n`
11707
- }, {
11708
- file: "src/cli.ts",
11709
- find: COMMANDS_ANCHOR,
11710
- replace: ` skill: { packaged: "skills/${binName}" },\n${COMMANDS_ANCHOR}`
11711
- }],
11712
- files: ["skills"]
11734
+ }]
11713
11735
  };
11714
11736
  }
11715
11737
  async function installSetupOf(install, packageName, binName) {
11716
- return install.mode === "global" ? globalSetup(install.skillHub, packageName, binName) : dependencySetup(packageName, binName);
11738
+ return install === "global" ? globalSetup(packageName, binName) : dependencySetup(packageName, binName);
11717
11739
  }
11718
11740
  //#endregion
11719
11741
  //#region src/steps.ts
@@ -11866,9 +11888,6 @@ async function editManifest(root, edit) {
11866
11888
  async function setRepository(root, repository) {
11867
11889
  await editManifest(root, (entries) => entries.flatMap((entry) => entry[0] === "description" ? [entry, ["repository", repository]] : [entry]));
11868
11890
  }
11869
- async function addFiles(root, files) {
11870
- await editManifest(root, (entries) => entries.map(([key, value]) => key === "files" && Array.isArray(value) ? [key, [...value, ...files]] : [key, value]));
11871
- }
11872
11891
  async function ensureFree(path) {
11873
11892
  if (!await isFree(path)) throw new UsageError({
11874
11893
  code: "TARGET_NOT_EMPTY",
@@ -11894,18 +11913,11 @@ await runProcess(defineCli({
11894
11913
  install: boolean().default(true).describe("run pnpm install"),
11895
11914
  git: boolean().default(true).describe("create a git repository with an initial commit"),
11896
11915
  repository: url({ protocol: /^https$/ }).optional().describe("the https URL of the CLI's git repository"),
11897
- skillHubGit: url({ protocol: /^https$/ }).refine((url) => new URL(url).search === "" && new URL(url).hash === "", "must name the hub's repository with no query or fragment").refine((url) => new URL(url).pathname.endsWith(".git"), "must end in .git").optional().describe("the https git URL of a git Skill Hub each release commits the CLI's skill to"),
11898
- skillHubApi: url({ protocol: /^https$/ }).optional().describe("the https URL of an API Skill Hub each release publishes the CLI's skill to"),
11899
- skillAdd: string().optional().describe("the whole line that installs the CLI's skill from the API Skill Hub"),
11900
- dependency: boolean().default(false).describe("install the CLI per project, as a devDependency, with its skill shipped inside the package")
11916
+ dependency: boolean().default(false).describe("install the CLI per project, as a devDependency, with its skill copied into the project")
11901
11917
  }),
11902
- plan: async ({ name, description, install, git, repository, skillHubGit, skillHubApi, skillAdd, dependency }, ctx) => {
11918
+ plan: async ({ name, description, install, git, repository, dependency }, ctx) => {
11903
11919
  const binName = PACKAGE_NAME.exec(name)?.[1] ?? name;
11904
- const mode = installModeOf({
11905
- git: skillHubGit,
11906
- api: skillHubApi,
11907
- add: skillAdd
11908
- }, dependency, binName);
11920
+ const mode = installModeOf(dependency, binName);
11909
11921
  const path = resolve(ctx.cwd, binName);
11910
11922
  await ensureFree(path);
11911
11923
  const setup = await installSetupOf(mode, name, binName);
@@ -11928,8 +11940,7 @@ await runProcess(defineCli({
11928
11940
  }),
11929
11941
  install,
11930
11942
  repository,
11931
- edits: setup.edits,
11932
- manifestFiles: setup.files
11943
+ edits: setup.edits
11933
11944
  };
11934
11945
  },
11935
11946
  apply: async (plan, ctx) => {
@@ -11937,7 +11948,6 @@ await runProcess(defineCli({
11937
11948
  await writeTemplate(plan.path, plan.files, plan.values);
11938
11949
  if (plan.repository !== void 0) await setRepository(plan.path, plan.repository);
11939
11950
  for (const edit of plan.edits) await applyEdit(plan.path, edit);
11940
- if (plan.manifestFiles.length > 0) await addFiles(plan.path, plan.manifestFiles);
11941
11951
  await runSteps(plan.steps, plan.path, ctx);
11942
11952
  return {
11943
11953
  path: plan.path,