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