@duyquangnvx/create-agent-cli 0.23.0 → 0.24.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/CHANGELOG.md +31 -0
- package/README.md +2 -2
- package/dist/bin.js +1031 -1029
- package/package.json +2 -2
- package/partials/dependency/installSteps.md +1 -1
- package/partials/dependency/skillCheck.md +1 -1
- package/partials/dependency/updateSteps.md +2 -2
- package/partials/dependency/whenMissing.md +1 -0
- package/partials/global/installLines.md +3 -3
- package/partials/global/installSteps.md +5 -9
- package/partials/global/sandbox.md +1 -1
- package/partials/global/skillCheck.md +1 -1
- package/partials/global/updateSteps.md +2 -3
- package/partials/global/whenMissing.md +1 -0
- package/template/AGENTS.md +3 -1
- package/template/docs/cli/README.md +34 -0
- package/template/{skills/{{binName}}/references → docs/cli}/setup.md +1 -1
- package/template/package.json +3 -1
- package/template/scripts/release.ts +0 -1
- package/template/skills/{{binName}}/SKILL.md +4 -35
- package/template/src/bin.ts +20 -2
- package/template/src/cli.ts +2 -0
- package/template/test/cli.test.ts +15 -8
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
|
|
@@ -7085,9 +7085,9 @@ function secretRows(secrets, commands) {
|
|
|
7085
7085
|
/** The bundler's stamp, absent when the CLI runs unbundled. Read per call, so a test can define it. */
|
|
7086
7086
|
function buildStamp() {
|
|
7087
7087
|
return {
|
|
7088
|
-
"sha": "
|
|
7088
|
+
"sha": "45caa4e",
|
|
7089
7089
|
"dirty": false,
|
|
7090
|
-
"builtAt": "2026-11-
|
|
7090
|
+
"builtAt": "2026-11-05T07:43:22.000Z"
|
|
7091
7091
|
};
|
|
7092
7092
|
}
|
|
7093
7093
|
/** The first line of `--version`: the version, and the commit and time of a stamped build. */
|
|
@@ -7237,6 +7237,16 @@ function installCommandLine(registry, version = "latest") {
|
|
|
7237
7237
|
...registry.flags
|
|
7238
7238
|
].join(" ");
|
|
7239
7239
|
}
|
|
7240
|
+
/** The line that sets a Global install up, run before the CLI is on the machine. */
|
|
7241
|
+
function setupCommandLine({ package: name, flags }) {
|
|
7242
|
+
return [
|
|
7243
|
+
"npx",
|
|
7244
|
+
"-y",
|
|
7245
|
+
...flags,
|
|
7246
|
+
`${name}@latest`,
|
|
7247
|
+
"setup"
|
|
7248
|
+
].join(" ");
|
|
7249
|
+
}
|
|
7240
7250
|
function npmOf(ctx, windows) {
|
|
7241
7251
|
const beside = ctx.node === void 0 ? void 0 : join(dirname(ctx.node), windows ? "npm.cmd" : "npm");
|
|
7242
7252
|
if (beside === void 0 || !existsSync(beside)) return {
|
|
@@ -7280,13 +7290,13 @@ function failureOf(call, answer, ctx) {
|
|
|
7280
7290
|
const code = NPM_CODE.exec(answer.stderr)?.[1];
|
|
7281
7291
|
const output = tail$1(answer.stderr);
|
|
7282
7292
|
const next = [ctx.rerun];
|
|
7283
|
-
if (code !== void 0 && NETWORK_CODES.has(code)) return
|
|
7293
|
+
if (code !== void 0 && NETWORK_CODES.has(code)) return runtimeError({
|
|
7284
7294
|
code: REGISTRY_UNREACHABLE,
|
|
7285
7295
|
message: `npm could not reach ${registry} (${code})${output}`,
|
|
7286
7296
|
hint: `${unreachableHint}, then ${RUN_AGAIN}`,
|
|
7287
7297
|
next
|
|
7288
7298
|
});
|
|
7289
|
-
if (code !== void 0 && AUTH_CODES.has(code)) return
|
|
7299
|
+
if (code !== void 0 && AUTH_CODES.has(code)) return runtimeError({
|
|
7290
7300
|
code: REGISTRY_AUTH,
|
|
7291
7301
|
message: `${registry} refused npm's credentials (${code})${output}`,
|
|
7292
7302
|
hint: authHint(code, ctx),
|
|
@@ -7299,7 +7309,7 @@ function failureOf(call, answer, ctx) {
|
|
|
7299
7309
|
hint: `fix what npm reports, then ${RUN_AGAIN} with the first Next step, or install it yourself with the second`,
|
|
7300
7310
|
next: [...next, wholeLine(installCommandLine(ctx.registry))]
|
|
7301
7311
|
});
|
|
7302
|
-
return
|
|
7312
|
+
return runtimeError({
|
|
7303
7313
|
code: NPM_FAILED,
|
|
7304
7314
|
message: `npm ${call} failed with exit code ${answer.exitCode}${output}`,
|
|
7305
7315
|
hint: `fix what npm reports, then ${RUN_AGAIN}`,
|
|
@@ -7623,183 +7633,6 @@ function dependencyInstall(cli, install) {
|
|
|
7623
7633
|
};
|
|
7624
7634
|
}
|
|
7625
7635
|
//#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
7636
|
//#region ../agent-cli/dist/context/errno.js
|
|
7804
7637
|
const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
7805
7638
|
const DISK_FULL = "DISK_FULL";
|
|
@@ -7862,565 +7695,84 @@ function classifyErrno(error, context) {
|
|
|
7862
7695
|
}
|
|
7863
7696
|
}
|
|
7864
7697
|
//#endregion
|
|
7865
|
-
//#region ../agent-cli/dist/context/
|
|
7866
|
-
const
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7698
|
+
//#region ../agent-cli/dist/context/interrupts.js
|
|
7699
|
+
const INTERRUPTED = "INTERRUPTED";
|
|
7700
|
+
const DEADLINE_EXCEEDED = "DEADLINE_EXCEEDED";
|
|
7701
|
+
/** How long an interrupted Command may take to settle before the run ends without it. */
|
|
7702
|
+
const GRACE_MS = 3e3;
|
|
7703
|
+
const GRACE_OVER = Symbol("grace over");
|
|
7704
|
+
function interruptedError(rerun) {
|
|
7705
|
+
return new CliError({
|
|
7706
|
+
code: INTERRUPTED,
|
|
7707
|
+
message: "interrupted before it finished",
|
|
7708
|
+
hint: "to finish it, run the Next step, unless it was stopped on purpose; if a tool timeout stopped it, give it longer",
|
|
7709
|
+
next: [rerun]
|
|
7710
|
+
});
|
|
7870
7711
|
}
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
}
|
|
7712
|
+
const BOUND_WORDS = {
|
|
7713
|
+
deadline: "deadline",
|
|
7714
|
+
ignoreStop: "timeout"
|
|
7715
|
+
};
|
|
7716
|
+
function waitHint(bound) {
|
|
7717
|
+
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
7718
|
}
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7719
|
+
function deadlineError(commandLine, ms, advice, bound = "deadline") {
|
|
7720
|
+
return new CliError({
|
|
7721
|
+
code: DEADLINE_EXCEEDED,
|
|
7722
|
+
message: `${commandLine} ran past its ${ms} ms ${BOUND_WORDS[bound]}`,
|
|
7723
|
+
...advice
|
|
7883
7724
|
});
|
|
7884
|
-
await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
|
|
7885
7725
|
}
|
|
7886
|
-
/**
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7726
|
+
/** One Command run's cancellation: its signal, its deadlines and the steps it must finish. */
|
|
7727
|
+
var RunControl = class {
|
|
7728
|
+
signal;
|
|
7729
|
+
#controller = new AbortController();
|
|
7730
|
+
#commandLine;
|
|
7731
|
+
#rerun;
|
|
7732
|
+
#outer;
|
|
7733
|
+
#onOuterAbort = () => this.#controller.abort(interruptedError(this.#rerun));
|
|
7734
|
+
/** Each running must-finish step, tracked until it settles, with its name. */
|
|
7735
|
+
#steps = /* @__PURE__ */ new Map();
|
|
7736
|
+
#progress;
|
|
7737
|
+
#clock;
|
|
7738
|
+
#disposed = new AbortController();
|
|
7739
|
+
/** The reasons this run's deadlines aborted with, to tell them in a Command's error. */
|
|
7740
|
+
#abortReasons = /* @__PURE__ */ new WeakSet();
|
|
7741
|
+
#graceCancel = new AbortController();
|
|
7742
|
+
#endGrace = () => {};
|
|
7743
|
+
#graceOver = new Promise((resolve) => {
|
|
7744
|
+
this.#endGrace = resolve;
|
|
7745
|
+
});
|
|
7746
|
+
constructor({ commandLine, rerun, outer, clock, progress, runDeadline }) {
|
|
7747
|
+
this.#commandLine = commandLine;
|
|
7748
|
+
this.#rerun = rerun;
|
|
7749
|
+
this.#outer = outer;
|
|
7750
|
+
this.#clock = clock;
|
|
7751
|
+
this.#progress = progress;
|
|
7752
|
+
this.signal = this.#controller.signal;
|
|
7753
|
+
this.signal.addEventListener("abort", () => this.#onStop(), { once: true });
|
|
7754
|
+
if (outer?.aborted === true) this.#onOuterAbort();
|
|
7755
|
+
else outer?.addEventListener("abort", this.#onOuterAbort, { once: true });
|
|
7756
|
+
if (runDeadline !== void 0) {
|
|
7757
|
+
const { ms, advice } = runDeadline;
|
|
7758
|
+
after(clock, ms, this.#disposed.signal, () => this.#controller.abort(deadlineError(commandLine, ms, advice())));
|
|
7759
|
+
}
|
|
7899
7760
|
}
|
|
7900
|
-
|
|
7901
|
-
|
|
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);
|
|
7761
|
+
deadline(ms, options = {}) {
|
|
7762
|
+
return options.ignoreStop === true ? this.#abortsAfter(ms, "ignoreStop") : AbortSignal.any([this.signal, this.#abortsAfter(ms, "deadline")]);
|
|
7916
7763
|
}
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
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;
|
|
7764
|
+
#abortsAfter(ms, bound) {
|
|
7765
|
+
const controller = new AbortController();
|
|
7766
|
+
after(this.#clock, ms, this.#disposed.signal, () => {
|
|
7767
|
+
const advice = {
|
|
7768
|
+
hint: waitHint(bound),
|
|
7769
|
+
next: [this.#rerun]
|
|
7770
|
+
};
|
|
7771
|
+
const reason = deadlineError(this.#commandLine, ms, advice, bound);
|
|
7772
|
+
this.#abortReasons.add(reason);
|
|
7773
|
+
controller.abort(reason);
|
|
7774
|
+
});
|
|
7775
|
+
return controller.signal;
|
|
8424
7776
|
}
|
|
8425
7777
|
async runToEnd(name, work) {
|
|
8426
7778
|
this.#graceCancel.abort();
|
|
@@ -8492,52 +7844,185 @@ const NOT_GLOBAL_INSTALL = "NOT_GLOBAL_INSTALL";
|
|
|
8492
7844
|
function globalModulesDir(prefix) {
|
|
8493
7845
|
return process.platform === "win32" ? join(prefix, "node_modules") : join(prefix, "lib", "node_modules");
|
|
8494
7846
|
}
|
|
8495
|
-
function notGlobal(ctx, message) {
|
|
8496
|
-
return runtimeError({
|
|
8497
|
-
code: NOT_GLOBAL_INSTALL,
|
|
8498
|
-
message,
|
|
8499
|
-
hint: "install it
|
|
8500
|
-
next: [wholeLine(
|
|
7847
|
+
function notGlobal(ctx, message) {
|
|
7848
|
+
return runtimeError({
|
|
7849
|
+
code: NOT_GLOBAL_INSTALL,
|
|
7850
|
+
message,
|
|
7851
|
+
hint: "install it by running the Next step, so its update Command can replace it",
|
|
7852
|
+
next: [wholeLine(setupCommandLine(ctx.registry))]
|
|
7853
|
+
});
|
|
7854
|
+
}
|
|
7855
|
+
/**
|
|
7856
|
+
* The global npm install this copy runs from, found by asking npm for its global prefix.
|
|
7857
|
+
* `bin` is the script that started the CLI; throws `NOT_GLOBAL_INSTALL` when it is not inside that install.
|
|
7858
|
+
*/
|
|
7859
|
+
async function globalInstallOf(bin, ctx) {
|
|
7860
|
+
if (bin === void 0) throw notGlobal(ctx, `${ctx.cliName} runs from source, not from an npm install`);
|
|
7861
|
+
const prefix = await globalPrefix(ctx);
|
|
7862
|
+
const packageDir = join(globalModulesDir(prefix), ctx.registry.package);
|
|
7863
|
+
const installed = await orMissing(realpath(packageDir));
|
|
7864
|
+
const here = await orMissing(realpath(bin)) ?? bin;
|
|
7865
|
+
if (installed === void 0 || !isInside(installed, here)) throw notGlobal(ctx, `${ctx.cliName} runs from ${here}, not from npm's global install at ${packageDir}`);
|
|
7866
|
+
if ((await lstat(packageDir)).isSymbolicLink()) throw notGlobal(ctx, `${ctx.cliName} runs from a link to ${installed} (npm link), not from an npm install`);
|
|
7867
|
+
return {
|
|
7868
|
+
prefix,
|
|
7869
|
+
packageDir
|
|
7870
|
+
};
|
|
7871
|
+
}
|
|
7872
|
+
const NPMJS = "https://registry.npmjs.org/";
|
|
7873
|
+
/** The flag that points npm at the CLI's registry; none for npmjs, npm's default. */
|
|
7874
|
+
function registryFlags({ package: name, registry }) {
|
|
7875
|
+
if (registry === NPMJS) return [];
|
|
7876
|
+
const scope = scopeOf(name);
|
|
7877
|
+
return [scope === void 0 ? `--registry=${registry}` : `--${scope}:registry=${registry}`];
|
|
7878
|
+
}
|
|
7879
|
+
/** The line that sets a Global install up, run before the CLI is on the machine. */
|
|
7880
|
+
function setupLineOf(install) {
|
|
7881
|
+
return setupCommandLine(globalRegistry(install));
|
|
7882
|
+
}
|
|
7883
|
+
function globalRegistry(install) {
|
|
7884
|
+
const { package: name, registry } = install;
|
|
7885
|
+
return {
|
|
7886
|
+
package: name,
|
|
7887
|
+
label: registry,
|
|
7888
|
+
flags: registryFlags(install),
|
|
7889
|
+
unreachableHint: "check the network or VPN",
|
|
7890
|
+
loginHint: `log in to ${registry} (\`npm login --registry=${registry}\`) or set its token in ~/.npmrc`,
|
|
7891
|
+
missingHint: `check that ${name} is published on ${registry}`
|
|
7892
|
+
};
|
|
7893
|
+
}
|
|
7894
|
+
/** npm for a Global install, run in the working directory. */
|
|
7895
|
+
function globalNpm(cli, install, ctx, node) {
|
|
7896
|
+
return npmContext(cli, globalRegistry(install), ctx, node, ctx.cwd);
|
|
7897
|
+
}
|
|
7898
|
+
//#endregion
|
|
7899
|
+
//#region ../agent-cli/dist/built-ins/skill/sync.js
|
|
7900
|
+
/** Every file under `dir`, relative to it and sorted; none when `dir` is missing. */
|
|
7901
|
+
async function filesIn(dir) {
|
|
7902
|
+
return (await orMissing(readdir(dir, {
|
|
7903
|
+
recursive: true,
|
|
7904
|
+
withFileTypes: true
|
|
7905
|
+
})) ?? []).filter((entry) => entry.isFile()).map((entry) => relative(dir, join(entry.parentPath, entry.name))).sort();
|
|
7906
|
+
}
|
|
7907
|
+
async function sameContent(left, right) {
|
|
7908
|
+
const [a, b] = await Promise.all([readFile(left), readFile(right)]);
|
|
7909
|
+
return a.equals(b);
|
|
7910
|
+
}
|
|
7911
|
+
async function syncOf(source, copy) {
|
|
7912
|
+
const [shipped, present] = await Promise.all([filesIn(source), filesIn(copy)]);
|
|
7913
|
+
const differs = await Promise.all(shipped.map(async (file) => present.includes(file) && !await sameContent(join(source, file), join(copy, file))));
|
|
7914
|
+
return {
|
|
7915
|
+
source,
|
|
7916
|
+
copy,
|
|
7917
|
+
write: shipped.filter((file) => !present.includes(file)),
|
|
7918
|
+
replace: shipped.filter((_file, index) => differs[index]),
|
|
7919
|
+
remove: present.filter((file) => !shipped.includes(file))
|
|
7920
|
+
};
|
|
7921
|
+
}
|
|
7922
|
+
async function applySync(sync, ctx) {
|
|
7923
|
+
for (const file of [...sync.write, ...sync.replace]) await ctx.files.write(join(sync.copy, file), await readFile(join(sync.source, file)));
|
|
7924
|
+
for (const file of sync.remove) await ctx.files.remove(join(sync.copy, file));
|
|
7925
|
+
}
|
|
7926
|
+
//#endregion
|
|
7927
|
+
//#region ../agent-cli/dist/built-ins/skill/home.js
|
|
7928
|
+
const SKILL_DIRS = [[".claude", "skills"], [".agents", "skills"]];
|
|
7929
|
+
const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
7930
|
+
const METADATA_VERSION = /^metadata:[ \t]*\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+version:[ \t]*["']?([^"'\s]+)["']?[ \t]*$/m;
|
|
7931
|
+
/** The version a SKILL.md's frontmatter `metadata.version` states. */
|
|
7932
|
+
function skillVersionIn(skill) {
|
|
7933
|
+
const frontmatter = FRONTMATTER.exec(skill)?.[1];
|
|
7934
|
+
return frontmatter === void 0 ? void 0 : METADATA_VERSION.exec(`${frontmatter}\n`)?.[1];
|
|
7935
|
+
}
|
|
7936
|
+
/** How the row words a copy that states a version, which only a skill from before CLI docs does. */
|
|
7937
|
+
function statedVersionNote(version) {
|
|
7938
|
+
return `states metadata.version ${version}: an older skill that CLI docs replace`;
|
|
7939
|
+
}
|
|
7940
|
+
function homeOf(env) {
|
|
7941
|
+
return env.HOME ?? env.USERPROFILE ?? homedir();
|
|
7942
|
+
}
|
|
7943
|
+
/** The home folders of the Skill copy; the skills installer may have made one a link to the other. */
|
|
7944
|
+
async function homeCopiesOf(name, env) {
|
|
7945
|
+
const paths = SKILL_DIRS.map((dir) => join(homeOf(env), ...dir, name));
|
|
7946
|
+
const reals = await Promise.all(paths.map(async (path) => orMissing(realpath(path))));
|
|
7947
|
+
return paths.filter((_path, index) => {
|
|
7948
|
+
const real = reals[index];
|
|
7949
|
+
return real === void 0 || reals.indexOf(real) === index;
|
|
8501
7950
|
});
|
|
8502
7951
|
}
|
|
7952
|
+
const homeSyncSchema = object({
|
|
7953
|
+
copies: array(string()),
|
|
7954
|
+
written: array(string()),
|
|
7955
|
+
removed: array(string())
|
|
7956
|
+
});
|
|
8503
7957
|
/**
|
|
8504
|
-
*
|
|
8505
|
-
*
|
|
7958
|
+
* Makes each home Skill copy match the folder in the package at `packageDir`. Null when the CLI
|
|
7959
|
+
* declares no skill, or that package ships none, which must not empty a copy.
|
|
8506
7960
|
*/
|
|
8507
|
-
async function
|
|
8508
|
-
if (
|
|
8509
|
-
const
|
|
8510
|
-
|
|
8511
|
-
const
|
|
8512
|
-
const
|
|
8513
|
-
|
|
8514
|
-
if ((await lstat(packageDir)).isSymbolicLink()) throw notGlobal(ctx, `${ctx.cliName} runs from a link to ${installed} (npm link), not from an npm install`);
|
|
7961
|
+
async function syncHome(cli, packageDir, ctx) {
|
|
7962
|
+
if (cli.skill === void 0) return null;
|
|
7963
|
+
const source = join(packageDir, cli.skill.folder);
|
|
7964
|
+
if ((await filesIn(source)).length === 0) return null;
|
|
7965
|
+
const copies = await homeCopiesOf(cli.name, ctx.env);
|
|
7966
|
+
const syncs = await Promise.all(copies.map((copy) => syncOf(source, copy)));
|
|
7967
|
+
for (const sync of syncs) await applySync(sync, ctx);
|
|
8515
7968
|
return {
|
|
8516
|
-
|
|
8517
|
-
|
|
7969
|
+
copies,
|
|
7970
|
+
written: syncs.flatMap((sync) => [...sync.write, ...sync.replace]),
|
|
7971
|
+
removed: syncs.flatMap((sync) => sync.remove)
|
|
8518
7972
|
};
|
|
8519
7973
|
}
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
if (registry === NPMJS) return [];
|
|
8524
|
-
const scope = scopeOf(name);
|
|
8525
|
-
return [scope === void 0 ? `--registry=${registry}` : `--${scope}:registry=${registry}`];
|
|
7974
|
+
/** What a dry run says of the copies, since the release's files may not be on disk yet. */
|
|
7975
|
+
function renderPlannedSync(name, copies) {
|
|
7976
|
+
return copies.length === 0 ? [] : [`would make the ${name} skill at ${copies.join(", ")} match the release`];
|
|
8526
7977
|
}
|
|
8527
|
-
function
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
7978
|
+
function renderHomeSync(name, sync) {
|
|
7979
|
+
if (sync === null) return [];
|
|
7980
|
+
const at = sync.copies.join(", ");
|
|
7981
|
+
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}`];
|
|
7982
|
+
}
|
|
7983
|
+
async function homeCopyAt(path) {
|
|
7984
|
+
try {
|
|
7985
|
+
return {
|
|
7986
|
+
path,
|
|
7987
|
+
state: "readable",
|
|
7988
|
+
stated: skillVersionIn(await readFile(join(path, "SKILL.md"), "utf8"))
|
|
7989
|
+
};
|
|
7990
|
+
} catch (error) {
|
|
7991
|
+
return {
|
|
7992
|
+
path,
|
|
7993
|
+
state: isMissingFile(error) ? "missing" : "unreadable"
|
|
7994
|
+
};
|
|
7995
|
+
}
|
|
7996
|
+
}
|
|
7997
|
+
function describeCopy(copy) {
|
|
7998
|
+
if (copy.state === "missing") return `no copy at ${copy.path}`;
|
|
7999
|
+
if (copy.state !== "readable") return `${copy.path} (could not be read)`;
|
|
8000
|
+
return copy.stated === void 0 ? copy.path : `${copy.path} (${statedVersionNote(copy.stated)})`;
|
|
8001
|
+
}
|
|
8002
|
+
async function findingOf$1(cli, folder, install, ctx) {
|
|
8003
|
+
const paths = await homeCopiesOf(cli.name, ctx.env);
|
|
8004
|
+
const [copies, shipped] = await Promise.all([Promise.all(paths.map(homeCopyAt)), orMissing(readFile(join(ctx.packageRoot, folder, "SKILL.md"), "utf8"))]);
|
|
8005
|
+
const message = copies.map(describeCopy).join("; ");
|
|
8006
|
+
const shipsStated = shipped !== void 0 && skillVersionIn(shipped) !== void 0;
|
|
8007
|
+
return copies.every((copy) => copy.state === "readable" && (copy.stated === void 0 || shipsStated)) ? {
|
|
8008
|
+
state: "ok",
|
|
8009
|
+
message
|
|
8010
|
+
} : {
|
|
8011
|
+
state: "problem",
|
|
8012
|
+
message,
|
|
8013
|
+
fix: `${install.runAs} setup`
|
|
8536
8014
|
};
|
|
8537
8015
|
}
|
|
8538
|
-
/**
|
|
8539
|
-
function
|
|
8540
|
-
return
|
|
8016
|
+
/** A skill the package ships, which `setup` and `update` copy under home. */
|
|
8017
|
+
function homeSkill(cli, { folder }, install) {
|
|
8018
|
+
return {
|
|
8019
|
+
builtIns: {},
|
|
8020
|
+
rows: [{
|
|
8021
|
+
name: "skill",
|
|
8022
|
+
advisory: true,
|
|
8023
|
+
run: (ctx) => findingOf$1(cli, folder, install, ctx)
|
|
8024
|
+
}]
|
|
8025
|
+
};
|
|
8541
8026
|
}
|
|
8542
8027
|
//#endregion
|
|
8543
8028
|
//#region ../agent-cli/dist/built-ins/upgrades.js
|
|
@@ -8604,6 +8089,147 @@ async function upgradesOf(packageDir, from, to) {
|
|
|
8604
8089
|
}
|
|
8605
8090
|
return releasesIn(changelog).filter(({ version }) => compareVersions(version, from) > 0 && compareVersions(version, to) <= 0).sort((a, b) => compareVersions(a.version, b.version)).flatMap(upgradeOf);
|
|
8606
8091
|
}
|
|
8092
|
+
function renderUpgrade(upgrade) {
|
|
8093
|
+
return "steps" in upgrade ? [`\nupgrade steps for ${upgrade.version}:`, ...upgrade.steps.map((step) => `- ${step}`)].join("\n") : `\nchanges in ${upgrade.version}:\n${upgrade.notes}`;
|
|
8094
|
+
}
|
|
8095
|
+
//#endregion
|
|
8096
|
+
//#region ../agent-cli/dist/built-ins/setup.js
|
|
8097
|
+
const NOT_ON_PATH = "NOT_ON_PATH";
|
|
8098
|
+
const setupSchema = object({
|
|
8099
|
+
version: string(),
|
|
8100
|
+
prefix: string(),
|
|
8101
|
+
installed: boolean(),
|
|
8102
|
+
skill: homeSyncSchema.nullable(),
|
|
8103
|
+
upgrades: array(upgradeSchema),
|
|
8104
|
+
docs: string().nullable()
|
|
8105
|
+
});
|
|
8106
|
+
const installedManifest = object({ version: string() });
|
|
8107
|
+
async function versionAt(packageDir) {
|
|
8108
|
+
const text = await orMissing(readFile(join(packageDir, "package.json"), "utf8"));
|
|
8109
|
+
if (text === void 0) return null;
|
|
8110
|
+
try {
|
|
8111
|
+
const parsed = installedManifest.safeParse(JSON.parse(text));
|
|
8112
|
+
return parsed.success ? parsed.data.version : null;
|
|
8113
|
+
} catch (error) {
|
|
8114
|
+
if (error instanceof SyntaxError) return null;
|
|
8115
|
+
throw error;
|
|
8116
|
+
}
|
|
8117
|
+
}
|
|
8118
|
+
function npmOfSetup(cli, install, ctx, node) {
|
|
8119
|
+
return {
|
|
8120
|
+
...globalNpm(cli, install, ctx, node),
|
|
8121
|
+
rerun: wholeLine(setupLineOf(install))
|
|
8122
|
+
};
|
|
8123
|
+
}
|
|
8124
|
+
function needsInstall({ from, latest }) {
|
|
8125
|
+
return from === null || standingOf$1(from, latest) === "behind";
|
|
8126
|
+
}
|
|
8127
|
+
function binDirOf(prefix) {
|
|
8128
|
+
return process.platform === "win32" ? prefix : join(prefix, "bin");
|
|
8129
|
+
}
|
|
8130
|
+
async function canonical(path) {
|
|
8131
|
+
return normalize(await realpath(path).catch(() => path));
|
|
8132
|
+
}
|
|
8133
|
+
async function assertOnPath(cli, prefix, node, ctx) {
|
|
8134
|
+
const binDir = binDirOf(prefix);
|
|
8135
|
+
const wanted = await canonical(binDir);
|
|
8136
|
+
const entries = pathOf(ctx.env).split(delimiter).filter(Boolean);
|
|
8137
|
+
if ((await Promise.all(entries.map(canonical))).includes(wanted)) return;
|
|
8138
|
+
throw runtimeError({
|
|
8139
|
+
code: NOT_ON_PATH,
|
|
8140
|
+
message: `${cli.name} is installed in ${binDir}, by the npm of ${node ?? "the Node on PATH"}, a folder not on this shell's PATH`,
|
|
8141
|
+
hint: `add ${binDir} to PATH, or ask your user which Node their terminal runs and run this under it`,
|
|
8142
|
+
next: []
|
|
8143
|
+
});
|
|
8144
|
+
}
|
|
8145
|
+
const REFUSED = /* @__PURE__ */ new Set([
|
|
8146
|
+
PERMISSION_DENIED,
|
|
8147
|
+
DISK_FULL,
|
|
8148
|
+
INSTALL_FAILED
|
|
8149
|
+
]);
|
|
8150
|
+
function withSetupLine(error, install) {
|
|
8151
|
+
if (!(error instanceof CliError) || !REFUSED.has(error.code)) return error;
|
|
8152
|
+
return runtimeError({
|
|
8153
|
+
code: error.code,
|
|
8154
|
+
message: error.message,
|
|
8155
|
+
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,
|
|
8156
|
+
next: [wholeLine(setupLineOf(install))]
|
|
8157
|
+
}, { cause: error });
|
|
8158
|
+
}
|
|
8159
|
+
function renderSetup(cli, install, setup) {
|
|
8160
|
+
const { version, prefix, docs } = setup;
|
|
8161
|
+
return [
|
|
8162
|
+
setup.installed ? `installed ${install.package}@${version} with the npm under ${prefix}` : `${install.package}@${version} is already installed under ${prefix}`,
|
|
8163
|
+
...renderHomeSync(cli.name, setup.skill),
|
|
8164
|
+
...setup.upgrades.map(renderUpgrade),
|
|
8165
|
+
...docs === null ? [] : [`docs ${docs}`, `Read that folder before using ${cli.name}.`]
|
|
8166
|
+
].join("\n");
|
|
8167
|
+
}
|
|
8168
|
+
/** The built-in `setup`, which runs from any copy, such as npx's, so it never asks to be the global one. */
|
|
8169
|
+
function setupCommand(cli, install, { node }) {
|
|
8170
|
+
return defineCommand$1({
|
|
8171
|
+
description: "install this CLI and its skill on this machine",
|
|
8172
|
+
examples: [{
|
|
8173
|
+
command: `${cli.name} setup`,
|
|
8174
|
+
note: "install the latest version and the skill"
|
|
8175
|
+
}, {
|
|
8176
|
+
command: `${cli.name} setup --dry-run`,
|
|
8177
|
+
note: "show what it would change"
|
|
8178
|
+
}],
|
|
8179
|
+
effect: "write",
|
|
8180
|
+
output: setupSchema,
|
|
8181
|
+
plan: async (_input, ctx) => {
|
|
8182
|
+
const npm = npmOfSetup(cli, install, ctx, node);
|
|
8183
|
+
const prefix = await globalPrefix(npm);
|
|
8184
|
+
const packageDir = join(globalModulesDir(prefix), install.package);
|
|
8185
|
+
if ((await orMissing(lstat(packageDir)))?.isSymbolicLink() === true) throw runtimeError({
|
|
8186
|
+
code: NOT_GLOBAL_INSTALL,
|
|
8187
|
+
message: `${packageDir} is a link (npm link), which an install would replace`,
|
|
8188
|
+
hint: "remove the link with npm unlink, then run the Next step",
|
|
8189
|
+
next: [wholeLine(setupLineOf(install))]
|
|
8190
|
+
});
|
|
8191
|
+
return {
|
|
8192
|
+
package: install.package,
|
|
8193
|
+
from: await versionAt(packageDir),
|
|
8194
|
+
latest: await latestVersion(npm),
|
|
8195
|
+
prefix,
|
|
8196
|
+
packageDir,
|
|
8197
|
+
copies: cli.skill === void 0 ? [] : await homeCopiesOf(cli.name, ctx.env)
|
|
8198
|
+
};
|
|
8199
|
+
},
|
|
8200
|
+
apply: async (plan, ctx) => {
|
|
8201
|
+
const { from, latest, prefix, packageDir } = plan;
|
|
8202
|
+
const installs = needsInstall(plan);
|
|
8203
|
+
const skill = await ctx.runToEnd(`installing ${plan.package}@${latest}`, async () => {
|
|
8204
|
+
if (installs) {
|
|
8205
|
+
ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
|
|
8206
|
+
await installGlobally(latest, npmOfSetup(cli, install, ctx, node));
|
|
8207
|
+
}
|
|
8208
|
+
return syncHome(cli, packageDir, ctx);
|
|
8209
|
+
}).catch((error) => {
|
|
8210
|
+
throw withSetupLine(error, install);
|
|
8211
|
+
});
|
|
8212
|
+
const setup = {
|
|
8213
|
+
version: installs || from === null ? latest : from,
|
|
8214
|
+
prefix,
|
|
8215
|
+
installed: installs,
|
|
8216
|
+
skill,
|
|
8217
|
+
upgrades: installs && from !== null ? await upgradesOf(packageDir, from, latest) : [],
|
|
8218
|
+
docs: cli.docs === void 0 ? null : join(packageDir, cli.docs)
|
|
8219
|
+
};
|
|
8220
|
+
ctx.deliver(setup);
|
|
8221
|
+
if (ctx.signal.aborted) throw new CliError({
|
|
8222
|
+
code: INTERRUPTED,
|
|
8223
|
+
message: `interrupted, but ${plan.package}@${setup.version} and its skill are already in place`,
|
|
8224
|
+
hint: "nothing to run again"
|
|
8225
|
+
});
|
|
8226
|
+
await assertOnPath(cli, prefix, node, ctx);
|
|
8227
|
+
return setup;
|
|
8228
|
+
},
|
|
8229
|
+
render: (setup) => renderSetup(cli, install, setup),
|
|
8230
|
+
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")
|
|
8231
|
+
});
|
|
8232
|
+
}
|
|
8607
8233
|
//#endregion
|
|
8608
8234
|
//#region ../agent-cli/dist/built-ins/update.js
|
|
8609
8235
|
const updateSchema = object({
|
|
@@ -8613,34 +8239,30 @@ const updateSchema = object({
|
|
|
8613
8239
|
version: string(),
|
|
8614
8240
|
prefix: string()
|
|
8615
8241
|
}).nullable(),
|
|
8616
|
-
|
|
8617
|
-
|
|
8242
|
+
skill: homeSyncSchema.nullable(),
|
|
8243
|
+
upgrades: array(upgradeSchema)
|
|
8618
8244
|
});
|
|
8619
8245
|
function nothingToInstall({ from, latest }) {
|
|
8620
8246
|
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
8247
|
}
|
|
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
8248
|
function renderUpdate(cli, install, update) {
|
|
8626
8249
|
const { installed } = update;
|
|
8627
|
-
const
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
...
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
]).join("\n");
|
|
8250
|
+
const skill = renderHomeSync(cli.name, update.skill);
|
|
8251
|
+
return installed === null ? [nothingToInstall(update), ...skill].join("\n") : [
|
|
8252
|
+
`installed ${install.package}@${installed.version} with the npm under ${installed.prefix}, replacing ${update.from}`,
|
|
8253
|
+
...skill,
|
|
8254
|
+
...update.upgrades.map(renderUpgrade)
|
|
8255
|
+
].join("\n");
|
|
8634
8256
|
}
|
|
8635
8257
|
function interruptedAfterInstall(plan) {
|
|
8636
8258
|
return new CliError({
|
|
8637
8259
|
code: INTERRUPTED,
|
|
8638
8260
|
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
|
|
8261
|
+
hint: "nothing to run again"
|
|
8640
8262
|
});
|
|
8641
8263
|
}
|
|
8642
8264
|
/** The built-in `update`, for a CLI installed globally with npm. */
|
|
8643
|
-
function updateCommand(cli, install,
|
|
8265
|
+
function updateCommand(cli, install, { bin, node }) {
|
|
8644
8266
|
return defineCommand$1({
|
|
8645
8267
|
description: "install the latest version of this CLI from its registry",
|
|
8646
8268
|
examples: [{
|
|
@@ -8660,40 +8282,217 @@ function updateCommand(cli, install, skill, { bin, node }) {
|
|
|
8660
8282
|
from: cli.version,
|
|
8661
8283
|
latest: await latestVersion(npm),
|
|
8662
8284
|
prefix: copy.prefix,
|
|
8663
|
-
packageDir: copy.packageDir
|
|
8285
|
+
packageDir: copy.packageDir,
|
|
8286
|
+
copies: cli.skill === void 0 ? [] : await homeCopiesOf(cli.name, ctx.env)
|
|
8287
|
+
};
|
|
8288
|
+
},
|
|
8289
|
+
apply: async (plan, ctx) => {
|
|
8290
|
+
const { from, latest, prefix } = plan;
|
|
8291
|
+
if (standingOf$1(from, latest) !== "behind") return {
|
|
8292
|
+
from,
|
|
8293
|
+
latest,
|
|
8294
|
+
installed: null,
|
|
8295
|
+
skill: await syncHome(cli, plan.packageDir, ctx),
|
|
8296
|
+
upgrades: []
|
|
8297
|
+
};
|
|
8298
|
+
ctx.signal.throwIfAborted();
|
|
8299
|
+
ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
|
|
8300
|
+
const skill = await ctx.runToEnd(`installing ${plan.package}@${latest}`, async () => {
|
|
8301
|
+
await installGlobally(latest, globalNpm(cli, install, ctx, node));
|
|
8302
|
+
return syncHome(cli, plan.packageDir, ctx);
|
|
8303
|
+
});
|
|
8304
|
+
const update = {
|
|
8305
|
+
from,
|
|
8306
|
+
latest,
|
|
8307
|
+
installed: {
|
|
8308
|
+
version: latest,
|
|
8309
|
+
prefix
|
|
8310
|
+
},
|
|
8311
|
+
skill,
|
|
8312
|
+
upgrades: await upgradesOf(plan.packageDir, from, latest)
|
|
8664
8313
|
};
|
|
8314
|
+
if (ctx.signal.aborted) {
|
|
8315
|
+
ctx.deliver(update);
|
|
8316
|
+
throw interruptedAfterInstall(plan);
|
|
8317
|
+
}
|
|
8318
|
+
return update;
|
|
8319
|
+
},
|
|
8320
|
+
render: (update) => renderUpdate(cli, install, update),
|
|
8321
|
+
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")
|
|
8322
|
+
});
|
|
8323
|
+
}
|
|
8324
|
+
//#endregion
|
|
8325
|
+
//#region ../agent-cli/dist/context/files.js
|
|
8326
|
+
const TEMP_FILE = /^\..+\.agent-cli-(\d+)\.tmp$/;
|
|
8327
|
+
/** Where the Runtime writes `path` before moving it into place; a dead writer's is swept. */
|
|
8328
|
+
function tempPathOf(path) {
|
|
8329
|
+
return join(dirname(path), `.${basename(path)}.agent-cli-${process.pid}.tmp`);
|
|
8330
|
+
}
|
|
8331
|
+
function isAlive(pid) {
|
|
8332
|
+
try {
|
|
8333
|
+
process.kill(pid, 0);
|
|
8334
|
+
return true;
|
|
8335
|
+
} catch (error) {
|
|
8336
|
+
return errnoOf(error) === "EPERM";
|
|
8337
|
+
}
|
|
8338
|
+
}
|
|
8339
|
+
async function sweep(folder) {
|
|
8340
|
+
const dead = (await readdir(folder)).filter((name) => {
|
|
8341
|
+
const pid = TEMP_FILE.exec(name)?.[1];
|
|
8342
|
+
return pid !== void 0 && !isAlive(Number(pid));
|
|
8343
|
+
});
|
|
8344
|
+
await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
|
|
8345
|
+
}
|
|
8346
|
+
/** The file a symlink at `path` points to, so a replacing write keeps the link. */
|
|
8347
|
+
async function targetOf$1(path) {
|
|
8348
|
+
try {
|
|
8349
|
+
return await realpath(path);
|
|
8350
|
+
} catch (error) {
|
|
8351
|
+
if (isMissingFile(error)) return path;
|
|
8352
|
+
throw error;
|
|
8353
|
+
}
|
|
8354
|
+
}
|
|
8355
|
+
async function writeWithMode(path, data, mode) {
|
|
8356
|
+
if (mode === void 0) {
|
|
8357
|
+
await writeFile(path, data);
|
|
8358
|
+
return;
|
|
8359
|
+
}
|
|
8360
|
+
await writeFile(path, data, { mode });
|
|
8361
|
+
await chmod(path, mode);
|
|
8362
|
+
}
|
|
8363
|
+
/** What `link` answers on a file system without hard links, such as exFAT. */
|
|
8364
|
+
const NO_HARD_LINKS = /* @__PURE__ */ new Set([
|
|
8365
|
+
"EPERM",
|
|
8366
|
+
"ENOTSUP",
|
|
8367
|
+
"EXDEV"
|
|
8368
|
+
]);
|
|
8369
|
+
/** A link fails atomically when `path` is taken, so no reader ever sees half a file there. */
|
|
8370
|
+
async function linkNew(temp, path, data, mode) {
|
|
8371
|
+
try {
|
|
8372
|
+
await link(temp, path);
|
|
8373
|
+
} catch (error) {
|
|
8374
|
+
if (!NO_HARD_LINKS.has(errnoOf(error))) throw error;
|
|
8375
|
+
await createNew(path, data, mode);
|
|
8376
|
+
}
|
|
8377
|
+
}
|
|
8378
|
+
/** A file this call created and could not finish is removed, so a rerun does not find half of it. */
|
|
8379
|
+
async function createNew(path, data, mode) {
|
|
8380
|
+
const handle = await open(path, "wx", mode);
|
|
8381
|
+
try {
|
|
8382
|
+
await handle.writeFile(data);
|
|
8383
|
+
if (mode !== void 0) await handle.chmod(mode);
|
|
8384
|
+
} catch (error) {
|
|
8385
|
+
await handle.close();
|
|
8386
|
+
await rm(path, { force: true });
|
|
8387
|
+
throw error;
|
|
8388
|
+
}
|
|
8389
|
+
await handle.close();
|
|
8390
|
+
}
|
|
8391
|
+
const RENAME_ATTEMPTS = 10;
|
|
8392
|
+
const RENAME_BACKOFF_MS = 10;
|
|
8393
|
+
const HELD_BY_ANOTHER_PROGRAM = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
|
|
8394
|
+
async function renameRetrying(from, to) {
|
|
8395
|
+
for (let attempt = 1;; attempt++) try {
|
|
8396
|
+
await rename(from, to);
|
|
8397
|
+
return;
|
|
8398
|
+
} catch (error) {
|
|
8399
|
+
if (!(process.platform === "win32" && HELD_BY_ANOTHER_PROGRAM.has(errnoOf(error))) || attempt === RENAME_ATTEMPTS) throw error;
|
|
8400
|
+
await setTimeout$1(attempt * RENAME_BACKOFF_MS);
|
|
8401
|
+
}
|
|
8402
|
+
}
|
|
8403
|
+
/** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
|
|
8404
|
+
function realFiles() {
|
|
8405
|
+
const swept = /* @__PURE__ */ new Set();
|
|
8406
|
+
return {
|
|
8407
|
+
exists: async (path) => {
|
|
8408
|
+
try {
|
|
8409
|
+
await access(path);
|
|
8410
|
+
return true;
|
|
8411
|
+
} catch (error) {
|
|
8412
|
+
if (isNothingThere(error)) return false;
|
|
8413
|
+
throw error;
|
|
8414
|
+
}
|
|
8415
|
+
},
|
|
8416
|
+
write: async (path, data, options = {}) => {
|
|
8417
|
+
await mkdir(dirname(path), { recursive: true });
|
|
8418
|
+
const target = options.exclusive === true ? path : await targetOf$1(path);
|
|
8419
|
+
const folder = dirname(target);
|
|
8420
|
+
if (!swept.has(folder)) {
|
|
8421
|
+
await sweep(folder);
|
|
8422
|
+
swept.add(folder);
|
|
8423
|
+
}
|
|
8424
|
+
const temp = tempPathOf(target);
|
|
8425
|
+
try {
|
|
8426
|
+
await writeWithMode(temp, data, options.mode);
|
|
8427
|
+
if (options.exclusive === true) {
|
|
8428
|
+
await linkNew(temp, target, data, options.mode);
|
|
8429
|
+
await rm(temp);
|
|
8430
|
+
} else await renameRetrying(temp, target);
|
|
8431
|
+
} catch (error) {
|
|
8432
|
+
await rm(temp, { force: true });
|
|
8433
|
+
throw error;
|
|
8434
|
+
}
|
|
8665
8435
|
},
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
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);
|
|
8436
|
+
read: (path) => readFile(path),
|
|
8437
|
+
list: (folder) => readdir(folder),
|
|
8438
|
+
remove: (path) => rm(path, {
|
|
8439
|
+
recursive: true,
|
|
8440
|
+
force: true
|
|
8441
|
+
}),
|
|
8442
|
+
mkdir: async (path, options) => {
|
|
8443
|
+
const mode = options?.mode;
|
|
8444
|
+
if (options?.exclusive !== true) {
|
|
8445
|
+
await mkdir(path, {
|
|
8446
|
+
recursive: true,
|
|
8447
|
+
mode
|
|
8448
|
+
});
|
|
8449
|
+
return;
|
|
8691
8450
|
}
|
|
8692
|
-
|
|
8451
|
+
await mkdir(dirname(path), {
|
|
8452
|
+
recursive: true,
|
|
8453
|
+
mode
|
|
8454
|
+
});
|
|
8455
|
+
await mkdir(path, { mode });
|
|
8693
8456
|
},
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8457
|
+
stat: async (path) => {
|
|
8458
|
+
try {
|
|
8459
|
+
const { size, dev, ino } = await stat(path, { bigint: true });
|
|
8460
|
+
return {
|
|
8461
|
+
size: Number(size),
|
|
8462
|
+
id: `${dev}:${ino}`
|
|
8463
|
+
};
|
|
8464
|
+
} catch (error) {
|
|
8465
|
+
if (isNothingThere(error)) return void 0;
|
|
8466
|
+
throw error;
|
|
8467
|
+
}
|
|
8468
|
+
}
|
|
8469
|
+
};
|
|
8470
|
+
}
|
|
8471
|
+
/**
|
|
8472
|
+
* `files` resolving paths against `cwd`, its failures classified by errno; `list` drops the
|
|
8473
|
+
* Runtime's temp files and sorts.
|
|
8474
|
+
*/
|
|
8475
|
+
function classifiedFiles(files, { cwd, fileError }) {
|
|
8476
|
+
async function at(access, path, work) {
|
|
8477
|
+
const target = resolve(cwd, path);
|
|
8478
|
+
try {
|
|
8479
|
+
return await work(target);
|
|
8480
|
+
} catch (error) {
|
|
8481
|
+
throw fileError(error, {
|
|
8482
|
+
path: target,
|
|
8483
|
+
access
|
|
8484
|
+
});
|
|
8485
|
+
}
|
|
8486
|
+
}
|
|
8487
|
+
return {
|
|
8488
|
+
exists: (path) => at("read", path, (target) => files.exists(target)),
|
|
8489
|
+
write: (path, data, options) => at("write", path, (target) => files.write(target, data, options)),
|
|
8490
|
+
read: (path) => at("read", path, (target) => files.read(target)),
|
|
8491
|
+
list: (folder) => at("read", folder, async (target) => (await files.list(target)).filter((name) => !TEMP_FILE.test(name)).sort()),
|
|
8492
|
+
remove: (path) => at("write", path, (target) => files.remove(target)),
|
|
8493
|
+
mkdir: (path, options) => at("write", path, (target) => files.mkdir(target, options)),
|
|
8494
|
+
stat: (path) => at("read", path, (target) => files.stat(target))
|
|
8495
|
+
};
|
|
8697
8496
|
}
|
|
8698
8497
|
//#endregion
|
|
8699
8498
|
//#region ../agent-cli/dist/built-ins/install/release-check.js
|
|
@@ -8807,78 +8606,310 @@ async function releaseCheck(cli, install, runAs, launch, ctx) {
|
|
|
8807
8606
|
debugLine(cli, ctx, messageOf(error));
|
|
8808
8607
|
}
|
|
8809
8608
|
}
|
|
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\``);
|
|
8609
|
+
async function checkAndTell(cli, install, runAs, launch, ctx) {
|
|
8610
|
+
const file = cacheFileOf(cli.name, ctx.env);
|
|
8611
|
+
const last = await lastCheck(file);
|
|
8612
|
+
if (last?.error !== void 0) debugLine(cli, ctx, last.error);
|
|
8613
|
+
if (last === void 0 || ctx.clock.now() - last.checkedAt >= CHECK_INTERVAL_MS) await startCheck(cli, install, file, last, launch, ctx);
|
|
8614
|
+
const latest = last?.latest;
|
|
8615
|
+
if (latest === void 0 || !isVersion(latest) || compareVersions(latest, cli.version) <= 0) return;
|
|
8616
|
+
ctx.notice(`${cli.name} ${latest} is out, this is ${cli.version}; run \`${runAs} update\``);
|
|
8617
|
+
}
|
|
8618
|
+
//#endregion
|
|
8619
|
+
//#region ../agent-cli/dist/built-ins/install/global.js
|
|
8620
|
+
function checkOf(cli, install, { bin, node }) {
|
|
8621
|
+
return {
|
|
8622
|
+
install: async (ctx) => {
|
|
8623
|
+
const npm = globalNpm(cli, install, ctx, node);
|
|
8624
|
+
try {
|
|
8625
|
+
return {
|
|
8626
|
+
state: "ok",
|
|
8627
|
+
message: `a global npm install under ${(await globalInstallOf(bin, npm)).prefix}`
|
|
8628
|
+
};
|
|
8629
|
+
} catch (error) {
|
|
8630
|
+
if (error instanceof CliError && error.code === "NOT_GLOBAL_INSTALL") return {
|
|
8631
|
+
state: "problem",
|
|
8632
|
+
message: error.message,
|
|
8633
|
+
fix: setupLineOf(install)
|
|
8634
|
+
};
|
|
8635
|
+
throw error;
|
|
8636
|
+
}
|
|
8637
|
+
},
|
|
8638
|
+
release: async (ctx) => {
|
|
8639
|
+
const fix = `${cli.name} update`;
|
|
8640
|
+
return {
|
|
8641
|
+
npm: globalNpm(cli, install, ctx, node),
|
|
8642
|
+
advise: () => ({ fix })
|
|
8643
|
+
};
|
|
8644
|
+
}
|
|
8645
|
+
};
|
|
8646
|
+
}
|
|
8647
|
+
/** The Install of a CLI installed once per machine with npm, which `setup` puts there and `update` replaces. */
|
|
8648
|
+
function globalInstall(cli, install) {
|
|
8649
|
+
const adapter = {
|
|
8650
|
+
runAs: runAsOf(cli),
|
|
8651
|
+
builtIns: (launch) => ({
|
|
8652
|
+
setup: setupCommand(cli, install, launch),
|
|
8653
|
+
update: updateCommand(cli, install, launch)
|
|
8654
|
+
}),
|
|
8655
|
+
exitClasses: ["source"],
|
|
8656
|
+
check: (launch) => checkOf(cli, install, launch),
|
|
8657
|
+
notice: (launch, ctx) => releaseCheck(cli, install, adapter.runAs, launch, ctx)
|
|
8658
|
+
};
|
|
8659
|
+
return adapter;
|
|
8660
|
+
}
|
|
8661
|
+
//#endregion
|
|
8662
|
+
//#region ../agent-cli/dist/built-ins/install/install.js
|
|
8663
|
+
const noNotice = async () => {};
|
|
8664
|
+
function noInstall(cli) {
|
|
8665
|
+
const skipped = async () => ({
|
|
8666
|
+
state: "skipped",
|
|
8667
|
+
message: `not checked: ${cli.name} declares no install`
|
|
8668
|
+
});
|
|
8669
|
+
return {
|
|
8670
|
+
runAs: runAsOf(cli),
|
|
8671
|
+
builtIns: () => ({}),
|
|
8672
|
+
exitClasses: [],
|
|
8673
|
+
check: () => ({
|
|
8674
|
+
install: skipped,
|
|
8675
|
+
release: async () => ({ skipped: await skipped() })
|
|
8676
|
+
}),
|
|
8677
|
+
notice: noNotice
|
|
8678
|
+
};
|
|
8679
|
+
}
|
|
8680
|
+
function installOf(cli) {
|
|
8681
|
+
const { install } = cli;
|
|
8682
|
+
if (install === void 0) return noInstall(cli);
|
|
8683
|
+
return install.mode === "global" ? globalInstall(cli, install) : dependencyInstall(cli, install);
|
|
8684
|
+
}
|
|
8685
|
+
//#endregion
|
|
8686
|
+
//#region ../agent-cli/dist/built-ins/agents-md.js
|
|
8687
|
+
function markersOf(name) {
|
|
8688
|
+
return {
|
|
8689
|
+
begin: `<!-- BEGIN:${name} -->`,
|
|
8690
|
+
end: `<!-- END:${name} -->`
|
|
8691
|
+
};
|
|
8692
|
+
}
|
|
8693
|
+
/** `text` with the CLI's Agents section between its markers, appended when they are missing; nothing else changes. */
|
|
8694
|
+
function withSection({ name, text: section }, text) {
|
|
8695
|
+
const { begin, end } = markersOf(name);
|
|
8696
|
+
const whole = `${begin}\n${section}\n${end}`;
|
|
8697
|
+
const start = text.indexOf(begin);
|
|
8698
|
+
const stop = start === -1 ? -1 : text.indexOf(end, start);
|
|
8699
|
+
if (stop !== -1) return `${text.slice(0, start)}${whole}${text.slice(stop + end.length)}`;
|
|
8700
|
+
if (text === "") return `${whole}\n`;
|
|
8701
|
+
return `${text}${text.endsWith("\n") ? "" : "\n"}\n${whole}\n`;
|
|
8702
|
+
}
|
|
8703
|
+
async function agentsMdAt(agents, root) {
|
|
8704
|
+
const path = join(root, "AGENTS.md");
|
|
8705
|
+
const existing = await orMissing(readFile(path, "utf8"));
|
|
8706
|
+
if (existing === void 0) return {
|
|
8707
|
+
path,
|
|
8708
|
+
agents,
|
|
8709
|
+
change: "create"
|
|
8710
|
+
};
|
|
8711
|
+
if (withSection(agents, existing) === existing) return {
|
|
8712
|
+
path,
|
|
8713
|
+
agents,
|
|
8714
|
+
change: null
|
|
8715
|
+
};
|
|
8716
|
+
return {
|
|
8717
|
+
path,
|
|
8718
|
+
agents,
|
|
8719
|
+
change: existing.includes(markersOf(agents.name).begin) ? "rewrite" : "add"
|
|
8720
|
+
};
|
|
8721
|
+
}
|
|
8722
|
+
async function handEditsAt(root) {
|
|
8723
|
+
const claudeMd = join(root, "CLAUDE.md");
|
|
8724
|
+
const text = await orMissing(readFile(claudeMd, "utf8"));
|
|
8725
|
+
if (text === void 0 || text.split(/\r?\n/).some((line) => line.trim() === "@AGENTS.md")) return [];
|
|
8726
|
+
return [`add a line \`@AGENTS.md\` to ${claudeMd}, so Claude Code reads AGENTS.md`];
|
|
8727
|
+
}
|
|
8728
|
+
function agentsRow(agents, install) {
|
|
8729
|
+
const { name } = agents;
|
|
8730
|
+
return {
|
|
8731
|
+
name: "agents",
|
|
8732
|
+
advisory: true,
|
|
8733
|
+
run: async (ctx) => {
|
|
8734
|
+
const root = await projectRootOf(ctx.cwd);
|
|
8735
|
+
const { path, change } = await agentsMdAt(agents, root);
|
|
8736
|
+
const fix = `${install.runAs} skill install`;
|
|
8737
|
+
if (change === "rewrite") return {
|
|
8738
|
+
state: "problem",
|
|
8739
|
+
message: `${path} holds a ${name} Agents section other than the one ${fix} writes`,
|
|
8740
|
+
fix
|
|
8741
|
+
};
|
|
8742
|
+
if (change !== null) return {
|
|
8743
|
+
state: "problem",
|
|
8744
|
+
message: `${path} holds no ${name} Agents section, so sessions in this repo do not know ${name}`,
|
|
8745
|
+
fix
|
|
8746
|
+
};
|
|
8747
|
+
const holds = `${path} holds the ${name} Agents section`;
|
|
8748
|
+
const [edit] = await handEditsAt(root);
|
|
8749
|
+
return edit === void 0 ? {
|
|
8750
|
+
state: "ok",
|
|
8751
|
+
message: holds
|
|
8752
|
+
} : {
|
|
8753
|
+
state: "problem",
|
|
8754
|
+
message: `${holds}; ${edit}`
|
|
8755
|
+
};
|
|
8756
|
+
}
|
|
8757
|
+
};
|
|
8758
|
+
}
|
|
8759
|
+
//#endregion
|
|
8760
|
+
//#region ../agent-cli/dist/built-ins/skill/install-command.js
|
|
8761
|
+
/** The project root and the Skill copy under it, as `skill install` writes it. */
|
|
8762
|
+
async function skillCopyOf(cli, cwd) {
|
|
8763
|
+
const root = await projectRootOf(cwd);
|
|
8764
|
+
return {
|
|
8765
|
+
root,
|
|
8766
|
+
copy: join(root, ".claude", "skills", cli.name)
|
|
8767
|
+
};
|
|
8768
|
+
}
|
|
8769
|
+
const installSchema = object({
|
|
8770
|
+
copy: string(),
|
|
8771
|
+
written: array(string()),
|
|
8772
|
+
removed: array(string()),
|
|
8773
|
+
agentsMd: object({
|
|
8774
|
+
path: string(),
|
|
8775
|
+
changed: boolean()
|
|
8776
|
+
}).nullable(),
|
|
8777
|
+
handEdits: array(string())
|
|
8778
|
+
});
|
|
8779
|
+
async function planOf(cli, folder, agents, cwd, packageRoot) {
|
|
8780
|
+
const { root, copy } = await skillCopyOf(cli, cwd);
|
|
8781
|
+
return {
|
|
8782
|
+
...await syncOf(join(packageRoot, folder), copy),
|
|
8783
|
+
agentsMd: agents === void 0 ? null : await agentsMdAt(agents, root),
|
|
8784
|
+
handEdits: agents === void 0 ? [] : await handEditsAt(root)
|
|
8785
|
+
};
|
|
8786
|
+
}
|
|
8787
|
+
const SECTION_VERBS = {
|
|
8788
|
+
create: "create",
|
|
8789
|
+
add: "add the Agents section to",
|
|
8790
|
+
rewrite: "rewrite the Agents section in"
|
|
8791
|
+
};
|
|
8792
|
+
function renderPlan(cli, plan) {
|
|
8793
|
+
const files = [
|
|
8794
|
+
...plan.write.map((file) => ` write ${file}`),
|
|
8795
|
+
...plan.replace.map((file) => ` replace ${file}`),
|
|
8796
|
+
...plan.remove.map((file) => ` remove ${file}`)
|
|
8797
|
+
];
|
|
8798
|
+
const { agentsMd } = plan;
|
|
8799
|
+
return [
|
|
8800
|
+
files.length === 0 ? `the ${cli.name} skill at ${plan.copy} already matches the package` : `would copy the ${cli.name} skill to ${plan.copy}:`,
|
|
8801
|
+
...files,
|
|
8802
|
+
...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}`],
|
|
8803
|
+
...plan.handEdits.map((edit) => `by hand: ${edit}`)
|
|
8804
|
+
].join("\n");
|
|
8818
8805
|
}
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8806
|
+
function renderInstalled(cli, data) {
|
|
8807
|
+
const { written, removed, agentsMd } = data;
|
|
8808
|
+
return [
|
|
8809
|
+
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}`,
|
|
8810
|
+
...agentsMd === null ? [] : [agentsMd.changed ? `wrote the ${cli.name} Agents section in ${agentsMd.path}` : `${agentsMd.path} already holds the ${cli.name} Agents section`],
|
|
8811
|
+
...data.handEdits.map((edit) => `by hand: ${edit}`)
|
|
8812
|
+
].join("\n");
|
|
8813
|
+
}
|
|
8814
|
+
/** The built-in `skill install`, for a CLI whose skill ships in its package. */
|
|
8815
|
+
function skillInstallCommand(cli, folder, agents) {
|
|
8816
|
+
return defineCommand$1({
|
|
8817
|
+
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",
|
|
8818
|
+
examples: [{
|
|
8819
|
+
command: `${cli.name} skill install`,
|
|
8820
|
+
note: "install the skill this version ships"
|
|
8821
|
+
}, {
|
|
8822
|
+
command: `${cli.name} skill install --dry-run`,
|
|
8823
|
+
note: "show what it would change"
|
|
8824
|
+
}],
|
|
8825
|
+
effect: "write",
|
|
8826
|
+
output: installSchema,
|
|
8827
|
+
plan: (_input, ctx) => planOf(cli, folder, agents, ctx.cwd, ctx.packageRoot),
|
|
8828
|
+
apply: async (plan, ctx) => {
|
|
8829
|
+
await applySync(plan, ctx);
|
|
8830
|
+
const { agentsMd } = plan;
|
|
8831
|
+
if (agentsMd !== null && agentsMd.change !== null) {
|
|
8832
|
+
const existing = await orMissing(readFile(agentsMd.path, "utf8")) ?? "";
|
|
8833
|
+
await ctx.files.write(agentsMd.path, withSection(agentsMd.agents, existing));
|
|
8837
8834
|
}
|
|
8838
|
-
},
|
|
8839
|
-
release: async (ctx) => {
|
|
8840
|
-
const fix = `${cli.name} update`;
|
|
8841
8835
|
return {
|
|
8842
|
-
|
|
8843
|
-
|
|
8836
|
+
copy: plan.copy,
|
|
8837
|
+
written: [...plan.write, ...plan.replace].sort(),
|
|
8838
|
+
removed: [...plan.remove],
|
|
8839
|
+
agentsMd: agentsMd === null ? null : {
|
|
8840
|
+
path: agentsMd.path,
|
|
8841
|
+
changed: agentsMd.change !== null
|
|
8842
|
+
},
|
|
8843
|
+
handEdits: [...plan.handEdits]
|
|
8844
8844
|
};
|
|
8845
|
-
}
|
|
8846
|
-
|
|
8845
|
+
},
|
|
8846
|
+
render: (data) => renderInstalled(cli, data),
|
|
8847
|
+
renderPlan: (plan) => renderPlan(cli, plan)
|
|
8848
|
+
});
|
|
8847
8849
|
}
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8850
|
+
//#endregion
|
|
8851
|
+
//#region ../agent-cli/dist/built-ins/skill/project.js
|
|
8852
|
+
async function personalCopyOf(name, copy, ctx) {
|
|
8853
|
+
const home = join(homeOf(ctx.env), ".claude", "skills", name);
|
|
8854
|
+
const real = await orMissing(realpath(home));
|
|
8855
|
+
return real === void 0 || real === await orMissing(realpath(copy)) ? void 0 : home;
|
|
8856
|
+
}
|
|
8857
|
+
/** The project root's copy against the package's, and any under home. */
|
|
8858
|
+
async function findingOf(cli, folder, install, ctx) {
|
|
8859
|
+
const { copy } = await skillCopyOf(cli, ctx.cwd);
|
|
8860
|
+
const [installed, shipped] = await Promise.all([orMissing(readFile(join(copy, "SKILL.md"), "utf8")), readFile(join(ctx.packageRoot, folder, "SKILL.md"), "utf8")]);
|
|
8861
|
+
const stated = installed === void 0 ? void 0 : skillVersionIn(installed);
|
|
8862
|
+
const left = stated !== void 0 && skillVersionIn(shipped) === void 0;
|
|
8863
|
+
const found = left ? `${copy} (${statedVersionNote(stated)})` : copy;
|
|
8864
|
+
const head = installed === void 0 ? `no copy of the ${cli.name} skill at ${copy}` : found;
|
|
8865
|
+
const home = await personalCopyOf(cli.name, copy, ctx);
|
|
8866
|
+
const message = home === void 0 ? head : `${head}; remove ${home}: Claude Code prefers a personal skill over the project's`;
|
|
8867
|
+
if (installed === void 0 || left) return {
|
|
8868
|
+
state: "problem",
|
|
8869
|
+
message,
|
|
8870
|
+
fix: `${install.runAs} skill install`
|
|
8871
|
+
};
|
|
8872
|
+
return home === void 0 ? {
|
|
8873
|
+
state: "ok",
|
|
8874
|
+
message
|
|
8875
|
+
} : {
|
|
8876
|
+
state: "problem",
|
|
8877
|
+
message
|
|
8856
8878
|
};
|
|
8857
|
-
return adapter;
|
|
8858
8879
|
}
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
const
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
message: `not checked: ${cli.name} declares no install`
|
|
8866
|
-
});
|
|
8880
|
+
/** A skill the package ships in `folder`, which `skill install` copies to the project root. */
|
|
8881
|
+
function projectSkill(cli, { folder, agents: text }, install) {
|
|
8882
|
+
const agents = text === void 0 ? void 0 : {
|
|
8883
|
+
name: cli.name,
|
|
8884
|
+
text
|
|
8885
|
+
};
|
|
8867
8886
|
return {
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
}),
|
|
8875
|
-
notice: noNotice
|
|
8887
|
+
builtIns: { skill: { install: skillInstallCommand(cli, folder, agents) } },
|
|
8888
|
+
rows: [{
|
|
8889
|
+
name: "skill",
|
|
8890
|
+
advisory: true,
|
|
8891
|
+
run: (ctx) => findingOf(cli, folder, install, ctx)
|
|
8892
|
+
}, ...agents === void 0 ? [] : [agentsRow(agents, install)]]
|
|
8876
8893
|
};
|
|
8877
8894
|
}
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8895
|
+
//#endregion
|
|
8896
|
+
//#region ../agent-cli/dist/built-ins/skill/skill.js
|
|
8897
|
+
const noSkill = ({ name }) => ({
|
|
8898
|
+
builtIns: {},
|
|
8899
|
+
rows: [{
|
|
8900
|
+
name: "skill",
|
|
8901
|
+
advisory: true,
|
|
8902
|
+
run: async () => ({
|
|
8903
|
+
state: "skipped",
|
|
8904
|
+
message: `not checked: ${name} declares no skill`
|
|
8905
|
+
})
|
|
8906
|
+
}]
|
|
8907
|
+
});
|
|
8908
|
+
/** A Global install's Skill copy goes under home; any other goes under the project. */
|
|
8909
|
+
function skillOf(cli, install) {
|
|
8910
|
+
const { skill } = cli;
|
|
8911
|
+
if (skill === void 0) return noSkill(cli);
|
|
8912
|
+
return cli.install?.mode === "global" ? homeSkill(cli, skill, install) : projectSkill(cli, skill, install);
|
|
8882
8913
|
}
|
|
8883
8914
|
//#endregion
|
|
8884
8915
|
//#region ../agent-cli/dist/built-ins/doctor-rows.js
|
|
@@ -9015,7 +9046,7 @@ function doctorChecks(cli, launch, scope) {
|
|
|
9015
9046
|
...secretRows(cli.secrets ?? [], cli.commands),
|
|
9016
9047
|
installRow(check),
|
|
9017
9048
|
registryRow(cli, check),
|
|
9018
|
-
...skillOf
|
|
9049
|
+
...skillOf(cli, install).rows
|
|
9019
9050
|
],
|
|
9020
9051
|
tools: new Set(tools.map(({ name }) => name))
|
|
9021
9052
|
};
|
|
@@ -9033,8 +9064,7 @@ const reportSchema = object({
|
|
|
9033
9064
|
]),
|
|
9034
9065
|
message: string(),
|
|
9035
9066
|
fix: string().optional(),
|
|
9036
|
-
code: string().optional()
|
|
9037
|
-
skill: packagedSkillDetailsSchema.optional()
|
|
9067
|
+
code: string().optional()
|
|
9038
9068
|
})),
|
|
9039
9069
|
next: array(string())
|
|
9040
9070
|
});
|
|
@@ -9169,15 +9199,15 @@ function doctorCommand(cli, launch) {
|
|
|
9169
9199
|
//#endregion
|
|
9170
9200
|
//#region ../agent-cli/dist/built-ins/commands.js
|
|
9171
9201
|
/**
|
|
9172
|
-
* The Commands the Runtime adds to a multi-Command CLI: `update` only for a Global
|
|
9173
|
-
* `skill install` only for a
|
|
9202
|
+
* The Commands the Runtime adds to a multi-Command CLI: `setup` and `update` only for a Global
|
|
9203
|
+
* install, and `skill install` only for a skill whose copy goes under the project.
|
|
9174
9204
|
*/
|
|
9175
9205
|
function builtInCommands(cli, launch) {
|
|
9176
9206
|
const install = installOf(cli);
|
|
9177
9207
|
return {
|
|
9178
9208
|
doctor: doctorCommand(cli, launch),
|
|
9179
9209
|
...install.builtIns(launch),
|
|
9180
|
-
...skillOf
|
|
9210
|
+
...skillOf(cli, install).builtIns
|
|
9181
9211
|
};
|
|
9182
9212
|
}
|
|
9183
9213
|
//#endregion
|
|
@@ -9314,6 +9344,7 @@ const RUNTIME_EXIT_CLASSES = {
|
|
|
9314
9344
|
PROJECT_INVALID: "operator",
|
|
9315
9345
|
DOCTOR_PROBLEMS: "operator",
|
|
9316
9346
|
NOT_GLOBAL_INSTALL: "operator",
|
|
9347
|
+
NOT_ON_PATH: "operator",
|
|
9317
9348
|
REGISTRY_UNREACHABLE: "operator",
|
|
9318
9349
|
REGISTRY_AUTH: "operator",
|
|
9319
9350
|
INSTALL_FAILED: "operator",
|
|
@@ -9387,20 +9418,22 @@ function checkTree(commands, path, table) {
|
|
|
9387
9418
|
else checkTree(node, at, table);
|
|
9388
9419
|
}
|
|
9389
9420
|
}
|
|
9390
|
-
function
|
|
9391
|
-
if (skill
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
if (
|
|
9421
|
+
function checkSkill({ name, skill, install }) {
|
|
9422
|
+
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`);
|
|
9423
|
+
}
|
|
9424
|
+
function checkDocs({ name, docs }) {
|
|
9425
|
+
if (docs === void 0) return;
|
|
9426
|
+
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
9427
|
}
|
|
9396
9428
|
function checkCli(cli) {
|
|
9397
9429
|
const table = errorTableOf(cli);
|
|
9398
|
-
|
|
9430
|
+
checkDocs(cli);
|
|
9399
9431
|
if (cli.commands === void 0) {
|
|
9400
9432
|
checkCommand(cli.command, commandLabel([], cli.name).join(" "), table);
|
|
9401
|
-
if (
|
|
9433
|
+
if (cli.skill !== void 0) throw new Error(`${cli.name} declares a skill, which only a CLI with several Commands can install`);
|
|
9402
9434
|
return cli;
|
|
9403
9435
|
}
|
|
9436
|
+
checkSkill(cli);
|
|
9404
9437
|
const { commands } = cli;
|
|
9405
9438
|
checkTree(commands, [], table);
|
|
9406
9439
|
const taken = Object.keys(builtInCommands(cli, {
|
|
@@ -9504,30 +9537,6 @@ function startSideTask(sideTask, runWith, { clock, failed }) {
|
|
|
9504
9537
|
};
|
|
9505
9538
|
}
|
|
9506
9539
|
//#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
9540
|
//#region ../agent-cli/dist/command-line/suggest.js
|
|
9532
9541
|
const MAX_DISTANCE = 3;
|
|
9533
9542
|
const MIN_SIMILARITY = .4;
|
|
@@ -10279,7 +10288,11 @@ function helpResult(node, root, cli, { style, json }) {
|
|
|
10279
10288
|
}
|
|
10280
10289
|
function versionResult(cli, packageRoot) {
|
|
10281
10290
|
return {
|
|
10282
|
-
stdout: `${
|
|
10291
|
+
stdout: `${[
|
|
10292
|
+
versionLine(cli),
|
|
10293
|
+
...buildStamp() === void 0 ? [] : [`changelog ${join(packageRoot, "CHANGELOG.md")}`],
|
|
10294
|
+
...cli.docs === void 0 ? [] : [`docs ${join(packageRoot, cli.docs)}`]
|
|
10295
|
+
].join("\n")}\n`,
|
|
10283
10296
|
stderr: "",
|
|
10284
10297
|
exitCode: SUCCESS_EXIT_CODE
|
|
10285
10298
|
};
|
|
@@ -10346,6 +10359,77 @@ function checkOperator({ operator, commandLine }, env, rerun) {
|
|
|
10346
10359
|
});
|
|
10347
10360
|
}
|
|
10348
10361
|
//#endregion
|
|
10362
|
+
//#region ../agent-cli/dist/context/outputs.js
|
|
10363
|
+
const OUTPUT_EXISTS = "OUTPUT_EXISTS";
|
|
10364
|
+
/** Refuses a run that would overwrite existing outputs unless the caller passed `--force`. */
|
|
10365
|
+
async function checkOutputs({ rerun, commandLine, force, files }, outputs) {
|
|
10366
|
+
if (outputs === void 0 || force) return;
|
|
10367
|
+
const found = await Promise.all(outputs.map((path) => files.exists(path)));
|
|
10368
|
+
const existing = outputs.filter((_path, index) => found[index]);
|
|
10369
|
+
if (existing.length === 0) return;
|
|
10370
|
+
const them = existing.length === 1 ? "it" : "them";
|
|
10371
|
+
throw new CliError({
|
|
10372
|
+
code: OUTPUT_EXISTS,
|
|
10373
|
+
message: `\`${commandLine}\` would overwrite ${existing.join(", ")}`,
|
|
10374
|
+
hint: `once the user agrees to replace ${them}, run the Next step`,
|
|
10375
|
+
next: [rerun({ add: [FORCE_FLAG.flag] })]
|
|
10376
|
+
});
|
|
10377
|
+
}
|
|
10378
|
+
/** Guards the writes to declared outputs of `files`, whose paths resolve against `cwd`. */
|
|
10379
|
+
function outputsGuard(files, cwd) {
|
|
10380
|
+
const unwritten = /* @__PURE__ */ new Set();
|
|
10381
|
+
let forceStep = [];
|
|
10382
|
+
const exclusiveWrite = async (path, data, options) => {
|
|
10383
|
+
const target = resolve(cwd, path);
|
|
10384
|
+
unwritten.delete(target);
|
|
10385
|
+
try {
|
|
10386
|
+
await files.write(path, data, {
|
|
10387
|
+
...options,
|
|
10388
|
+
exclusive: true
|
|
10389
|
+
});
|
|
10390
|
+
} catch (error) {
|
|
10391
|
+
unwritten.add(target);
|
|
10392
|
+
if (!(error instanceof CliError) || error.code !== "ALREADY_EXISTS") throw error;
|
|
10393
|
+
throw new CliError({
|
|
10394
|
+
code: ALREADY_EXISTS,
|
|
10395
|
+
message: error.message,
|
|
10396
|
+
hint: "once the user agrees to replace it, run the Next step",
|
|
10397
|
+
next: [forceStep]
|
|
10398
|
+
}, { cause: error });
|
|
10399
|
+
}
|
|
10400
|
+
};
|
|
10401
|
+
return {
|
|
10402
|
+
files: {
|
|
10403
|
+
...files,
|
|
10404
|
+
write: (path, data, options) => unwritten.has(resolve(cwd, path)) ? exclusiveWrite(path, data, options) : files.write(path, data, options)
|
|
10405
|
+
},
|
|
10406
|
+
guard: (outputs, step) => {
|
|
10407
|
+
forceStep = step;
|
|
10408
|
+
for (const path of outputs) unwritten.add(resolve(cwd, path));
|
|
10409
|
+
}
|
|
10410
|
+
};
|
|
10411
|
+
}
|
|
10412
|
+
/**
|
|
10413
|
+
* Creates each output folder and a temp file in it, so a sandbox denial fails before `apply`
|
|
10414
|
+
* spends anything. The temp file's name is the Runtime's, so a crashed run's leftover is swept.
|
|
10415
|
+
*/
|
|
10416
|
+
async function probeOutputs({ files, cwd, fileError }, outputs) {
|
|
10417
|
+
const folders = new Set(outputs.map((path) => dirname(resolve(cwd, path))));
|
|
10418
|
+
await Promise.all([...folders].map(async (folder) => {
|
|
10419
|
+
const probe = tempPathOf(join(folder, "outputs-probe"));
|
|
10420
|
+
try {
|
|
10421
|
+
await files.mkdir(folder);
|
|
10422
|
+
await files.write(probe, "");
|
|
10423
|
+
await files.remove(probe);
|
|
10424
|
+
} catch (error) {
|
|
10425
|
+
throw fileError(error, {
|
|
10426
|
+
path: folder,
|
|
10427
|
+
access: "write"
|
|
10428
|
+
});
|
|
10429
|
+
}
|
|
10430
|
+
}));
|
|
10431
|
+
}
|
|
10432
|
+
//#endregion
|
|
10349
10433
|
//#region ../agent-cli/dist/context/spill.js
|
|
10350
10434
|
const STDOUT_BUDGET_CHARS = 3e4;
|
|
10351
10435
|
const SPILL_RETENTION_MS = 6048e5;
|
|
@@ -11238,11 +11322,7 @@ async function runCommand(cli, routed, options, write, baseFormat) {
|
|
|
11238
11322
|
});
|
|
11239
11323
|
});
|
|
11240
11324
|
} catch (error) {
|
|
11241
|
-
return failed(
|
|
11242
|
-
cwd,
|
|
11243
|
-
env,
|
|
11244
|
-
packageRoot
|
|
11245
|
-
}, invocation.runAs), noSideTask);
|
|
11325
|
+
return failed(error, noSideTask);
|
|
11246
11326
|
}
|
|
11247
11327
|
const { input, flags } = parsed;
|
|
11248
11328
|
resumed = command.deadline?.resume(input, rerunOf);
|
|
@@ -11374,9 +11454,7 @@ function once(build) {
|
|
|
11374
11454
|
};
|
|
11375
11455
|
}
|
|
11376
11456
|
async function noticesOf(cli, launch, ctx) {
|
|
11377
|
-
|
|
11378
|
-
await skillOf$1(cli, install).notice(launch, ctx);
|
|
11379
|
-
await install.notice(launch, ctx);
|
|
11457
|
+
await installOf(cli).notice(launch, ctx);
|
|
11380
11458
|
}
|
|
11381
11459
|
//#endregion
|
|
11382
11460
|
//#region ../agent-cli/dist/style.js
|
|
@@ -11460,16 +11538,10 @@ async function routeAndRun(cli, argv, options, write, stopNotice) {
|
|
|
11460
11538
|
...format,
|
|
11461
11539
|
json: routed.json
|
|
11462
11540
|
}));
|
|
11463
|
-
case "usage error": {
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
};
|
|
11468
|
-
return ranAlone(errorResult(await withDifferingSkill(routed.error, cli, place, runAs), {
|
|
11469
|
-
...misused(routed.node),
|
|
11470
|
-
json: routed.json
|
|
11471
|
-
}));
|
|
11472
|
-
}
|
|
11541
|
+
case "usage error": return ranAlone(errorResult(routed.error, {
|
|
11542
|
+
...misused(routed.node),
|
|
11543
|
+
json: routed.json
|
|
11544
|
+
}));
|
|
11473
11545
|
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
11546
|
}
|
|
11475
11547
|
}
|
|
@@ -11532,7 +11604,7 @@ async function runProcess(cli) {
|
|
|
11532
11604
|
//#region package.json
|
|
11533
11605
|
var package_default = {
|
|
11534
11606
|
name: "@duyquangnvx/create-agent-cli",
|
|
11535
|
-
version: "0.
|
|
11607
|
+
version: "0.24.0",
|
|
11536
11608
|
description: "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
|
|
11537
11609
|
type: "module",
|
|
11538
11610
|
engines: { "node": ">=22.12.0" },
|
|
@@ -11581,80 +11653,35 @@ function fill(text, values, quoted) {
|
|
|
11581
11653
|
//#region src/install-mode.ts
|
|
11582
11654
|
const PARTIALS_DIR = fileURLToPath(new URL("../partials/", import.meta.url));
|
|
11583
11655
|
const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
11584
|
-
const
|
|
11585
|
-
|
|
11586
|
-
|
|
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({
|
|
11656
|
+
const TEMPLATE_REGISTRY = "https://registry.npmjs.org/";
|
|
11657
|
+
function installModeOf(dependency, binName) {
|
|
11658
|
+
if (!SKILL_NAME.test(binName)) throw new UsageError({
|
|
11626
11659
|
code: "SKILL_NAME_INVALID",
|
|
11627
11660
|
message: `${binName} cannot name a skill, whose names are lowercase letters, digits and single hyphens`,
|
|
11628
|
-
hint:
|
|
11661
|
+
hint: "choose a name such as my-cli"
|
|
11629
11662
|
});
|
|
11630
|
-
return 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}\``;
|
|
11663
|
+
return dependency ? "dependency" : "global";
|
|
11638
11664
|
}
|
|
11639
11665
|
async function partialsOf(mode, values) {
|
|
11640
11666
|
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([
|
|
11667
|
+
const [installLines, skillCheck, installSteps, updateSteps, sandbox, whenMissing] = await Promise.all([
|
|
11642
11668
|
partial("installLines"),
|
|
11643
11669
|
partial("skillCheck"),
|
|
11644
11670
|
partial("installSteps"),
|
|
11645
11671
|
partial("updateSteps"),
|
|
11646
|
-
partial("sandbox")
|
|
11672
|
+
partial("sandbox"),
|
|
11673
|
+
partial("whenMissing")
|
|
11647
11674
|
]);
|
|
11648
11675
|
return {
|
|
11649
11676
|
installLines,
|
|
11650
11677
|
skillCheck,
|
|
11651
11678
|
installSteps,
|
|
11652
11679
|
updateSteps,
|
|
11653
|
-
sandbox
|
|
11680
|
+
sandbox,
|
|
11681
|
+
whenMissing
|
|
11654
11682
|
};
|
|
11655
11683
|
}
|
|
11656
|
-
async function globalSetup(
|
|
11657
|
-
const skillLine = skillLineOf(skillHub, binName);
|
|
11684
|
+
async function globalSetup(packageName, binName) {
|
|
11658
11685
|
const runAs = runAsOf({ name: binName });
|
|
11659
11686
|
return {
|
|
11660
11687
|
values: {
|
|
@@ -11664,24 +11691,16 @@ async function globalSetup(skillHub, packageName, binName) {
|
|
|
11664
11691
|
packageName,
|
|
11665
11692
|
binName,
|
|
11666
11693
|
runAs,
|
|
11667
|
-
|
|
11694
|
+
setupLine: setupLineOf({
|
|
11695
|
+
mode: "global",
|
|
11696
|
+
package: packageName,
|
|
11697
|
+
registry: TEMPLATE_REGISTRY
|
|
11698
|
+
})
|
|
11668
11699
|
})
|
|
11669
11700
|
},
|
|
11670
|
-
edits:
|
|
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: []
|
|
11701
|
+
edits: []
|
|
11680
11702
|
};
|
|
11681
11703
|
}
|
|
11682
|
-
function skillOf(skillHub) {
|
|
11683
|
-
return skillHub.kind === "git" ? `{ source: ${JSON.stringify(skillHub.url)} }` : `{ add: ${JSON.stringify(skillHub.add)} }`;
|
|
11684
|
-
}
|
|
11685
11704
|
async function dependencySetup(packageName, binName) {
|
|
11686
11705
|
const runAs = runAsOf({
|
|
11687
11706
|
name: binName,
|
|
@@ -11702,18 +11721,13 @@ async function dependencySetup(packageName, binName) {
|
|
|
11702
11721
|
},
|
|
11703
11722
|
edits: [{
|
|
11704
11723
|
file: "src/cli.ts",
|
|
11705
|
-
find:
|
|
11724
|
+
find: ` install: "global",\n`,
|
|
11706
11725
|
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"]
|
|
11726
|
+
}]
|
|
11713
11727
|
};
|
|
11714
11728
|
}
|
|
11715
11729
|
async function installSetupOf(install, packageName, binName) {
|
|
11716
|
-
return install
|
|
11730
|
+
return install === "global" ? globalSetup(packageName, binName) : dependencySetup(packageName, binName);
|
|
11717
11731
|
}
|
|
11718
11732
|
//#endregion
|
|
11719
11733
|
//#region src/steps.ts
|
|
@@ -11866,9 +11880,6 @@ async function editManifest(root, edit) {
|
|
|
11866
11880
|
async function setRepository(root, repository) {
|
|
11867
11881
|
await editManifest(root, (entries) => entries.flatMap((entry) => entry[0] === "description" ? [entry, ["repository", repository]] : [entry]));
|
|
11868
11882
|
}
|
|
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
11883
|
async function ensureFree(path) {
|
|
11873
11884
|
if (!await isFree(path)) throw new UsageError({
|
|
11874
11885
|
code: "TARGET_NOT_EMPTY",
|
|
@@ -11894,18 +11905,11 @@ await runProcess(defineCli({
|
|
|
11894
11905
|
install: boolean().default(true).describe("run pnpm install"),
|
|
11895
11906
|
git: boolean().default(true).describe("create a git repository with an initial commit"),
|
|
11896
11907
|
repository: url({ protocol: /^https$/ }).optional().describe("the https URL of the CLI's git repository"),
|
|
11897
|
-
|
|
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")
|
|
11908
|
+
dependency: boolean().default(false).describe("install the CLI per project, as a devDependency, with its skill copied into the project")
|
|
11901
11909
|
}),
|
|
11902
|
-
plan: async ({ name, description, install, git, repository,
|
|
11910
|
+
plan: async ({ name, description, install, git, repository, dependency }, ctx) => {
|
|
11903
11911
|
const binName = PACKAGE_NAME.exec(name)?.[1] ?? name;
|
|
11904
|
-
const mode = installModeOf(
|
|
11905
|
-
git: skillHubGit,
|
|
11906
|
-
api: skillHubApi,
|
|
11907
|
-
add: skillAdd
|
|
11908
|
-
}, dependency, binName);
|
|
11912
|
+
const mode = installModeOf(dependency, binName);
|
|
11909
11913
|
const path = resolve(ctx.cwd, binName);
|
|
11910
11914
|
await ensureFree(path);
|
|
11911
11915
|
const setup = await installSetupOf(mode, name, binName);
|
|
@@ -11928,8 +11932,7 @@ await runProcess(defineCli({
|
|
|
11928
11932
|
}),
|
|
11929
11933
|
install,
|
|
11930
11934
|
repository,
|
|
11931
|
-
edits: setup.edits
|
|
11932
|
-
manifestFiles: setup.files
|
|
11935
|
+
edits: setup.edits
|
|
11933
11936
|
};
|
|
11934
11937
|
},
|
|
11935
11938
|
apply: async (plan, ctx) => {
|
|
@@ -11937,7 +11940,6 @@ await runProcess(defineCli({
|
|
|
11937
11940
|
await writeTemplate(plan.path, plan.files, plan.values);
|
|
11938
11941
|
if (plan.repository !== void 0) await setRepository(plan.path, plan.repository);
|
|
11939
11942
|
for (const edit of plan.edits) await applyEdit(plan.path, edit);
|
|
11940
|
-
if (plan.manifestFiles.length > 0) await addFiles(plan.path, plan.manifestFiles);
|
|
11941
11943
|
await runSteps(plan.steps, plan.path, ctx);
|
|
11942
11944
|
return {
|
|
11943
11945
|
path: plan.path,
|