@duyquangnvx/create-agent-cli 0.22.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 +37 -0
- package/README.md +2 -2
- package/dist/bin.js +1257 -1117
- 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
|
|
@@ -5959,8 +5959,12 @@ async function orMissing(pending) {
|
|
|
5959
5959
|
function isNothingThere(error) {
|
|
5960
5960
|
return isMissingFile(error) || errnoOf(error) === "ENOTDIR";
|
|
5961
5961
|
}
|
|
5962
|
+
/** The CLI's own environment variable, such as `TOOL_DEBUG` for `cliVariable("tool", "DEBUG")`. */
|
|
5963
|
+
function cliVariable(cliName, suffix) {
|
|
5964
|
+
return `${cliName.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_${suffix}`;
|
|
5965
|
+
}
|
|
5962
5966
|
function debugVariable(cliName) {
|
|
5963
|
-
return
|
|
5967
|
+
return cliVariable(cliName, "DEBUG");
|
|
5964
5968
|
}
|
|
5965
5969
|
function messageOf(error) {
|
|
5966
5970
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -7081,9 +7085,9 @@ function secretRows(secrets, commands) {
|
|
|
7081
7085
|
/** The bundler's stamp, absent when the CLI runs unbundled. Read per call, so a test can define it. */
|
|
7082
7086
|
function buildStamp() {
|
|
7083
7087
|
return {
|
|
7084
|
-
"sha": "
|
|
7088
|
+
"sha": "45caa4e",
|
|
7085
7089
|
"dirty": false,
|
|
7086
|
-
"builtAt": "2026-11-
|
|
7090
|
+
"builtAt": "2026-11-05T07:43:22.000Z"
|
|
7087
7091
|
};
|
|
7088
7092
|
}
|
|
7089
7093
|
/** The first line of `--version`: the version, and the commit and time of a stamped build. */
|
|
@@ -7233,6 +7237,16 @@ function installCommandLine(registry, version = "latest") {
|
|
|
7233
7237
|
...registry.flags
|
|
7234
7238
|
].join(" ");
|
|
7235
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
|
+
}
|
|
7236
7250
|
function npmOf(ctx, windows) {
|
|
7237
7251
|
const beside = ctx.node === void 0 ? void 0 : join(dirname(ctx.node), windows ? "npm.cmd" : "npm");
|
|
7238
7252
|
if (beside === void 0 || !existsSync(beside)) return {
|
|
@@ -7276,13 +7290,13 @@ function failureOf(call, answer, ctx) {
|
|
|
7276
7290
|
const code = NPM_CODE.exec(answer.stderr)?.[1];
|
|
7277
7291
|
const output = tail$1(answer.stderr);
|
|
7278
7292
|
const next = [ctx.rerun];
|
|
7279
|
-
if (code !== void 0 && NETWORK_CODES.has(code)) return
|
|
7293
|
+
if (code !== void 0 && NETWORK_CODES.has(code)) return runtimeError({
|
|
7280
7294
|
code: REGISTRY_UNREACHABLE,
|
|
7281
7295
|
message: `npm could not reach ${registry} (${code})${output}`,
|
|
7282
7296
|
hint: `${unreachableHint}, then ${RUN_AGAIN}`,
|
|
7283
7297
|
next
|
|
7284
7298
|
});
|
|
7285
|
-
if (code !== void 0 && AUTH_CODES.has(code)) return
|
|
7299
|
+
if (code !== void 0 && AUTH_CODES.has(code)) return runtimeError({
|
|
7286
7300
|
code: REGISTRY_AUTH,
|
|
7287
7301
|
message: `${registry} refused npm's credentials (${code})${output}`,
|
|
7288
7302
|
hint: authHint(code, ctx),
|
|
@@ -7295,7 +7309,7 @@ function failureOf(call, answer, ctx) {
|
|
|
7295
7309
|
hint: `fix what npm reports, then ${RUN_AGAIN} with the first Next step, or install it yourself with the second`,
|
|
7296
7310
|
next: [...next, wholeLine(installCommandLine(ctx.registry))]
|
|
7297
7311
|
});
|
|
7298
|
-
return
|
|
7312
|
+
return runtimeError({
|
|
7299
7313
|
code: NPM_FAILED,
|
|
7300
7314
|
message: `npm ${call} failed with exit code ${answer.exitCode}${output}`,
|
|
7301
7315
|
hint: `fix what npm reports, then ${RUN_AGAIN}`,
|
|
@@ -7361,15 +7375,18 @@ async function installGlobally(version, ctx) {
|
|
|
7361
7375
|
...ctx.registry.flags
|
|
7362
7376
|
], ctx, null);
|
|
7363
7377
|
}
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
const answer = await runNpm("view", [
|
|
7378
|
+
function latestViewArgs({ package: name, flags }) {
|
|
7379
|
+
return [
|
|
7367
7380
|
"view",
|
|
7368
7381
|
`${name}@latest`,
|
|
7369
7382
|
"version",
|
|
7370
7383
|
"--json",
|
|
7371
7384
|
...flags
|
|
7372
|
-
]
|
|
7385
|
+
];
|
|
7386
|
+
}
|
|
7387
|
+
async function latestVersion(ctx) {
|
|
7388
|
+
const { package: name, label: registry } = ctx.registry;
|
|
7389
|
+
const answer = await runNpm("view", latestViewArgs(ctx.registry), ctx);
|
|
7373
7390
|
const version = versionInViewAnswer(answer);
|
|
7374
7391
|
if (version === void 0) throw noLatest(ctx, `npm answered no version for ${name}@latest on ${registry}: ${answer.trim()}`);
|
|
7375
7392
|
return version;
|
|
@@ -7611,184 +7628,8 @@ function dependencyInstall(cli, install) {
|
|
|
7611
7628
|
runAs,
|
|
7612
7629
|
builtIns: () => ({}),
|
|
7613
7630
|
exitClasses: [],
|
|
7614
|
-
check: (launch) => checkOf$1(cli, install, runAs, launch)
|
|
7615
|
-
|
|
7616
|
-
}
|
|
7617
|
-
//#endregion
|
|
7618
|
-
//#region ../agent-cli/dist/built-ins/skill/hub.js
|
|
7619
|
-
const SKILL_DIRS = [[".claude", "skills"], [".agents", "skills"]];
|
|
7620
|
-
const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
7621
|
-
const METADATA_VERSION = /^metadata:[ \t]*\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+version:[ \t]*["']?([^"'\s]+)["']?[ \t]*$/m;
|
|
7622
|
-
/** The version a SKILL.md's frontmatter `metadata.version` states. */
|
|
7623
|
-
function skillVersionIn(skill) {
|
|
7624
|
-
const frontmatter = FRONTMATTER.exec(skill)?.[1];
|
|
7625
|
-
return frontmatter === void 0 ? void 0 : METADATA_VERSION.exec(`${frontmatter}\n`)?.[1];
|
|
7626
|
-
}
|
|
7627
|
-
function homeOf(env) {
|
|
7628
|
-
return env.HOME ?? env.USERPROFILE ?? homedir();
|
|
7629
|
-
}
|
|
7630
|
-
async function copyAt(path, version) {
|
|
7631
|
-
let skill;
|
|
7632
|
-
try {
|
|
7633
|
-
skill = await readFile(join(path, "SKILL.md"), "utf8");
|
|
7634
|
-
} catch (error) {
|
|
7635
|
-
if (isMissingFile(error)) return;
|
|
7636
|
-
return {
|
|
7637
|
-
path,
|
|
7638
|
-
version: void 0,
|
|
7639
|
-
state: "unreadable"
|
|
7640
|
-
};
|
|
7641
|
-
}
|
|
7642
|
-
const stated = skillVersionIn(skill);
|
|
7643
|
-
if (stated === void 0 || !isVersion(stated) || !isVersion(version)) return {
|
|
7644
|
-
path,
|
|
7645
|
-
version: stated,
|
|
7646
|
-
state: "unversioned"
|
|
7647
|
-
};
|
|
7648
|
-
return {
|
|
7649
|
-
path,
|
|
7650
|
-
version: stated,
|
|
7651
|
-
state: standingOf$1(stated, version)
|
|
7652
|
-
};
|
|
7653
|
-
}
|
|
7654
|
-
async function skillCopies(cliName, version, { cwd, env }) {
|
|
7655
|
-
const paths = [cwd, homeOf(env)].flatMap((root) => SKILL_DIRS.map((dir) => join(root, ...dir, cliName)));
|
|
7656
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7657
|
-
const copies = [];
|
|
7658
|
-
for (const path of paths) {
|
|
7659
|
-
const copy = await copyAt(path, version);
|
|
7660
|
-
const real = copy === void 0 ? void 0 : await realpath(path);
|
|
7661
|
-
if (copy !== void 0 && real !== void 0 && !seen.has(real)) {
|
|
7662
|
-
seen.add(real);
|
|
7663
|
-
copies.push(copy);
|
|
7664
|
-
}
|
|
7665
|
-
}
|
|
7666
|
-
return copies;
|
|
7667
|
-
}
|
|
7668
|
-
const has = (copies, wanted) => copies.some(({ state }) => state === wanted);
|
|
7669
|
-
function addLineOf(cliName, from) {
|
|
7670
|
-
return "add" in from ? from.add : `npx -y skills add ${from.source} --skill ${cliName} -g`;
|
|
7671
|
-
}
|
|
7672
|
-
function fixFor(cliName, copies, from) {
|
|
7673
|
-
const update = `npx -y skills update ${cliName}`;
|
|
7674
|
-
const add = from === void 0 ? void 0 : addLineOf(cliName, from);
|
|
7675
|
-
if (copies.length === 0) return add;
|
|
7676
|
-
if (has(copies, "unversioned") && add !== void 0) return has(copies, "behind") ? `${update} && ${add}` : add;
|
|
7677
|
-
return has(copies, "behind") || has(copies, "unversioned") ? update : void 0;
|
|
7678
|
-
}
|
|
7679
|
-
const SKILL_STATE_NOTES = {
|
|
7680
|
-
current: "the same as this build",
|
|
7681
|
-
behind: "older than this build",
|
|
7682
|
-
ahead: "newer than this build",
|
|
7683
|
-
unversioned: "states no metadata.version",
|
|
7684
|
-
unreadable: "could not be read"
|
|
7685
|
-
};
|
|
7686
|
-
function describeCopy({ path, version, state }) {
|
|
7687
|
-
return `${path}${version === void 0 ? "" : ` ${version}`} (${SKILL_STATE_NOTES[state]})`;
|
|
7688
|
-
}
|
|
7689
|
-
/** A skill on a Skill Hub, which `from` says how to install; without it, no line adds it. */
|
|
7690
|
-
function hubSkill(cli, from) {
|
|
7691
|
-
const findingOf = (copies) => {
|
|
7692
|
-
const fix = fixFor(cli.name, copies, from);
|
|
7693
|
-
const message = copies.length === 0 ? `no copy of the ${cli.name} skill is installed` : copies.map(describeCopy).join("; ");
|
|
7694
|
-
return fix === void 0 ? {
|
|
7695
|
-
state: "ok",
|
|
7696
|
-
message
|
|
7697
|
-
} : {
|
|
7698
|
-
state: "ok",
|
|
7699
|
-
message,
|
|
7700
|
-
fix
|
|
7701
|
-
};
|
|
7702
|
-
};
|
|
7703
|
-
return {
|
|
7704
|
-
kind: "hub",
|
|
7705
|
-
builtIns: {},
|
|
7706
|
-
rows: [{
|
|
7707
|
-
name: "skill",
|
|
7708
|
-
advisory: true,
|
|
7709
|
-
run: async (ctx) => findingOf(await skillCopies(cli.name, cli.version, ctx))
|
|
7710
|
-
}],
|
|
7711
|
-
notice: async () => {},
|
|
7712
|
-
differing: async (place) => (await skillCopies(cli.name, cli.version, place)).filter(({ state }) => state !== "current" && state !== "unreadable").map(describeCopy),
|
|
7713
|
-
nextAt: async (version, ctx) => {
|
|
7714
|
-
const copies = await skillCopies(cli.name, version, ctx);
|
|
7715
|
-
const fix = fixFor(cli.name, copies, from);
|
|
7716
|
-
return fix === void 0 ? [] : [fix];
|
|
7717
|
-
}
|
|
7718
|
-
};
|
|
7719
|
-
}
|
|
7720
|
-
//#endregion
|
|
7721
|
-
//#region ../agent-cli/dist/built-ins/agents-md.js
|
|
7722
|
-
function markersOf(name) {
|
|
7723
|
-
return {
|
|
7724
|
-
begin: `<!-- BEGIN:${name} -->`,
|
|
7725
|
-
end: `<!-- END:${name} -->`
|
|
7726
|
-
};
|
|
7727
|
-
}
|
|
7728
|
-
/** `text` with the CLI's Agents section between its markers, appended when they are missing; nothing else changes. */
|
|
7729
|
-
function withSection({ name, text: section }, text) {
|
|
7730
|
-
const { begin, end } = markersOf(name);
|
|
7731
|
-
const whole = `${begin}\n${section}\n${end}`;
|
|
7732
|
-
const start = text.indexOf(begin);
|
|
7733
|
-
const stop = start === -1 ? -1 : text.indexOf(end, start);
|
|
7734
|
-
if (stop !== -1) return `${text.slice(0, start)}${whole}${text.slice(stop + end.length)}`;
|
|
7735
|
-
if (text === "") return `${whole}\n`;
|
|
7736
|
-
return `${text}${text.endsWith("\n") ? "" : "\n"}\n${whole}\n`;
|
|
7737
|
-
}
|
|
7738
|
-
async function agentsMdAt(agents, root) {
|
|
7739
|
-
const path = join(root, "AGENTS.md");
|
|
7740
|
-
const existing = await orMissing(readFile(path, "utf8"));
|
|
7741
|
-
if (existing === void 0) return {
|
|
7742
|
-
path,
|
|
7743
|
-
agents,
|
|
7744
|
-
change: "create"
|
|
7745
|
-
};
|
|
7746
|
-
if (withSection(agents, existing) === existing) return {
|
|
7747
|
-
path,
|
|
7748
|
-
agents,
|
|
7749
|
-
change: null
|
|
7750
|
-
};
|
|
7751
|
-
return {
|
|
7752
|
-
path,
|
|
7753
|
-
agents,
|
|
7754
|
-
change: existing.includes(markersOf(agents.name).begin) ? "rewrite" : "add"
|
|
7755
|
-
};
|
|
7756
|
-
}
|
|
7757
|
-
async function handEditsAt(root) {
|
|
7758
|
-
const claudeMd = join(root, "CLAUDE.md");
|
|
7759
|
-
const text = await orMissing(readFile(claudeMd, "utf8"));
|
|
7760
|
-
if (text === void 0 || text.split(/\r?\n/).some((line) => line.trim() === "@AGENTS.md")) return [];
|
|
7761
|
-
return [`add a line \`@AGENTS.md\` to ${claudeMd}, so Claude Code reads AGENTS.md`];
|
|
7762
|
-
}
|
|
7763
|
-
function agentsRow(agents, install) {
|
|
7764
|
-
const { name } = agents;
|
|
7765
|
-
return {
|
|
7766
|
-
name: "agents",
|
|
7767
|
-
advisory: true,
|
|
7768
|
-
run: async (ctx) => {
|
|
7769
|
-
const root = await projectRootOf(ctx.cwd);
|
|
7770
|
-
const { path, change } = await agentsMdAt(agents, root);
|
|
7771
|
-
const fix = `${install.runAs} skill install`;
|
|
7772
|
-
if (change === "rewrite") return {
|
|
7773
|
-
state: "problem",
|
|
7774
|
-
message: `${path} holds a ${name} Agents section other than the one ${fix} writes`,
|
|
7775
|
-
fix
|
|
7776
|
-
};
|
|
7777
|
-
if (change !== null) return {
|
|
7778
|
-
state: "problem",
|
|
7779
|
-
message: `${path} holds no ${name} Agents section, so sessions in this repo do not know ${name}`,
|
|
7780
|
-
fix
|
|
7781
|
-
};
|
|
7782
|
-
const holds = `${path} holds the ${name} Agents section`;
|
|
7783
|
-
const [edit] = await handEditsAt(root);
|
|
7784
|
-
return edit === void 0 ? {
|
|
7785
|
-
state: "ok",
|
|
7786
|
-
message: holds
|
|
7787
|
-
} : {
|
|
7788
|
-
state: "problem",
|
|
7789
|
-
message: `${holds}; ${edit}`
|
|
7790
|
-
};
|
|
7791
|
-
}
|
|
7631
|
+
check: (launch) => checkOf$1(cli, install, runAs, launch),
|
|
7632
|
+
notice: async () => {}
|
|
7792
7633
|
};
|
|
7793
7634
|
}
|
|
7794
7635
|
//#endregion
|
|
@@ -7854,259 +7695,208 @@ function classifyErrno(error, context) {
|
|
|
7854
7695
|
}
|
|
7855
7696
|
}
|
|
7856
7697
|
//#endregion
|
|
7857
|
-
//#region ../agent-cli/dist/context/
|
|
7858
|
-
const
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
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
|
+
});
|
|
7862
7711
|
}
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
}
|
|
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";
|
|
7870
7718
|
}
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
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
|
|
7875
7724
|
});
|
|
7876
|
-
await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
|
|
7877
|
-
}
|
|
7878
|
-
/** The file a symlink at `path` points to, so a replacing write keeps the link. */
|
|
7879
|
-
async function targetOf$1(path) {
|
|
7880
|
-
try {
|
|
7881
|
-
return await realpath(path);
|
|
7882
|
-
} catch (error) {
|
|
7883
|
-
if (isMissingFile(error)) return path;
|
|
7884
|
-
throw error;
|
|
7885
|
-
}
|
|
7886
7725
|
}
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
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
|
+
}
|
|
7891
7760
|
}
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
}
|
|
7895
|
-
/** What `link` answers on a file system without hard links, such as exFAT. */
|
|
7896
|
-
const NO_HARD_LINKS = /* @__PURE__ */ new Set([
|
|
7897
|
-
"EPERM",
|
|
7898
|
-
"ENOTSUP",
|
|
7899
|
-
"EXDEV"
|
|
7900
|
-
]);
|
|
7901
|
-
/** A link fails atomically when `path` is taken, so no reader ever sees half a file there. */
|
|
7902
|
-
async function linkNew(temp, path, data, mode) {
|
|
7903
|
-
try {
|
|
7904
|
-
await link(temp, path);
|
|
7905
|
-
} catch (error) {
|
|
7906
|
-
if (!NO_HARD_LINKS.has(errnoOf(error))) throw error;
|
|
7907
|
-
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")]);
|
|
7908
7763
|
}
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
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;
|
|
7920
7776
|
}
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
const
|
|
7925
|
-
const
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
await setTimeout$1(attempt * RENAME_BACKOFF_MS);
|
|
7933
|
-
}
|
|
7934
|
-
}
|
|
7935
|
-
/** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
|
|
7936
|
-
function realFiles() {
|
|
7937
|
-
const swept = /* @__PURE__ */ new Set();
|
|
7938
|
-
return {
|
|
7939
|
-
exists: async (path) => {
|
|
7940
|
-
try {
|
|
7941
|
-
await access(path);
|
|
7942
|
-
return true;
|
|
7943
|
-
} catch (error) {
|
|
7944
|
-
if (isNothingThere(error)) return false;
|
|
7945
|
-
throw error;
|
|
7946
|
-
}
|
|
7947
|
-
},
|
|
7948
|
-
write: async (path, data, options = {}) => {
|
|
7949
|
-
await mkdir(dirname(path), { recursive: true });
|
|
7950
|
-
const target = options.exclusive === true ? path : await targetOf$1(path);
|
|
7951
|
-
const folder = dirname(target);
|
|
7952
|
-
if (!swept.has(folder)) {
|
|
7953
|
-
await sweep(folder);
|
|
7954
|
-
swept.add(folder);
|
|
7955
|
-
}
|
|
7956
|
-
const temp = tempPathOf(target);
|
|
7957
|
-
try {
|
|
7958
|
-
await writeWithMode(temp, data, options.mode);
|
|
7959
|
-
if (options.exclusive === true) {
|
|
7960
|
-
await linkNew(temp, target, data, options.mode);
|
|
7961
|
-
await rm(temp);
|
|
7962
|
-
} else await renameRetrying(temp, target);
|
|
7963
|
-
} catch (error) {
|
|
7964
|
-
await rm(temp, { force: true });
|
|
7965
|
-
throw error;
|
|
7966
|
-
}
|
|
7967
|
-
},
|
|
7968
|
-
read: (path) => readFile(path),
|
|
7969
|
-
list: (folder) => readdir(folder),
|
|
7970
|
-
remove: (path) => rm(path, {
|
|
7971
|
-
recursive: true,
|
|
7972
|
-
force: true
|
|
7973
|
-
}),
|
|
7974
|
-
mkdir: async (path, options) => {
|
|
7975
|
-
const mode = options?.mode;
|
|
7976
|
-
if (options?.exclusive !== true) {
|
|
7977
|
-
await mkdir(path, {
|
|
7978
|
-
recursive: true,
|
|
7979
|
-
mode
|
|
7980
|
-
});
|
|
7981
|
-
return;
|
|
7982
|
-
}
|
|
7983
|
-
await mkdir(dirname(path), {
|
|
7984
|
-
recursive: true,
|
|
7985
|
-
mode
|
|
7986
|
-
});
|
|
7987
|
-
await mkdir(path, { mode });
|
|
7988
|
-
},
|
|
7989
|
-
stat: async (path) => {
|
|
7990
|
-
try {
|
|
7991
|
-
const { size, dev, ino } = await stat(path, { bigint: true });
|
|
7992
|
-
return {
|
|
7993
|
-
size: Number(size),
|
|
7994
|
-
id: `${dev}:${ino}`
|
|
7995
|
-
};
|
|
7996
|
-
} catch (error) {
|
|
7997
|
-
if (isNothingThere(error)) return void 0;
|
|
7998
|
-
throw error;
|
|
7999
|
-
}
|
|
7777
|
+
async runToEnd(name, work) {
|
|
7778
|
+
this.#graceCancel.abort();
|
|
7779
|
+
if (this.signal.aborted) this.#finishing(name);
|
|
7780
|
+
const step = work();
|
|
7781
|
+
const settled = step.then(() => {}, () => {});
|
|
7782
|
+
this.#steps.set(settled, name);
|
|
7783
|
+
try {
|
|
7784
|
+
return await step;
|
|
7785
|
+
} finally {
|
|
7786
|
+
this.#steps.delete(settled);
|
|
7787
|
+
this.#startGrace();
|
|
8000
7788
|
}
|
|
8001
|
-
}
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
* `files` resolving paths against `cwd`, its failures classified by errno; `list` drops the
|
|
8005
|
-
* Runtime's temp files and sorts.
|
|
8006
|
-
*/
|
|
8007
|
-
function classifiedFiles(files, { cwd, fileError }) {
|
|
8008
|
-
async function at(access, path, work) {
|
|
8009
|
-
const target = resolve(cwd, path);
|
|
7789
|
+
}
|
|
7790
|
+
/** Waits for the Command, its must-finish steps, and no longer than the grace once interrupted. */
|
|
7791
|
+
async settle(work) {
|
|
8010
7792
|
try {
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
7793
|
+
const value = await Promise.race([work, this.#graceOver]);
|
|
7794
|
+
if (this.signal.aborted || value === GRACE_OVER) throw this.signal.reason;
|
|
7795
|
+
return value;
|
|
7796
|
+
} finally {
|
|
7797
|
+
await this.#stepsDone();
|
|
7798
|
+
this.close();
|
|
8017
7799
|
}
|
|
8018
7800
|
}
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
7801
|
+
/**
|
|
7802
|
+
* The error a run ends with: an interrupt or the run deadline outranks what the Command threw,
|
|
7803
|
+
* unless the Command threw its own `INTERRUPTED`, saying what it finished before it stopped.
|
|
7804
|
+
* Otherwise each leaf that is not a `CliError` and whose cause chain holds a deadline's abort is
|
|
7805
|
+
* reported as that abort, so a declared error keeps its own code.
|
|
7806
|
+
*/
|
|
7807
|
+
errorAfter(error) {
|
|
7808
|
+
if (this.signal.aborted) return error instanceof CliError && error.code === "INTERRUPTED" ? error : this.signal.reason;
|
|
7809
|
+
const leaves = errorsOf(error).map((leaf) => this.#abortIn(leaf) ?? leaf);
|
|
7810
|
+
return leaves.length === 1 ? leaves[0] : new AggregateError(leaves);
|
|
7811
|
+
}
|
|
7812
|
+
#abortIn(error) {
|
|
7813
|
+
if (error instanceof CliError) return void 0;
|
|
7814
|
+
return causesOf(error).find((each) => each instanceof CliError && this.#abortReasons.has(each));
|
|
7815
|
+
}
|
|
7816
|
+
#onStop() {
|
|
7817
|
+
for (const name of this.#steps.values()) this.#finishing(name);
|
|
7818
|
+
this.#startGrace();
|
|
7819
|
+
}
|
|
7820
|
+
#finishing(name) {
|
|
7821
|
+
this.#progress(`finishing ${name}`);
|
|
7822
|
+
}
|
|
7823
|
+
#startGrace() {
|
|
7824
|
+
if (this.signal.aborted && this.#steps.size === 0) {
|
|
7825
|
+
this.#graceCancel.abort();
|
|
7826
|
+
this.#graceCancel = new AbortController();
|
|
7827
|
+
after(this.#clock, GRACE_MS, this.#graceCancel.signal, () => this.#endGrace(GRACE_OVER));
|
|
7828
|
+
}
|
|
7829
|
+
}
|
|
7830
|
+
async #stepsDone() {
|
|
7831
|
+
while (this.#steps.size > 0) await Promise.all(this.#steps.keys());
|
|
7832
|
+
}
|
|
7833
|
+
/** Stops the run's timers and its hold on the caller's signal, for a run that never settles. */
|
|
7834
|
+
close() {
|
|
7835
|
+
this.#graceCancel.abort();
|
|
7836
|
+
this.#disposed.abort();
|
|
7837
|
+
this.#outer?.removeEventListener("abort", this.#onOuterAbort);
|
|
7838
|
+
}
|
|
7839
|
+
};
|
|
8029
7840
|
//#endregion
|
|
8030
|
-
//#region ../agent-cli/dist/
|
|
8031
|
-
const
|
|
8032
|
-
/**
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
hint: `once the user agrees to replace ${them}, run the Next step`,
|
|
8043
|
-
next: [rerun({ add: [FORCE_FLAG.flag] })]
|
|
7841
|
+
//#region ../agent-cli/dist/built-ins/install/global-npm.js
|
|
7842
|
+
const NOT_GLOBAL_INSTALL = "NOT_GLOBAL_INSTALL";
|
|
7843
|
+
/** Where npm puts global packages under its global prefix. */
|
|
7844
|
+
function globalModulesDir(prefix) {
|
|
7845
|
+
return process.platform === "win32" ? join(prefix, "node_modules") : join(prefix, "lib", "node_modules");
|
|
7846
|
+
}
|
|
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))]
|
|
8044
7853
|
});
|
|
8045
7854
|
}
|
|
8046
|
-
/**
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
} catch (error) {
|
|
8059
|
-
unwritten.add(target);
|
|
8060
|
-
if (!(error instanceof CliError) || error.code !== "ALREADY_EXISTS") throw error;
|
|
8061
|
-
throw new CliError({
|
|
8062
|
-
code: ALREADY_EXISTS,
|
|
8063
|
-
message: error.message,
|
|
8064
|
-
hint: "once the user agrees to replace it, run the Next step",
|
|
8065
|
-
next: [forceStep]
|
|
8066
|
-
}, { cause: error });
|
|
8067
|
-
}
|
|
8068
|
-
};
|
|
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`);
|
|
8069
7867
|
return {
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
write: (path, data, options) => unwritten.has(resolve(cwd, path)) ? exclusiveWrite(path, data, options) : files.write(path, data, options)
|
|
8073
|
-
},
|
|
8074
|
-
guard: (outputs, step) => {
|
|
8075
|
-
forceStep = step;
|
|
8076
|
-
for (const path of outputs) unwritten.add(resolve(cwd, path));
|
|
8077
|
-
}
|
|
7868
|
+
prefix,
|
|
7869
|
+
packageDir
|
|
8078
7870
|
};
|
|
8079
7871
|
}
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
await Promise.all([...folders].map(async (folder) => {
|
|
8087
|
-
const probe = tempPathOf(join(folder, "outputs-probe"));
|
|
8088
|
-
try {
|
|
8089
|
-
await files.mkdir(folder);
|
|
8090
|
-
await files.write(probe, "");
|
|
8091
|
-
await files.remove(probe);
|
|
8092
|
-
} catch (error) {
|
|
8093
|
-
throw fileError(error, {
|
|
8094
|
-
path: folder,
|
|
8095
|
-
access: "write"
|
|
8096
|
-
});
|
|
8097
|
-
}
|
|
8098
|
-
}));
|
|
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}`];
|
|
8099
7878
|
}
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
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;
|
|
8105
7885
|
return {
|
|
8106
|
-
|
|
8107
|
-
|
|
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}`
|
|
8108
7892
|
};
|
|
8109
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
|
|
8110
7900
|
/** Every file under `dir`, relative to it and sorted; none when `dir` is missing. */
|
|
8111
7901
|
async function filesIn(dir) {
|
|
8112
7902
|
return (await orMissing(readdir(dir, {
|
|
@@ -8114,34 +7904,11 @@ async function filesIn(dir) {
|
|
|
8114
7904
|
withFileTypes: true
|
|
8115
7905
|
})) ?? []).filter((entry) => entry.isFile()).map((entry) => relative(dir, join(entry.parentPath, entry.name))).sort();
|
|
8116
7906
|
}
|
|
8117
|
-
/** The versions of the package's skill in `source` and of its copy in `copy`. */
|
|
8118
|
-
async function skillVersionsOf(source, copy) {
|
|
8119
|
-
const [shipped, installed] = await Promise.all([readFile(join(source, "SKILL.md"), "utf8"), orMissing(readFile(join(copy, "SKILL.md"), "utf8"))]);
|
|
8120
|
-
const packageVersion = skillVersionIn(shipped) ?? null;
|
|
8121
|
-
const copyVersion = installed === void 0 ? void 0 : skillVersionIn(installed) ?? null;
|
|
8122
|
-
return {
|
|
8123
|
-
packageVersion,
|
|
8124
|
-
copyVersion,
|
|
8125
|
-
same: copyVersion === packageVersion
|
|
8126
|
-
};
|
|
8127
|
-
}
|
|
8128
|
-
const installSchema = object({
|
|
8129
|
-
copy: string(),
|
|
8130
|
-
written: array(string()),
|
|
8131
|
-
removed: array(string()),
|
|
8132
|
-
agentsMd: object({
|
|
8133
|
-
path: string(),
|
|
8134
|
-
changed: boolean()
|
|
8135
|
-
}).nullable(),
|
|
8136
|
-
handEdits: array(string())
|
|
8137
|
-
});
|
|
8138
7907
|
async function sameContent(left, right) {
|
|
8139
7908
|
const [a, b] = await Promise.all([readFile(left), readFile(right)]);
|
|
8140
7909
|
return a.equals(b);
|
|
8141
7910
|
}
|
|
8142
|
-
async function
|
|
8143
|
-
const source = join(packageRoot, folder);
|
|
8144
|
-
const { root, copy } = await skillCopyOf(cli, cwd);
|
|
7911
|
+
async function syncOf(source, copy) {
|
|
8145
7912
|
const [shipped, present] = await Promise.all([filesIn(source), filesIn(copy)]);
|
|
8146
7913
|
const differs = await Promise.all(shipped.map(async (file) => present.includes(file) && !await sameContent(join(source, file), join(copy, file))));
|
|
8147
7914
|
return {
|
|
@@ -8149,604 +7916,1000 @@ async function planOf(cli, folder, agents, cwd, packageRoot) {
|
|
|
8149
7916
|
copy,
|
|
8150
7917
|
write: shipped.filter((file) => !present.includes(file)),
|
|
8151
7918
|
replace: shipped.filter((_file, index) => differs[index]),
|
|
8152
|
-
remove: present.filter((file) => !shipped.includes(file))
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
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;
|
|
7950
|
+
});
|
|
7951
|
+
}
|
|
7952
|
+
const homeSyncSchema = object({
|
|
7953
|
+
copies: array(string()),
|
|
7954
|
+
written: array(string()),
|
|
7955
|
+
removed: array(string())
|
|
7956
|
+
});
|
|
7957
|
+
/**
|
|
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.
|
|
7960
|
+
*/
|
|
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);
|
|
7968
|
+
return {
|
|
7969
|
+
copies,
|
|
7970
|
+
written: syncs.flatMap((sync) => [...sync.write, ...sync.replace]),
|
|
7971
|
+
removed: syncs.flatMap((sync) => sync.remove)
|
|
7972
|
+
};
|
|
7973
|
+
}
|
|
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`];
|
|
7977
|
+
}
|
|
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`
|
|
8014
|
+
};
|
|
8015
|
+
}
|
|
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
|
+
};
|
|
8026
|
+
}
|
|
8027
|
+
//#endregion
|
|
8028
|
+
//#region ../agent-cli/dist/built-ins/upgrades.js
|
|
8029
|
+
/** A skipped release's Upgrade steps, or its whole section when it lists none. */
|
|
8030
|
+
const upgradeSchema = union([object({
|
|
8031
|
+
version: string(),
|
|
8032
|
+
steps: array(string())
|
|
8033
|
+
}), object({
|
|
8034
|
+
version: string(),
|
|
8035
|
+
notes: string()
|
|
8036
|
+
})]);
|
|
8037
|
+
const RELEASE_HEADING = /^## v?(\S+)/;
|
|
8038
|
+
const UPGRADE_HEADING = /^### Upgrade from v\S+$/;
|
|
8039
|
+
const HEADING = /^#{1,6} /;
|
|
8040
|
+
const ITEM = /^[-*] /;
|
|
8041
|
+
const NOTHING_TO_DO = /^nothing to do\.?$/i;
|
|
8042
|
+
function releasesIn(changelog) {
|
|
8043
|
+
const releases = [];
|
|
8044
|
+
let current;
|
|
8045
|
+
for (const line of changelog.split(/\r?\n/)) {
|
|
8046
|
+
const heading = RELEASE_HEADING.exec(line);
|
|
8047
|
+
if (heading !== null) {
|
|
8048
|
+
const version = heading[1] ?? "";
|
|
8049
|
+
current = isVersion(version) ? {
|
|
8050
|
+
version,
|
|
8051
|
+
lines: []
|
|
8052
|
+
} : void 0;
|
|
8053
|
+
if (current !== void 0) releases.push(current);
|
|
8054
|
+
} else current?.lines.push(line);
|
|
8055
|
+
}
|
|
8056
|
+
return releases;
|
|
8057
|
+
}
|
|
8058
|
+
function listedSteps(lines) {
|
|
8059
|
+
const start = lines.findIndex((line) => UPGRADE_HEADING.test(line.trimEnd()));
|
|
8060
|
+
if (start === -1) return [];
|
|
8061
|
+
const end = lines.findIndex((line, i) => i > start && HEADING.test(line));
|
|
8062
|
+
const body = lines.slice(start + 1, end === -1 ? void 0 : end).filter((line) => line.trim() !== "");
|
|
8063
|
+
const starts = body.flatMap((line, i) => ITEM.test(line) ? [i] : []);
|
|
8064
|
+
return starts.map((first, i) => body.slice(first, starts[i + 1]).map((line) => line.trim()).join(" ").replace(ITEM, ""));
|
|
8065
|
+
}
|
|
8066
|
+
function upgradeOf({ version, lines }) {
|
|
8067
|
+
const listed = listedSteps(lines);
|
|
8068
|
+
if (listed.length === 0) return [{
|
|
8069
|
+
version,
|
|
8070
|
+
notes: lines.join("\n").trim()
|
|
8071
|
+
}];
|
|
8072
|
+
const todo = listed.filter((step) => !NOTHING_TO_DO.test(step));
|
|
8073
|
+
return todo.length === 0 ? [] : [{
|
|
8074
|
+
version,
|
|
8075
|
+
steps: todo
|
|
8076
|
+
}];
|
|
8077
|
+
}
|
|
8078
|
+
/**
|
|
8079
|
+
* What each release after `from` up to `to` asks of the operator, oldest first, from the
|
|
8080
|
+
* CHANGELOG.md in `packageDir`; none when the package ships no CHANGELOG.
|
|
8081
|
+
*/
|
|
8082
|
+
async function upgradesOf(packageDir, from, to) {
|
|
8083
|
+
let changelog;
|
|
8084
|
+
try {
|
|
8085
|
+
changelog = await readFile(join(packageDir, "CHANGELOG.md"), "utf8");
|
|
8086
|
+
} catch (error) {
|
|
8087
|
+
if (isMissingFile(error)) return [];
|
|
8088
|
+
throw error;
|
|
8089
|
+
}
|
|
8090
|
+
return releasesIn(changelog).filter(({ version }) => compareVersions(version, from) > 0 && compareVersions(version, to) <= 0).sort((a, b) => compareVersions(a.version, b.version)).flatMap(upgradeOf);
|
|
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))
|
|
8156
8122
|
};
|
|
8157
8123
|
}
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
add: "add the Agents section to",
|
|
8161
|
-
rewrite: "rewrite the Agents section in"
|
|
8162
|
-
};
|
|
8163
|
-
function renderPlan(cli, plan) {
|
|
8164
|
-
const files = [
|
|
8165
|
-
...plan.write.map((file) => ` write ${file}`),
|
|
8166
|
-
...plan.replace.map((file) => ` replace ${file}`),
|
|
8167
|
-
...plan.remove.map((file) => ` remove ${file}`)
|
|
8168
|
-
];
|
|
8169
|
-
const { agentsMd } = plan;
|
|
8170
|
-
return [
|
|
8171
|
-
files.length === 0 ? `the ${cli.name} skill at ${plan.copy} already matches the package` : `would copy the ${cli.name} skill to ${plan.copy}:`,
|
|
8172
|
-
...files,
|
|
8173
|
-
...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}`],
|
|
8174
|
-
...plan.handEdits.map((edit) => `by hand: ${edit}`)
|
|
8175
|
-
].join("\n");
|
|
8124
|
+
function needsInstall({ from, latest }) {
|
|
8125
|
+
return from === null || standingOf$1(from, latest) === "behind";
|
|
8176
8126
|
}
|
|
8177
|
-
function
|
|
8178
|
-
|
|
8179
|
-
return [
|
|
8180
|
-
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}`,
|
|
8181
|
-
...agentsMd === null ? [] : [agentsMd.changed ? `wrote the ${cli.name} Agents section in ${agentsMd.path}` : `${agentsMd.path} already holds the ${cli.name} Agents section`],
|
|
8182
|
-
...data.handEdits.map((edit) => `by hand: ${edit}`)
|
|
8183
|
-
].join("\n");
|
|
8127
|
+
function binDirOf(prefix) {
|
|
8128
|
+
return process.platform === "win32" ? prefix : join(prefix, "bin");
|
|
8184
8129
|
}
|
|
8185
|
-
function
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
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: []
|
|
8192
8143
|
});
|
|
8193
8144
|
}
|
|
8194
|
-
|
|
8195
|
-
|
|
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 }) {
|
|
8196
8170
|
return defineCommand$1({
|
|
8197
|
-
description:
|
|
8171
|
+
description: "install this CLI and its skill on this machine",
|
|
8198
8172
|
examples: [{
|
|
8199
|
-
command: `${cli.name}
|
|
8200
|
-
note: "install the
|
|
8173
|
+
command: `${cli.name} setup`,
|
|
8174
|
+
note: "install the latest version and the skill"
|
|
8201
8175
|
}, {
|
|
8202
|
-
command: `${cli.name}
|
|
8176
|
+
command: `${cli.name} setup --dry-run`,
|
|
8203
8177
|
note: "show what it would change"
|
|
8204
8178
|
}],
|
|
8205
8179
|
effect: "write",
|
|
8206
|
-
output:
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
if (
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
await ctx.files.write(agentsMd.path, withSection(agentsMd.agents, existing));
|
|
8218
|
-
}
|
|
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
|
+
});
|
|
8219
8191
|
return {
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
},
|
|
8227
|
-
handEdits: [...plan.handEdits]
|
|
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)
|
|
8228
8198
|
};
|
|
8229
8199
|
},
|
|
8230
|
-
|
|
8231
|
-
|
|
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")
|
|
8232
8231
|
});
|
|
8233
8232
|
}
|
|
8234
8233
|
//#endregion
|
|
8235
|
-
//#region ../agent-cli/dist/built-ins/
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
packageVersion: string().nullable(),
|
|
8246
|
-
/** A personal copy, which Claude Code prefers over the project's. */
|
|
8247
|
-
home: string().optional()
|
|
8234
|
+
//#region ../agent-cli/dist/built-ins/update.js
|
|
8235
|
+
const updateSchema = object({
|
|
8236
|
+
from: string(),
|
|
8237
|
+
latest: string(),
|
|
8238
|
+
installed: object({
|
|
8239
|
+
version: string(),
|
|
8240
|
+
prefix: string()
|
|
8241
|
+
}).nullable(),
|
|
8242
|
+
skill: homeSyncSchema.nullable(),
|
|
8243
|
+
upgrades: array(upgradeSchema)
|
|
8248
8244
|
});
|
|
8249
|
-
function
|
|
8250
|
-
return
|
|
8251
|
-
}
|
|
8252
|
-
function compared(name, { copy, copyVersion, packageVersion }, same) {
|
|
8253
|
-
const stated = `${copy} ${versionLabel(copyVersion)}`;
|
|
8254
|
-
return same ? `${stated}, the version ${name} ships` : `${stated} differs from the ${versionLabel(packageVersion)} that ${name} ships`;
|
|
8255
|
-
}
|
|
8256
|
-
/** The project root's copy against the package's, and any under home. */
|
|
8257
|
-
async function findingOf(cli, folder, install, ctx) {
|
|
8258
|
-
const { copy } = await skillCopyOf(cli, ctx.cwd);
|
|
8259
|
-
const versions = await skillVersionsOf(join(ctx.packageRoot, folder), copy);
|
|
8260
|
-
const { packageVersion } = versions;
|
|
8261
|
-
const copyVersion = versions.copyVersion ?? null;
|
|
8262
|
-
const home = await personalCopyOf(cli.name, copy, ctx);
|
|
8263
|
-
const skill = {
|
|
8264
|
-
copy,
|
|
8265
|
-
copyVersion,
|
|
8266
|
-
packageVersion,
|
|
8267
|
-
...home === void 0 ? {} : { home }
|
|
8268
|
-
};
|
|
8269
|
-
const head = versions.copyVersion === void 0 ? `no copy of the ${cli.name} skill at ${copy}` : compared(cli.name, skill, versions.same);
|
|
8270
|
-
const message = home === void 0 ? head : `${head}; remove ${home}: Claude Code prefers a personal skill over the project's`;
|
|
8271
|
-
if (!versions.same) return {
|
|
8272
|
-
state: "problem",
|
|
8273
|
-
message,
|
|
8274
|
-
fix: `${install.runAs} skill install`,
|
|
8275
|
-
skill
|
|
8276
|
-
};
|
|
8277
|
-
return home === void 0 ? {
|
|
8278
|
-
state: "ok",
|
|
8279
|
-
message,
|
|
8280
|
-
skill
|
|
8281
|
-
} : {
|
|
8282
|
-
state: "problem",
|
|
8283
|
-
message,
|
|
8284
|
-
skill
|
|
8285
|
-
};
|
|
8286
|
-
}
|
|
8287
|
-
async function notice(cli, folder, install, { builtIn, bin }, ctx) {
|
|
8288
|
-
if (builtIn || bin === void 0 || cli.commands === void 0) return;
|
|
8289
|
-
let finding;
|
|
8290
|
-
try {
|
|
8291
|
-
finding = await findingOf(cli, folder, install, ctx);
|
|
8292
|
-
} catch (error) {
|
|
8293
|
-
if (errnoOf(error) === void 0) throw error;
|
|
8294
|
-
ctx.notice(`could not check the ${cli.name} skill: ${messageOf(error)}`);
|
|
8295
|
-
return;
|
|
8296
|
-
}
|
|
8297
|
-
if (finding.state !== "problem") return;
|
|
8298
|
-
ctx.notice(finding.fix === void 0 ? finding.message : `${finding.message}; run \`${finding.fix}\``);
|
|
8299
|
-
}
|
|
8300
|
-
/** A skill shipped in the package at `packaged`, which `skill install` copies to the project root. */
|
|
8301
|
-
function packagedSkill(cli, { packaged: folder, agents: text }, install) {
|
|
8302
|
-
const agents = text === void 0 ? void 0 : {
|
|
8303
|
-
name: cli.name,
|
|
8304
|
-
text
|
|
8305
|
-
};
|
|
8306
|
-
return {
|
|
8307
|
-
kind: "packaged",
|
|
8308
|
-
builtIns: { skill: { install: skillInstallCommand(cli, folder, agents) } },
|
|
8309
|
-
rows: [{
|
|
8310
|
-
name: "skill",
|
|
8311
|
-
advisory: true,
|
|
8312
|
-
run: (ctx) => findingOf(cli, folder, install, ctx)
|
|
8313
|
-
}, ...agents === void 0 ? [] : [agentsRow(agents, install)]],
|
|
8314
|
-
notice: (launch, ctx) => notice(cli, folder, install, launch, ctx),
|
|
8315
|
-
differing: async ({ cwd, packageRoot }) => {
|
|
8316
|
-
const { copy } = await skillCopyOf(cli, cwd);
|
|
8317
|
-
const { copyVersion, packageVersion, same } = await skillVersionsOf(join(packageRoot, folder), copy);
|
|
8318
|
-
return copyVersion === void 0 || same ? [] : [compared(cli.name, {
|
|
8319
|
-
copy,
|
|
8320
|
-
copyVersion,
|
|
8321
|
-
packageVersion
|
|
8322
|
-
}, false)];
|
|
8323
|
-
},
|
|
8324
|
-
nextAt: async (_version, ctx) => {
|
|
8325
|
-
const { fix } = await findingOf(cli, folder, install, ctx);
|
|
8326
|
-
return fix === void 0 ? [] : [fix];
|
|
8327
|
-
}
|
|
8328
|
-
};
|
|
8245
|
+
function nothingToInstall({ from, latest }) {
|
|
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`;
|
|
8329
8247
|
}
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8248
|
+
function renderUpdate(cli, install, update) {
|
|
8249
|
+
const { installed } = update;
|
|
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");
|
|
8336
8256
|
}
|
|
8337
|
-
|
|
8338
|
-
//#region ../agent-cli/dist/context/interrupts.js
|
|
8339
|
-
const INTERRUPTED = "INTERRUPTED";
|
|
8340
|
-
const DEADLINE_EXCEEDED = "DEADLINE_EXCEEDED";
|
|
8341
|
-
/** How long an interrupted Command may take to settle before the run ends without it. */
|
|
8342
|
-
const GRACE_MS = 3e3;
|
|
8343
|
-
const GRACE_OVER = Symbol("grace over");
|
|
8344
|
-
function interruptedError(rerun) {
|
|
8257
|
+
function interruptedAfterInstall(plan) {
|
|
8345
8258
|
return new CliError({
|
|
8346
8259
|
code: INTERRUPTED,
|
|
8347
|
-
message:
|
|
8348
|
-
hint: "to
|
|
8349
|
-
next: [rerun]
|
|
8350
|
-
});
|
|
8351
|
-
}
|
|
8352
|
-
const BOUND_WORDS = {
|
|
8353
|
-
deadline: "deadline",
|
|
8354
|
-
ignoreStop: "timeout"
|
|
8355
|
-
};
|
|
8356
|
-
function waitHint(bound) {
|
|
8357
|
-
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";
|
|
8358
|
-
}
|
|
8359
|
-
function deadlineError(commandLine, ms, advice, bound = "deadline") {
|
|
8360
|
-
return new CliError({
|
|
8361
|
-
code: DEADLINE_EXCEEDED,
|
|
8362
|
-
message: `${commandLine} ran past its ${ms} ms ${BOUND_WORDS[bound]}`,
|
|
8363
|
-
...advice
|
|
8260
|
+
message: `interrupted, but ${plan.package}@${plan.latest} had already been installed with the npm under ${plan.prefix}, replacing ${plan.from}`,
|
|
8261
|
+
hint: "nothing to run again"
|
|
8364
8262
|
});
|
|
8365
8263
|
}
|
|
8366
|
-
/**
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
this.#outer = outer;
|
|
8390
|
-
this.#clock = clock;
|
|
8391
|
-
this.#progress = progress;
|
|
8392
|
-
this.signal = this.#controller.signal;
|
|
8393
|
-
this.signal.addEventListener("abort", () => this.#onStop(), { once: true });
|
|
8394
|
-
if (outer?.aborted === true) this.#onOuterAbort();
|
|
8395
|
-
else outer?.addEventListener("abort", this.#onOuterAbort, { once: true });
|
|
8396
|
-
if (runDeadline !== void 0) {
|
|
8397
|
-
const { ms, advice } = runDeadline;
|
|
8398
|
-
after(clock, ms, this.#disposed.signal, () => this.#controller.abort(deadlineError(commandLine, ms, advice())));
|
|
8399
|
-
}
|
|
8400
|
-
}
|
|
8401
|
-
deadline(ms, options = {}) {
|
|
8402
|
-
return options.ignoreStop === true ? this.#abortsAfter(ms, "ignoreStop") : AbortSignal.any([this.signal, this.#abortsAfter(ms, "deadline")]);
|
|
8403
|
-
}
|
|
8404
|
-
#abortsAfter(ms, bound) {
|
|
8405
|
-
const controller = new AbortController();
|
|
8406
|
-
after(this.#clock, ms, this.#disposed.signal, () => {
|
|
8407
|
-
const advice = {
|
|
8408
|
-
hint: waitHint(bound),
|
|
8409
|
-
next: [this.#rerun]
|
|
8264
|
+
/** The built-in `update`, for a CLI installed globally with npm. */
|
|
8265
|
+
function updateCommand(cli, install, { bin, node }) {
|
|
8266
|
+
return defineCommand$1({
|
|
8267
|
+
description: "install the latest version of this CLI from its registry",
|
|
8268
|
+
examples: [{
|
|
8269
|
+
command: `${cli.name} update`,
|
|
8270
|
+
note: "install the latest version and show what changed"
|
|
8271
|
+
}, {
|
|
8272
|
+
command: `${cli.name} update --dry-run`,
|
|
8273
|
+
note: "show the version it would install"
|
|
8274
|
+
}],
|
|
8275
|
+
effect: "write",
|
|
8276
|
+
output: updateSchema,
|
|
8277
|
+
plan: async (_input, ctx) => {
|
|
8278
|
+
const npm = globalNpm(cli, install, ctx, node);
|
|
8279
|
+
const copy = await globalInstallOf(bin, npm);
|
|
8280
|
+
return {
|
|
8281
|
+
package: install.package,
|
|
8282
|
+
from: cli.version,
|
|
8283
|
+
latest: await latestVersion(npm),
|
|
8284
|
+
prefix: copy.prefix,
|
|
8285
|
+
packageDir: copy.packageDir,
|
|
8286
|
+
copies: cli.skill === void 0 ? [] : await homeCopiesOf(cli.name, ctx.env)
|
|
8410
8287
|
};
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
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)
|
|
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";
|
|
8429
8337
|
}
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
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;
|
|
8440
8353
|
}
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
*/
|
|
8447
|
-
errorAfter(error) {
|
|
8448
|
-
if (this.signal.aborted) return error instanceof CliError && error.code === "INTERRUPTED" ? error : this.signal.reason;
|
|
8449
|
-
const leaves = errorsOf(error).map((leaf) => this.#abortIn(leaf) ?? leaf);
|
|
8450
|
-
return leaves.length === 1 ? leaves[0] : new AggregateError(leaves);
|
|
8354
|
+
}
|
|
8355
|
+
async function writeWithMode(path, data, mode) {
|
|
8356
|
+
if (mode === void 0) {
|
|
8357
|
+
await writeFile(path, data);
|
|
8358
|
+
return;
|
|
8451
8359
|
}
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
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);
|
|
8455
8376
|
}
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
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;
|
|
8459
8388
|
}
|
|
8460
|
-
|
|
8461
|
-
|
|
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);
|
|
8462
8401
|
}
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
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
|
+
}
|
|
8435
|
+
},
|
|
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;
|
|
8450
|
+
}
|
|
8451
|
+
await mkdir(dirname(path), {
|
|
8452
|
+
recursive: true,
|
|
8453
|
+
mode
|
|
8454
|
+
});
|
|
8455
|
+
await mkdir(path, { mode });
|
|
8456
|
+
},
|
|
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
|
+
});
|
|
8468
8485
|
}
|
|
8469
8486
|
}
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
}
|
|
8479
|
-
}
|
|
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
|
+
};
|
|
8496
|
+
}
|
|
8480
8497
|
//#endregion
|
|
8481
|
-
//#region ../agent-cli/dist/built-ins/install/
|
|
8482
|
-
const
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8498
|
+
//#region ../agent-cli/dist/built-ins/install/release-check.js
|
|
8499
|
+
const CHECK_INTERVAL_MS = 864e5;
|
|
8500
|
+
const checkSchema = object({
|
|
8501
|
+
checkedAt: number(),
|
|
8502
|
+
latest: string().optional(),
|
|
8503
|
+
error: string().optional()
|
|
8504
|
+
});
|
|
8505
|
+
const CHECK_SCRIPT = `
|
|
8506
|
+
const { spawnSync } = require("node:child_process");
|
|
8507
|
+
const { mkdtempSync, renameSync, rmSync, writeFileSync } = require("node:fs");
|
|
8508
|
+
const { tmpdir } = require("node:os");
|
|
8509
|
+
const { join } = require("node:path");
|
|
8510
|
+
const [file, checkedAt, previous, npm, ...args] = process.argv.slice(1);
|
|
8511
|
+
const windows = process.platform === "win32";
|
|
8512
|
+
const cache = mkdtempSync(join(tmpdir(), "release-check-"));
|
|
8513
|
+
const write = (check) => {
|
|
8514
|
+
const temp = file + "." + process.pid + ".tmp";
|
|
8515
|
+
writeFileSync(temp, JSON.stringify({ checkedAt: Number(checkedAt), ...check }));
|
|
8516
|
+
renameSync(temp, file);
|
|
8517
|
+
};
|
|
8518
|
+
const latestOf = (stdout) => {
|
|
8519
|
+
try {
|
|
8520
|
+
const parsed = JSON.parse(stdout);
|
|
8521
|
+
const latest = Array.isArray(parsed) ? parsed.at(-1) : parsed;
|
|
8522
|
+
return typeof latest === "string" && /^\\d+\\.\\d+\\.\\d+/.test(latest) ? latest : undefined;
|
|
8523
|
+
} catch {
|
|
8524
|
+
return undefined;
|
|
8525
|
+
}
|
|
8526
|
+
};
|
|
8527
|
+
try {
|
|
8528
|
+
const answer = spawnSync(windows ? '"' + npm + '"' : npm, args, {
|
|
8529
|
+
encoding: "utf8",
|
|
8530
|
+
shell: windows,
|
|
8531
|
+
windowsHide: true,
|
|
8532
|
+
env: { ...process.env, npm_config_cache: join(cache, "cache"), npm_config_logs_max: "0" },
|
|
8533
|
+
});
|
|
8534
|
+
const latest = answer.status === 0 ? latestOf(answer.stdout) : undefined;
|
|
8535
|
+
if (latest !== undefined) {
|
|
8536
|
+
write({ latest });
|
|
8537
|
+
} else {
|
|
8538
|
+
const said = (answer.stderr || "").trim().split(/\\r?\\n/).slice(0, 5).join("; ");
|
|
8539
|
+
const error = answer.error ? answer.error.message : said || "npm answered " + answer.stdout.trim();
|
|
8540
|
+
write({ latest: previous || undefined, error });
|
|
8541
|
+
}
|
|
8542
|
+
} finally {
|
|
8543
|
+
rmSync(cache, { recursive: true, force: true });
|
|
8544
|
+
}
|
|
8545
|
+
`;
|
|
8546
|
+
function isOff(cliName, env) {
|
|
8547
|
+
return Boolean(env[cliVariable(cliName, "NO_RELEASE_CHECK")] || env.CI);
|
|
8548
|
+
}
|
|
8549
|
+
function cacheFileOf(cliName, env) {
|
|
8550
|
+
const cache = env.XDG_CACHE_HOME || join(env.HOME || env.USERPROFILE || homedir(), ".cache");
|
|
8551
|
+
return join(cache, cliName, "release-check.json");
|
|
8552
|
+
}
|
|
8553
|
+
function parsedCheck(text) {
|
|
8554
|
+
try {
|
|
8555
|
+
return checkSchema.safeParse(JSON.parse(text)).data;
|
|
8556
|
+
} catch {
|
|
8557
|
+
return;
|
|
8558
|
+
}
|
|
8486
8559
|
}
|
|
8487
|
-
function
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8560
|
+
async function lastCheck(file) {
|
|
8561
|
+
const text = await orMissing(readFile(file, "utf8"));
|
|
8562
|
+
return text === void 0 ? void 0 : parsedCheck(text);
|
|
8563
|
+
}
|
|
8564
|
+
async function writeCheck(file, check) {
|
|
8565
|
+
await mkdir(dirname(file), { recursive: true });
|
|
8566
|
+
const temp = tempPathOf(file);
|
|
8567
|
+
await writeFile(temp, JSON.stringify(check));
|
|
8568
|
+
await rename(temp, file);
|
|
8569
|
+
}
|
|
8570
|
+
async function startCheck(cli, install, file, last, { node }, ctx) {
|
|
8571
|
+
const checkedAt = ctx.clock.now();
|
|
8572
|
+
const previous = last?.latest ?? "";
|
|
8573
|
+
await writeCheck(file, {
|
|
8574
|
+
checkedAt,
|
|
8575
|
+
latest: last?.latest
|
|
8576
|
+
});
|
|
8577
|
+
const npm = globalNpm(cli, install, ctx, node);
|
|
8578
|
+
const { path, env } = npmOf(npm, process.platform === "win32");
|
|
8579
|
+
await ctx.spawnDetached(process.execPath, [
|
|
8580
|
+
"-e",
|
|
8581
|
+
CHECK_SCRIPT,
|
|
8582
|
+
file,
|
|
8583
|
+
String(checkedAt),
|
|
8584
|
+
previous,
|
|
8585
|
+
path,
|
|
8586
|
+
...latestViewArgs(npm.registry)
|
|
8587
|
+
], { env });
|
|
8588
|
+
}
|
|
8589
|
+
function debugLine(cli, ctx, text) {
|
|
8590
|
+
if (isDebug({
|
|
8591
|
+
env: ctx.env,
|
|
8592
|
+
cliName: cli.name
|
|
8593
|
+
})) ctx.progress(`release check failed: ${text}`);
|
|
8494
8594
|
}
|
|
8495
8595
|
/**
|
|
8496
|
-
*
|
|
8497
|
-
*
|
|
8596
|
+
* Tells the caller of a newer release that an earlier Release check found, and starts the next
|
|
8597
|
+
* check once the last is a day old.
|
|
8498
8598
|
*/
|
|
8499
|
-
async function
|
|
8500
|
-
if (bin === void 0
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8599
|
+
async function releaseCheck(cli, install, runAs, launch, ctx) {
|
|
8600
|
+
if (launch.builtIn || launch.bin === void 0 || cli.commands === void 0) return;
|
|
8601
|
+
if (isOff(cli.name, ctx.env)) return;
|
|
8602
|
+
try {
|
|
8603
|
+
await checkAndTell(cli, install, runAs, launch, ctx);
|
|
8604
|
+
} catch (error) {
|
|
8605
|
+
if (errnoOf(error) === void 0 && !(error instanceof CliError)) throw error;
|
|
8606
|
+
debugLine(cli, ctx, messageOf(error));
|
|
8607
|
+
}
|
|
8608
|
+
}
|
|
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 }) {
|
|
8507
8621
|
return {
|
|
8508
|
-
|
|
8509
|
-
|
|
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
|
+
}
|
|
8510
8645
|
};
|
|
8511
8646
|
}
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
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;
|
|
8518
8660
|
}
|
|
8519
|
-
|
|
8520
|
-
|
|
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
|
+
});
|
|
8521
8669
|
return {
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8670
|
+
runAs: runAsOf(cli),
|
|
8671
|
+
builtIns: () => ({}),
|
|
8672
|
+
exitClasses: [],
|
|
8673
|
+
check: () => ({
|
|
8674
|
+
install: skipped,
|
|
8675
|
+
release: async () => ({ skipped: await skipped() })
|
|
8676
|
+
}),
|
|
8677
|
+
notice: noNotice
|
|
8528
8678
|
};
|
|
8529
8679
|
}
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
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);
|
|
8533
8684
|
}
|
|
8534
8685
|
//#endregion
|
|
8535
|
-
//#region ../agent-cli/dist/built-ins/
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
}
|
|
8541
|
-
version: string(),
|
|
8542
|
-
notes: string()
|
|
8543
|
-
})]);
|
|
8544
|
-
const RELEASE_HEADING = /^## v?(\S+)/;
|
|
8545
|
-
const UPGRADE_HEADING = /^### Upgrade from v\S+$/;
|
|
8546
|
-
const HEADING = /^#{1,6} /;
|
|
8547
|
-
const ITEM = /^[-*] /;
|
|
8548
|
-
const NOTHING_TO_DO = /^nothing to do\.?$/i;
|
|
8549
|
-
function releasesIn(changelog) {
|
|
8550
|
-
const releases = [];
|
|
8551
|
-
let current;
|
|
8552
|
-
for (const line of changelog.split(/\r?\n/)) {
|
|
8553
|
-
const heading = RELEASE_HEADING.exec(line);
|
|
8554
|
-
if (heading !== null) {
|
|
8555
|
-
const version = heading[1] ?? "";
|
|
8556
|
-
current = isVersion(version) ? {
|
|
8557
|
-
version,
|
|
8558
|
-
lines: []
|
|
8559
|
-
} : void 0;
|
|
8560
|
-
if (current !== void 0) releases.push(current);
|
|
8561
|
-
} else current?.lines.push(line);
|
|
8562
|
-
}
|
|
8563
|
-
return releases;
|
|
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
|
+
};
|
|
8564
8692
|
}
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
const
|
|
8569
|
-
const
|
|
8570
|
-
const
|
|
8571
|
-
|
|
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`;
|
|
8572
8702
|
}
|
|
8573
|
-
function
|
|
8574
|
-
const
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
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
|
+
};
|
|
8584
8721
|
}
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
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
|
+
};
|
|
8598
8758
|
}
|
|
8599
8759
|
//#endregion
|
|
8600
|
-
//#region ../agent-cli/dist/built-ins/
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
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()
|
|
8607
8776
|
}).nullable(),
|
|
8608
|
-
|
|
8609
|
-
next: array(string())
|
|
8777
|
+
handEdits: array(string())
|
|
8610
8778
|
});
|
|
8611
|
-
function
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
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
|
+
};
|
|
8616
8786
|
}
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
...
|
|
8625
|
-
|
|
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");
|
|
8626
8805
|
}
|
|
8627
|
-
function
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
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");
|
|
8633
8813
|
}
|
|
8634
|
-
/** The built-in `
|
|
8635
|
-
function
|
|
8814
|
+
/** The built-in `skill install`, for a CLI whose skill ships in its package. */
|
|
8815
|
+
function skillInstallCommand(cli, folder, agents) {
|
|
8636
8816
|
return defineCommand$1({
|
|
8637
|
-
description: "
|
|
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",
|
|
8638
8818
|
examples: [{
|
|
8639
|
-
command: `${cli.name}
|
|
8640
|
-
note: "install the
|
|
8819
|
+
command: `${cli.name} skill install`,
|
|
8820
|
+
note: "install the skill this version ships"
|
|
8641
8821
|
}, {
|
|
8642
|
-
command: `${cli.name}
|
|
8643
|
-
note: "show
|
|
8822
|
+
command: `${cli.name} skill install --dry-run`,
|
|
8823
|
+
note: "show what it would change"
|
|
8644
8824
|
}],
|
|
8645
8825
|
effect: "write",
|
|
8646
|
-
output:
|
|
8647
|
-
plan:
|
|
8648
|
-
const npm = globalNpm(cli, install, ctx, node);
|
|
8649
|
-
const copy = await globalInstallOf(bin, npm);
|
|
8650
|
-
return {
|
|
8651
|
-
package: install.package,
|
|
8652
|
-
from: cli.version,
|
|
8653
|
-
latest: await latestVersion(npm),
|
|
8654
|
-
prefix: copy.prefix,
|
|
8655
|
-
packageDir: copy.packageDir
|
|
8656
|
-
};
|
|
8657
|
-
},
|
|
8826
|
+
output: installSchema,
|
|
8827
|
+
plan: (_input, ctx) => planOf(cli, folder, agents, ctx.cwd, ctx.packageRoot),
|
|
8658
8828
|
apply: async (plan, ctx) => {
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
latest,
|
|
8673
|
-
installed: {
|
|
8674
|
-
version: latest,
|
|
8675
|
-
prefix
|
|
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));
|
|
8834
|
+
}
|
|
8835
|
+
return {
|
|
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
|
|
8676
8842
|
},
|
|
8677
|
-
|
|
8678
|
-
next: await skill.nextAt(latest, ctx)
|
|
8843
|
+
handEdits: [...plan.handEdits]
|
|
8679
8844
|
};
|
|
8680
|
-
if (ctx.signal.aborted) {
|
|
8681
|
-
ctx.deliver(update);
|
|
8682
|
-
throw interruptedAfterInstall(plan);
|
|
8683
|
-
}
|
|
8684
|
-
return update;
|
|
8685
8845
|
},
|
|
8686
|
-
render: (
|
|
8687
|
-
renderPlan: (plan) =>
|
|
8846
|
+
render: (data) => renderInstalled(cli, data),
|
|
8847
|
+
renderPlan: (plan) => renderPlan(cli, plan)
|
|
8688
8848
|
});
|
|
8689
8849
|
}
|
|
8690
8850
|
//#endregion
|
|
8691
|
-
//#region ../agent-cli/dist/built-ins/
|
|
8692
|
-
function
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
try {
|
|
8697
|
-
return {
|
|
8698
|
-
state: "ok",
|
|
8699
|
-
message: `a global npm install under ${(await globalInstallOf(bin, npm)).prefix}`
|
|
8700
|
-
};
|
|
8701
|
-
} catch (error) {
|
|
8702
|
-
if (error instanceof CliError && error.code === "NOT_GLOBAL_INSTALL") return {
|
|
8703
|
-
state: "problem",
|
|
8704
|
-
message: error.message,
|
|
8705
|
-
fix: installCommandLine(npm.registry)
|
|
8706
|
-
};
|
|
8707
|
-
throw error;
|
|
8708
|
-
}
|
|
8709
|
-
},
|
|
8710
|
-
release: async (ctx) => {
|
|
8711
|
-
const fix = `${cli.name} update`;
|
|
8712
|
-
return {
|
|
8713
|
-
npm: globalNpm(cli, install, ctx, node),
|
|
8714
|
-
advise: () => ({ fix })
|
|
8715
|
-
};
|
|
8716
|
-
}
|
|
8717
|
-
};
|
|
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;
|
|
8718
8856
|
}
|
|
8719
|
-
/** The
|
|
8720
|
-
function
|
|
8721
|
-
const
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
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
|
|
8726
8878
|
};
|
|
8727
|
-
return adapter;
|
|
8728
8879
|
}
|
|
8729
|
-
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
});
|
|
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
|
+
};
|
|
8736
8886
|
return {
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
})
|
|
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)]]
|
|
8744
8893
|
};
|
|
8745
8894
|
}
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
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);
|
|
8750
8913
|
}
|
|
8751
8914
|
//#endregion
|
|
8752
8915
|
//#region ../agent-cli/dist/built-ins/doctor-rows.js
|
|
@@ -8883,7 +9046,7 @@ function doctorChecks(cli, launch, scope) {
|
|
|
8883
9046
|
...secretRows(cli.secrets ?? [], cli.commands),
|
|
8884
9047
|
installRow(check),
|
|
8885
9048
|
registryRow(cli, check),
|
|
8886
|
-
...skillOf
|
|
9049
|
+
...skillOf(cli, install).rows
|
|
8887
9050
|
],
|
|
8888
9051
|
tools: new Set(tools.map(({ name }) => name))
|
|
8889
9052
|
};
|
|
@@ -8901,8 +9064,7 @@ const reportSchema = object({
|
|
|
8901
9064
|
]),
|
|
8902
9065
|
message: string(),
|
|
8903
9066
|
fix: string().optional(),
|
|
8904
|
-
code: string().optional()
|
|
8905
|
-
skill: packagedSkillDetailsSchema.optional()
|
|
9067
|
+
code: string().optional()
|
|
8906
9068
|
})),
|
|
8907
9069
|
next: array(string())
|
|
8908
9070
|
});
|
|
@@ -9037,15 +9199,15 @@ function doctorCommand(cli, launch) {
|
|
|
9037
9199
|
//#endregion
|
|
9038
9200
|
//#region ../agent-cli/dist/built-ins/commands.js
|
|
9039
9201
|
/**
|
|
9040
|
-
* The Commands the Runtime adds to a multi-Command CLI: `update` only for a Global
|
|
9041
|
-
* `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.
|
|
9042
9204
|
*/
|
|
9043
9205
|
function builtInCommands(cli, launch) {
|
|
9044
9206
|
const install = installOf(cli);
|
|
9045
9207
|
return {
|
|
9046
9208
|
doctor: doctorCommand(cli, launch),
|
|
9047
9209
|
...install.builtIns(launch),
|
|
9048
|
-
...skillOf
|
|
9210
|
+
...skillOf(cli, install).builtIns
|
|
9049
9211
|
};
|
|
9050
9212
|
}
|
|
9051
9213
|
//#endregion
|
|
@@ -9182,6 +9344,7 @@ const RUNTIME_EXIT_CLASSES = {
|
|
|
9182
9344
|
PROJECT_INVALID: "operator",
|
|
9183
9345
|
DOCTOR_PROBLEMS: "operator",
|
|
9184
9346
|
NOT_GLOBAL_INSTALL: "operator",
|
|
9347
|
+
NOT_ON_PATH: "operator",
|
|
9185
9348
|
REGISTRY_UNREACHABLE: "operator",
|
|
9186
9349
|
REGISTRY_AUTH: "operator",
|
|
9187
9350
|
INSTALL_FAILED: "operator",
|
|
@@ -9255,20 +9418,22 @@ function checkTree(commands, path, table) {
|
|
|
9255
9418
|
else checkTree(node, at, table);
|
|
9256
9419
|
}
|
|
9257
9420
|
}
|
|
9258
|
-
function
|
|
9259
|
-
if (skill
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
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`);
|
|
9263
9427
|
}
|
|
9264
9428
|
function checkCli(cli) {
|
|
9265
9429
|
const table = errorTableOf(cli);
|
|
9266
|
-
|
|
9430
|
+
checkDocs(cli);
|
|
9267
9431
|
if (cli.commands === void 0) {
|
|
9268
9432
|
checkCommand(cli.command, commandLabel([], cli.name).join(" "), table);
|
|
9269
|
-
if (
|
|
9433
|
+
if (cli.skill !== void 0) throw new Error(`${cli.name} declares a skill, which only a CLI with several Commands can install`);
|
|
9270
9434
|
return cli;
|
|
9271
9435
|
}
|
|
9436
|
+
checkSkill(cli);
|
|
9272
9437
|
const { commands } = cli;
|
|
9273
9438
|
checkTree(commands, [], table);
|
|
9274
9439
|
const taken = Object.keys(builtInCommands(cli, {
|
|
@@ -9372,30 +9537,6 @@ function startSideTask(sideTask, runWith, { clock, failed }) {
|
|
|
9372
9537
|
};
|
|
9373
9538
|
}
|
|
9374
9539
|
//#endregion
|
|
9375
|
-
//#region ../agent-cli/dist/built-ins/skill/usage-hint.js
|
|
9376
|
-
const SKILL_USAGE_CODES = /* @__PURE__ */ new Set([
|
|
9377
|
-
UNKNOWN_OPTION,
|
|
9378
|
-
UNKNOWN_COMMAND,
|
|
9379
|
-
INVALID_INPUT
|
|
9380
|
-
]);
|
|
9381
|
-
/** A usage error whose hint names each Skill copy that differs from this build, since one may have named the input. */
|
|
9382
|
-
async function withDifferingSkill(error, cli, place, runAs) {
|
|
9383
|
-
if (!(error instanceof UsageError) || !SKILL_USAGE_CODES.has(error.code)) return error;
|
|
9384
|
-
let differing;
|
|
9385
|
-
try {
|
|
9386
|
-
differing = await skillOf$1(cli, installOf(cli)).differing(place);
|
|
9387
|
-
} catch (lookup) {
|
|
9388
|
-
if (errnoOf(lookup) === void 0) throw lookup;
|
|
9389
|
-
return error;
|
|
9390
|
-
}
|
|
9391
|
-
if (differing.length === 0) return error;
|
|
9392
|
-
return new UsageError({
|
|
9393
|
-
code: error.code,
|
|
9394
|
-
message: error.message,
|
|
9395
|
-
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`
|
|
9396
|
-
});
|
|
9397
|
-
}
|
|
9398
|
-
//#endregion
|
|
9399
9540
|
//#region ../agent-cli/dist/command-line/suggest.js
|
|
9400
9541
|
const MAX_DISTANCE = 3;
|
|
9401
9542
|
const MIN_SIMILARITY = .4;
|
|
@@ -10147,7 +10288,11 @@ function helpResult(node, root, cli, { style, json }) {
|
|
|
10147
10288
|
}
|
|
10148
10289
|
function versionResult(cli, packageRoot) {
|
|
10149
10290
|
return {
|
|
10150
|
-
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`,
|
|
10151
10296
|
stderr: "",
|
|
10152
10297
|
exitCode: SUCCESS_EXIT_CODE
|
|
10153
10298
|
};
|
|
@@ -10214,6 +10359,77 @@ function checkOperator({ operator, commandLine }, env, rerun) {
|
|
|
10214
10359
|
});
|
|
10215
10360
|
}
|
|
10216
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
|
|
10217
10433
|
//#region ../agent-cli/dist/context/spill.js
|
|
10218
10434
|
const STDOUT_BUDGET_CHARS = 3e4;
|
|
10219
10435
|
const SPILL_RETENTION_MS = 6048e5;
|
|
@@ -11106,11 +11322,7 @@ async function runCommand(cli, routed, options, write, baseFormat) {
|
|
|
11106
11322
|
});
|
|
11107
11323
|
});
|
|
11108
11324
|
} catch (error) {
|
|
11109
|
-
return failed(
|
|
11110
|
-
cwd,
|
|
11111
|
-
env,
|
|
11112
|
-
packageRoot
|
|
11113
|
-
}, invocation.runAs), noSideTask);
|
|
11325
|
+
return failed(error, noSideTask);
|
|
11114
11326
|
}
|
|
11115
11327
|
const { input, flags } = parsed;
|
|
11116
11328
|
resumed = command.deadline?.resume(input, rerunOf);
|
|
@@ -11178,9 +11390,10 @@ async function runCommand(cli, routed, options, write, baseFormat) {
|
|
|
11178
11390
|
let served = false;
|
|
11179
11391
|
let settled;
|
|
11180
11392
|
try {
|
|
11181
|
-
const outcome = await control.settle(
|
|
11393
|
+
const outcome = await control.settle(noticesOf(cli, {
|
|
11182
11394
|
builtIn,
|
|
11183
|
-
bin: options.bin
|
|
11395
|
+
bin: options.bin,
|
|
11396
|
+
node: options.node
|
|
11184
11397
|
}, ctx).then(() => execute(command, input, ctx, {
|
|
11185
11398
|
dryRun: flags.dryRun,
|
|
11186
11399
|
confirm: flags.confirm,
|
|
@@ -11240,6 +11453,9 @@ function once(build) {
|
|
|
11240
11453
|
throw outcome.error;
|
|
11241
11454
|
};
|
|
11242
11455
|
}
|
|
11456
|
+
async function noticesOf(cli, launch, ctx) {
|
|
11457
|
+
await installOf(cli).notice(launch, ctx);
|
|
11458
|
+
}
|
|
11243
11459
|
//#endregion
|
|
11244
11460
|
//#region ../agent-cli/dist/style.js
|
|
11245
11461
|
function colourEnabled(env, stdoutIsTTY) {
|
|
@@ -11322,16 +11538,10 @@ async function routeAndRun(cli, argv, options, write, stopNotice) {
|
|
|
11322
11538
|
...format,
|
|
11323
11539
|
json: routed.json
|
|
11324
11540
|
}));
|
|
11325
|
-
case "usage error": {
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
};
|
|
11330
|
-
return ranAlone(errorResult(await withDifferingSkill(routed.error, cli, place, runAs), {
|
|
11331
|
-
...misused(routed.node),
|
|
11332
|
-
json: routed.json
|
|
11333
|
-
}));
|
|
11334
|
-
}
|
|
11541
|
+
case "usage error": return ranAlone(errorResult(routed.error, {
|
|
11542
|
+
...misused(routed.node),
|
|
11543
|
+
json: routed.json
|
|
11544
|
+
}));
|
|
11335
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)));
|
|
11336
11546
|
}
|
|
11337
11547
|
}
|
|
@@ -11394,7 +11604,7 @@ async function runProcess(cli) {
|
|
|
11394
11604
|
//#region package.json
|
|
11395
11605
|
var package_default = {
|
|
11396
11606
|
name: "@duyquangnvx/create-agent-cli",
|
|
11397
|
-
version: "0.
|
|
11607
|
+
version: "0.24.0",
|
|
11398
11608
|
description: "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
|
|
11399
11609
|
type: "module",
|
|
11400
11610
|
engines: { "node": ">=22.12.0" },
|
|
@@ -11443,80 +11653,35 @@ function fill(text, values, quoted) {
|
|
|
11443
11653
|
//#region src/install-mode.ts
|
|
11444
11654
|
const PARTIALS_DIR = fileURLToPath(new URL("../partials/", import.meta.url));
|
|
11445
11655
|
const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
11446
|
-
const
|
|
11447
|
-
|
|
11448
|
-
|
|
11449
|
-
function skillHubOf({ git, api, add }) {
|
|
11450
|
-
if (git !== void 0 && api !== void 0) throw new UsageError({
|
|
11451
|
-
code: "SKILL_HOMES_CONFLICT",
|
|
11452
|
-
message: "--skill-hub-git and --skill-hub-api each name the Skill Hub the skill goes to",
|
|
11453
|
-
hint: "pass one of them"
|
|
11454
|
-
});
|
|
11455
|
-
if (api !== void 0 && add === void 0) throw new UsageError({
|
|
11456
|
-
code: "SKILL_ADD_MISSING",
|
|
11457
|
-
message: "--skill-hub-api needs --skill-add, the whole line that installs the skill from the hub",
|
|
11458
|
-
hint: "add --skill-add with the hub's install line for the skill"
|
|
11459
|
-
});
|
|
11460
|
-
if (add !== void 0 && api === void 0) throw new UsageError({
|
|
11461
|
-
code: "SKILL_HUB_API_MISSING",
|
|
11462
|
-
message: "--skill-add is the install line of an API Skill Hub, which --skill-hub-api names",
|
|
11463
|
-
hint: "add --skill-hub-api with the hub's URL"
|
|
11464
|
-
});
|
|
11465
|
-
if (add !== void 0 && (add.trim() === "" || /[\r\n]/.test(add))) throw new UsageError({
|
|
11466
|
-
code: "SKILL_ADD_INVALID",
|
|
11467
|
-
message: `--skill-add ${JSON.stringify(add)} is not one command line`,
|
|
11468
|
-
hint: "pass the hub's install line for the skill, on one line"
|
|
11469
|
-
});
|
|
11470
|
-
if (git !== void 0) return {
|
|
11471
|
-
kind: "git",
|
|
11472
|
-
url: git
|
|
11473
|
-
};
|
|
11474
|
-
return api === void 0 || add === void 0 ? void 0 : {
|
|
11475
|
-
kind: "api",
|
|
11476
|
-
url: api,
|
|
11477
|
-
add
|
|
11478
|
-
};
|
|
11479
|
-
}
|
|
11480
|
-
function installModeOf(flags, dependency, binName) {
|
|
11481
|
-
const skillHub = skillHubOf(flags);
|
|
11482
|
-
if (skillHub !== void 0 && dependency) throw new UsageError({
|
|
11483
|
-
code: "SKILL_HOMES_CONFLICT",
|
|
11484
|
-
message: `--skill-hub-${skillHub.kind} and --dependency each name where the skill comes from: a Dependency install ships it inside the package`,
|
|
11485
|
-
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`
|
|
11486
|
-
});
|
|
11487
|
-
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({
|
|
11488
11659
|
code: "SKILL_NAME_INVALID",
|
|
11489
11660
|
message: `${binName} cannot name a skill, whose names are lowercase letters, digits and single hyphens`,
|
|
11490
|
-
hint:
|
|
11661
|
+
hint: "choose a name such as my-cli"
|
|
11491
11662
|
});
|
|
11492
|
-
return dependency ?
|
|
11493
|
-
mode: "global",
|
|
11494
|
-
skillHub
|
|
11495
|
-
};
|
|
11496
|
-
}
|
|
11497
|
-
function skillLineOf(skillHub, binName) {
|
|
11498
|
-
if (skillHub === void 0) return NO_SKILL_HUB;
|
|
11499
|
-
return skillHub.kind === "git" ? `\`! npx -y skills add ${skillHub.url} --skill ${binName} -g\`` : `\`! ${skillHub.add}\``;
|
|
11663
|
+
return dependency ? "dependency" : "global";
|
|
11500
11664
|
}
|
|
11501
11665
|
async function partialsOf(mode, values) {
|
|
11502
11666
|
const partial = async (name) => fill(await readFile(join(PARTIALS_DIR, mode, `${name}.md`), "utf8"), values, false).trimEnd();
|
|
11503
|
-
const [installLines, skillCheck, installSteps, updateSteps, sandbox] = await Promise.all([
|
|
11667
|
+
const [installLines, skillCheck, installSteps, updateSteps, sandbox, whenMissing] = await Promise.all([
|
|
11504
11668
|
partial("installLines"),
|
|
11505
11669
|
partial("skillCheck"),
|
|
11506
11670
|
partial("installSteps"),
|
|
11507
11671
|
partial("updateSteps"),
|
|
11508
|
-
partial("sandbox")
|
|
11672
|
+
partial("sandbox"),
|
|
11673
|
+
partial("whenMissing")
|
|
11509
11674
|
]);
|
|
11510
11675
|
return {
|
|
11511
11676
|
installLines,
|
|
11512
11677
|
skillCheck,
|
|
11513
11678
|
installSteps,
|
|
11514
11679
|
updateSteps,
|
|
11515
|
-
sandbox
|
|
11680
|
+
sandbox,
|
|
11681
|
+
whenMissing
|
|
11516
11682
|
};
|
|
11517
11683
|
}
|
|
11518
|
-
async function globalSetup(
|
|
11519
|
-
const skillLine = skillLineOf(skillHub, binName);
|
|
11684
|
+
async function globalSetup(packageName, binName) {
|
|
11520
11685
|
const runAs = runAsOf({ name: binName });
|
|
11521
11686
|
return {
|
|
11522
11687
|
values: {
|
|
@@ -11526,24 +11691,16 @@ async function globalSetup(skillHub, packageName, binName) {
|
|
|
11526
11691
|
packageName,
|
|
11527
11692
|
binName,
|
|
11528
11693
|
runAs,
|
|
11529
|
-
|
|
11694
|
+
setupLine: setupLineOf({
|
|
11695
|
+
mode: "global",
|
|
11696
|
+
package: packageName,
|
|
11697
|
+
registry: TEMPLATE_REGISTRY
|
|
11698
|
+
})
|
|
11530
11699
|
})
|
|
11531
11700
|
},
|
|
11532
|
-
edits:
|
|
11533
|
-
file: "src/cli.ts",
|
|
11534
|
-
find: COMMANDS_ANCHOR,
|
|
11535
|
-
replace: ` skill: ${skillOf(skillHub)},\n${COMMANDS_ANCHOR}`
|
|
11536
|
-
}, {
|
|
11537
|
-
file: "scripts/release.ts",
|
|
11538
|
-
find: " smoke: {",
|
|
11539
|
-
replace: ` skillHub: { ${skillHub.kind}: ${JSON.stringify(skillHub.url)} },\n smoke: {`
|
|
11540
|
-
}],
|
|
11541
|
-
files: []
|
|
11701
|
+
edits: []
|
|
11542
11702
|
};
|
|
11543
11703
|
}
|
|
11544
|
-
function skillOf(skillHub) {
|
|
11545
|
-
return skillHub.kind === "git" ? `{ source: ${JSON.stringify(skillHub.url)} }` : `{ add: ${JSON.stringify(skillHub.add)} }`;
|
|
11546
|
-
}
|
|
11547
11704
|
async function dependencySetup(packageName, binName) {
|
|
11548
11705
|
const runAs = runAsOf({
|
|
11549
11706
|
name: binName,
|
|
@@ -11564,18 +11721,13 @@ async function dependencySetup(packageName, binName) {
|
|
|
11564
11721
|
},
|
|
11565
11722
|
edits: [{
|
|
11566
11723
|
file: "src/cli.ts",
|
|
11567
|
-
find:
|
|
11724
|
+
find: ` install: "global",\n`,
|
|
11568
11725
|
replace: ` install: "dependency",\n`
|
|
11569
|
-
}
|
|
11570
|
-
file: "src/cli.ts",
|
|
11571
|
-
find: COMMANDS_ANCHOR,
|
|
11572
|
-
replace: ` skill: { packaged: "skills/${binName}" },\n${COMMANDS_ANCHOR}`
|
|
11573
|
-
}],
|
|
11574
|
-
files: ["skills"]
|
|
11726
|
+
}]
|
|
11575
11727
|
};
|
|
11576
11728
|
}
|
|
11577
11729
|
async function installSetupOf(install, packageName, binName) {
|
|
11578
|
-
return install
|
|
11730
|
+
return install === "global" ? globalSetup(packageName, binName) : dependencySetup(packageName, binName);
|
|
11579
11731
|
}
|
|
11580
11732
|
//#endregion
|
|
11581
11733
|
//#region src/steps.ts
|
|
@@ -11728,9 +11880,6 @@ async function editManifest(root, edit) {
|
|
|
11728
11880
|
async function setRepository(root, repository) {
|
|
11729
11881
|
await editManifest(root, (entries) => entries.flatMap((entry) => entry[0] === "description" ? [entry, ["repository", repository]] : [entry]));
|
|
11730
11882
|
}
|
|
11731
|
-
async function addFiles(root, files) {
|
|
11732
|
-
await editManifest(root, (entries) => entries.map(([key, value]) => key === "files" && Array.isArray(value) ? [key, [...value, ...files]] : [key, value]));
|
|
11733
|
-
}
|
|
11734
11883
|
async function ensureFree(path) {
|
|
11735
11884
|
if (!await isFree(path)) throw new UsageError({
|
|
11736
11885
|
code: "TARGET_NOT_EMPTY",
|
|
@@ -11756,18 +11905,11 @@ await runProcess(defineCli({
|
|
|
11756
11905
|
install: boolean().default(true).describe("run pnpm install"),
|
|
11757
11906
|
git: boolean().default(true).describe("create a git repository with an initial commit"),
|
|
11758
11907
|
repository: url({ protocol: /^https$/ }).optional().describe("the https URL of the CLI's git repository"),
|
|
11759
|
-
|
|
11760
|
-
skillHubApi: url({ protocol: /^https$/ }).optional().describe("the https URL of an API Skill Hub each release publishes the CLI's skill to"),
|
|
11761
|
-
skillAdd: string().optional().describe("the whole line that installs the CLI's skill from the API Skill Hub"),
|
|
11762
|
-
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")
|
|
11763
11909
|
}),
|
|
11764
|
-
plan: async ({ name, description, install, git, repository,
|
|
11910
|
+
plan: async ({ name, description, install, git, repository, dependency }, ctx) => {
|
|
11765
11911
|
const binName = PACKAGE_NAME.exec(name)?.[1] ?? name;
|
|
11766
|
-
const mode = installModeOf(
|
|
11767
|
-
git: skillHubGit,
|
|
11768
|
-
api: skillHubApi,
|
|
11769
|
-
add: skillAdd
|
|
11770
|
-
}, dependency, binName);
|
|
11912
|
+
const mode = installModeOf(dependency, binName);
|
|
11771
11913
|
const path = resolve(ctx.cwd, binName);
|
|
11772
11914
|
await ensureFree(path);
|
|
11773
11915
|
const setup = await installSetupOf(mode, name, binName);
|
|
@@ -11790,8 +11932,7 @@ await runProcess(defineCli({
|
|
|
11790
11932
|
}),
|
|
11791
11933
|
install,
|
|
11792
11934
|
repository,
|
|
11793
|
-
edits: setup.edits
|
|
11794
|
-
manifestFiles: setup.files
|
|
11935
|
+
edits: setup.edits
|
|
11795
11936
|
};
|
|
11796
11937
|
},
|
|
11797
11938
|
apply: async (plan, ctx) => {
|
|
@@ -11799,7 +11940,6 @@ await runProcess(defineCli({
|
|
|
11799
11940
|
await writeTemplate(plan.path, plan.files, plan.values);
|
|
11800
11941
|
if (plan.repository !== void 0) await setRepository(plan.path, plan.repository);
|
|
11801
11942
|
for (const edit of plan.edits) await applyEdit(plan.path, edit);
|
|
11802
|
-
if (plan.manifestFiles.length > 0) await addFiles(plan.path, plan.manifestFiles);
|
|
11803
11943
|
await runSteps(plan.steps, plan.path, ctx);
|
|
11804
11944
|
return {
|
|
11805
11945
|
path: plan.path,
|