@duyquangnvx/create-agent-cli 0.13.1 → 0.14.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 +17 -0
- package/README.md +1 -1
- package/dist/bin.js +1188 -721
- package/package.json +3 -2
- package/partials/dependency/installLines.md +7 -0
- package/partials/dependency/installSteps.md +12 -0
- package/partials/dependency/sandbox.md +3 -0
- package/partials/dependency/skillCheck.md +1 -0
- package/partials/dependency/updateSteps.md +4 -0
- package/partials/global/installLines.md +5 -0
- package/partials/global/installSteps.md +10 -0
- package/partials/global/sandbox.md +3 -0
- package/partials/global/skillCheck.md +1 -0
- package/partials/global/updateSteps.md +4 -0
- package/template/README.md +1 -5
- package/template/skills/{{binName}}/SKILL.md +6 -6
- package/template/skills/{{binName}}/references/setup.md +6 -20
- package/template/src/cli.ts +19 -7
- package/template/test/cli.test.ts +16 -5
package/dist/bin.js
CHANGED
|
@@ -5803,6 +5803,15 @@ function errnoOf(error) {
|
|
|
5803
5803
|
function isMissingFile(error) {
|
|
5804
5804
|
return errnoOf(error) === "ENOENT";
|
|
5805
5805
|
}
|
|
5806
|
+
/** What `pending` resolves with, or undefined when it fails because a file is missing. */
|
|
5807
|
+
async function orMissing(pending) {
|
|
5808
|
+
try {
|
|
5809
|
+
return await pending;
|
|
5810
|
+
} catch (error) {
|
|
5811
|
+
if (isMissingFile(error)) return void 0;
|
|
5812
|
+
throw error;
|
|
5813
|
+
}
|
|
5814
|
+
}
|
|
5806
5815
|
function isNothingThere(error) {
|
|
5807
5816
|
return isMissingFile(error) || errnoOf(error) === "ENOTDIR";
|
|
5808
5817
|
}
|
|
@@ -6142,8 +6151,8 @@ const SHELL_SAFE = /^[\w@%+=:,./-]+$/;
|
|
|
6142
6151
|
function quoted$1(token) {
|
|
6143
6152
|
return SHELL_SAFE.test(token) ? token : `'${token.replaceAll("'", `'\\''`)}'`;
|
|
6144
6153
|
}
|
|
6145
|
-
function pasteable(
|
|
6146
|
-
const line = [
|
|
6154
|
+
function pasteable(runAs, tokens, stdinArg) {
|
|
6155
|
+
const line = [runAs, ...tokens.map(quoted$1)].join(" ");
|
|
6147
6156
|
return stdinArg === void 0 ? line : `${line} < <${stdinArg} file>`;
|
|
6148
6157
|
}
|
|
6149
6158
|
function split(argv) {
|
|
@@ -6173,10 +6182,10 @@ function without(flag, takes, tokens) {
|
|
|
6173
6182
|
* The caller's command line as typed, ready to paste, with each flag in `remove` dropped along with
|
|
6174
6183
|
* its value, the words in `add` placed before any `--`, and the stdin arg's file redirected in.
|
|
6175
6184
|
*/
|
|
6176
|
-
function commandLineOf({
|
|
6185
|
+
function commandLineOf({ runAs, argv, flags, stdinArg }, { add = [], remove = [] } = {}) {
|
|
6177
6186
|
const { options, operands } = split(argv);
|
|
6178
6187
|
const takes = new Map(flags.map((known) => [known.flag, known.takes]));
|
|
6179
|
-
return pasteable(
|
|
6188
|
+
return pasteable(runAs, [
|
|
6180
6189
|
...remove.reduce((tokens, flag) => without(flag, takes.get(flag) ?? "nothing", tokens), options),
|
|
6181
6190
|
...add,
|
|
6182
6191
|
...operands
|
|
@@ -6189,9 +6198,9 @@ function renamed(token, renames) {
|
|
|
6189
6198
|
return value.length === 0 ? name : `${name}=${value.join("=")}`;
|
|
6190
6199
|
}
|
|
6191
6200
|
/** The caller's command line, ready to paste, with each flag in `renames` typed as its new name. */
|
|
6192
|
-
function commandLineRenaming({
|
|
6201
|
+
function commandLineRenaming({ runAs, argv, stdinArg }, renames) {
|
|
6193
6202
|
const { options, operands } = split(argv);
|
|
6194
|
-
return pasteable(
|
|
6203
|
+
return pasteable(runAs, [...options.map((token) => renamed(token, renames)), ...operands], stdinArg);
|
|
6195
6204
|
}
|
|
6196
6205
|
//#endregion
|
|
6197
6206
|
//#region ../agent-cli/dist/usage.js
|
|
@@ -6478,6 +6487,15 @@ function isCommand(node) {
|
|
|
6478
6487
|
return typeof node.effect === "string";
|
|
6479
6488
|
}
|
|
6480
6489
|
//#endregion
|
|
6490
|
+
//#region ../agent-cli/dist/run-as.js
|
|
6491
|
+
/**
|
|
6492
|
+
* The Run-as: how every command line the Runtime gives its caller starts. A Dependency install runs
|
|
6493
|
+
* the copy the project pins, so it is `npx -q --no-install <bin>`; otherwise the bare name (ADR 0011).
|
|
6494
|
+
*/
|
|
6495
|
+
function runAsOf(cli) {
|
|
6496
|
+
return cli.install?.mode === "dependency" ? `npx -q --no-install ${cli.name}` : cli.name;
|
|
6497
|
+
}
|
|
6498
|
+
//#endregion
|
|
6481
6499
|
//#region ../agent-cli/dist/project.js
|
|
6482
6500
|
const PROJECT_INVALID = "PROJECT_INVALID";
|
|
6483
6501
|
function invalid(path, problem, callerLine, options) {
|
|
@@ -6717,11 +6735,7 @@ async function feed(stdin, input, signal) {
|
|
|
6717
6735
|
*/
|
|
6718
6736
|
async function spawnChild(command, args, { cwd, env, signal, onLine, stdin, detached = false }) {
|
|
6719
6737
|
signal.throwIfAborted();
|
|
6720
|
-
const launch =
|
|
6721
|
-
command,
|
|
6722
|
-
args,
|
|
6723
|
-
shell: false
|
|
6724
|
-
};
|
|
6738
|
+
const launch = await launchOf(command, args, env);
|
|
6725
6739
|
signal.throwIfAborted();
|
|
6726
6740
|
return new Promise((resolve, reject) => {
|
|
6727
6741
|
const child = spawn(launch.command, launch.args, {
|
|
@@ -6786,6 +6800,38 @@ async function spawnChild(command, args, { cwd, env, signal, onLine, stdin, deta
|
|
|
6786
6800
|
});
|
|
6787
6801
|
});
|
|
6788
6802
|
}
|
|
6803
|
+
async function launchOf(command, args, env) {
|
|
6804
|
+
return process.platform === "win32" ? windowsLaunch(command, args, env) : {
|
|
6805
|
+
command,
|
|
6806
|
+
args,
|
|
6807
|
+
shell: false
|
|
6808
|
+
};
|
|
6809
|
+
}
|
|
6810
|
+
/**
|
|
6811
|
+
* Starts a child in its own process group (its own console on Windows) with no stdio, and settles
|
|
6812
|
+
* once it has started; the parent neither waits for it nor keeps it from exiting.
|
|
6813
|
+
*/
|
|
6814
|
+
const realSpawnDetached = async (command, args, { cwd, env }) => {
|
|
6815
|
+
const launch = await launchOf(command, args, env);
|
|
6816
|
+
return new Promise((resolve, reject) => {
|
|
6817
|
+
const child = spawn(launch.command, launch.args, {
|
|
6818
|
+
cwd,
|
|
6819
|
+
env,
|
|
6820
|
+
shell: launch.shell,
|
|
6821
|
+
windowsHide: true,
|
|
6822
|
+
stdio: "ignore",
|
|
6823
|
+
detached: true
|
|
6824
|
+
});
|
|
6825
|
+
child.unref();
|
|
6826
|
+
child.once("error", reject);
|
|
6827
|
+
child.once("spawn", () => {
|
|
6828
|
+
child.off("error", reject);
|
|
6829
|
+
const { pid } = child;
|
|
6830
|
+
if (pid === void 0) reject(/* @__PURE__ */ new Error(`${command} started without a pid`));
|
|
6831
|
+
else resolve({ pid });
|
|
6832
|
+
});
|
|
6833
|
+
});
|
|
6834
|
+
};
|
|
6789
6835
|
const realSpawn = (command, args, options) => spawnChild(command, args, options);
|
|
6790
6836
|
function failedError(command, { exitCode, stderr }) {
|
|
6791
6837
|
const tail = lastLines(stderr, STDERR_TAIL_LINES$1);
|
|
@@ -6855,6 +6901,20 @@ function contextSpawn(run, defaults, scope) {
|
|
|
6855
6901
|
return result;
|
|
6856
6902
|
};
|
|
6857
6903
|
}
|
|
6904
|
+
/** `ctx.spawnDetached`: `run` bound to the run's cwd and env, its start failures classified by errno. */
|
|
6905
|
+
function contextSpawnDetached(run, defaults, scope) {
|
|
6906
|
+
return async (command, args = [], options = {}) => {
|
|
6907
|
+
defaults.signal.throwIfAborted();
|
|
6908
|
+
try {
|
|
6909
|
+
return await run(command, args, {
|
|
6910
|
+
cwd: options.cwd ?? defaults.cwd,
|
|
6911
|
+
env: options.env ?? defaults.env
|
|
6912
|
+
});
|
|
6913
|
+
} catch (error) {
|
|
6914
|
+
throw startError(command, error, scope);
|
|
6915
|
+
}
|
|
6916
|
+
};
|
|
6917
|
+
}
|
|
6858
6918
|
//#endregion
|
|
6859
6919
|
//#region ../agent-cli/dist/built-ins/semver.js
|
|
6860
6920
|
const VERSION = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
@@ -7177,17 +7237,19 @@ const LINES = {
|
|
|
7177
7237
|
pnpm: {
|
|
7178
7238
|
upgrade: (packages) => `pnpm up --latest ${packages.join(" ")}`,
|
|
7179
7239
|
add: (packages) => `pnpm add -D ${packages.join(" ")}`,
|
|
7180
|
-
|
|
7181
|
-
exec: (cli) => `pnpm exec ${cli}`
|
|
7240
|
+
install: "pnpm install"
|
|
7182
7241
|
},
|
|
7183
7242
|
npm: {
|
|
7184
7243
|
upgrade: (packages) => `npm install ${packages.map((name) => `${name}@latest`).join(" ")}`,
|
|
7185
7244
|
add: (packages) => `npm install -D ${packages.join(" ")}`,
|
|
7186
|
-
|
|
7187
|
-
exec: (cli) => `npx ${cli}`
|
|
7245
|
+
install: "npm install"
|
|
7188
7246
|
}
|
|
7189
7247
|
};
|
|
7190
|
-
const manifestSchema$1 = object({
|
|
7248
|
+
const manifestSchema$1 = object({
|
|
7249
|
+
packageManager: string().optional(),
|
|
7250
|
+
dependencies: record(string(), unknown()).optional(),
|
|
7251
|
+
devDependencies: record(string(), unknown()).optional()
|
|
7252
|
+
});
|
|
7191
7253
|
const packageSchema = object({ version: string() });
|
|
7192
7254
|
/** `dir` and every folder above it, nearest first. */
|
|
7193
7255
|
function ancestorsOf(dir) {
|
|
@@ -7203,7 +7265,7 @@ async function exists(path) {
|
|
|
7203
7265
|
throw error;
|
|
7204
7266
|
}
|
|
7205
7267
|
}
|
|
7206
|
-
async function
|
|
7268
|
+
async function manifestIn(dir) {
|
|
7207
7269
|
let text;
|
|
7208
7270
|
try {
|
|
7209
7271
|
text = await readFile(join(dir, "package.json"), "utf8");
|
|
@@ -7217,15 +7279,30 @@ async function declaredManager(dir) {
|
|
|
7217
7279
|
} catch {
|
|
7218
7280
|
return;
|
|
7219
7281
|
}
|
|
7220
|
-
|
|
7282
|
+
return manifestSchema$1.safeParse(manifest).data;
|
|
7283
|
+
}
|
|
7284
|
+
async function declaredManager(dir) {
|
|
7285
|
+
const name = (await manifestIn(dir))?.packageManager?.split("@")[0];
|
|
7221
7286
|
return MANAGERS.find((manager) => manager === name);
|
|
7222
7287
|
}
|
|
7288
|
+
async function manifestListing(name, cwd, root) {
|
|
7289
|
+
const dirs = ancestorsOf(cwd);
|
|
7290
|
+
const top = root === void 0 ? dirs.length : dirs.indexOf(root.dir) + 1;
|
|
7291
|
+
for (const dir of dirs.slice(0, top)) {
|
|
7292
|
+
const manifest = await manifestIn(dir);
|
|
7293
|
+
if (manifest?.dependencies?.[name] !== void 0 || manifest?.devDependencies?.[name] !== void 0) return join(dir, "package.json");
|
|
7294
|
+
}
|
|
7295
|
+
}
|
|
7223
7296
|
async function lockfileRootOf(cwd) {
|
|
7224
7297
|
for (const dir of ancestorsOf(cwd)) for (const [file, kind] of LOCKFILES) if (await exists(join(dir, file))) return {
|
|
7225
7298
|
dir,
|
|
7226
7299
|
manager: await declaredManager(dir) ?? kind
|
|
7227
7300
|
};
|
|
7228
7301
|
}
|
|
7302
|
+
/** The package manager's project root: the folder of the nearest lockfile, or `cwd` without one. */
|
|
7303
|
+
async function projectRootOf(cwd) {
|
|
7304
|
+
return (await lockfileRootOf(cwd))?.dir ?? cwd;
|
|
7305
|
+
}
|
|
7229
7306
|
async function versionIn(manifest) {
|
|
7230
7307
|
try {
|
|
7231
7308
|
return packageSchema.parse(JSON.parse(await readFile(manifest, "utf8"))).version;
|
|
@@ -7234,7 +7311,7 @@ async function versionIn(manifest) {
|
|
|
7234
7311
|
throw error;
|
|
7235
7312
|
}
|
|
7236
7313
|
}
|
|
7237
|
-
async function standingOf(install, bin, cwd) {
|
|
7314
|
+
async function standingOf(install, bin, cwd, root) {
|
|
7238
7315
|
if (bin === void 0) return { kind: "source" };
|
|
7239
7316
|
for (const projectDir of ancestorsOf(cwd)) {
|
|
7240
7317
|
const folder = join(projectDir, "node_modules", install.package);
|
|
@@ -7268,14 +7345,18 @@ async function standingOf(install, bin, cwd) {
|
|
|
7268
7345
|
runningFrom
|
|
7269
7346
|
};
|
|
7270
7347
|
}
|
|
7271
|
-
|
|
7348
|
+
const manifest = await manifestListing(install.package, cwd, root);
|
|
7349
|
+
return manifest === void 0 ? { kind: "missing" } : {
|
|
7350
|
+
kind: "listed",
|
|
7351
|
+
manifest
|
|
7352
|
+
};
|
|
7272
7353
|
}
|
|
7273
7354
|
async function surveyDependency(install, bin, cwd) {
|
|
7274
|
-
const
|
|
7355
|
+
const root = await lockfileRootOf(cwd);
|
|
7275
7356
|
return {
|
|
7276
7357
|
cwd,
|
|
7277
7358
|
root,
|
|
7278
|
-
standing
|
|
7359
|
+
standing: await standingOf(install, bin, cwd, root)
|
|
7279
7360
|
};
|
|
7280
7361
|
}
|
|
7281
7362
|
function packagesOf(install) {
|
|
@@ -7290,6 +7371,10 @@ function skippedFor(cli, install, standing) {
|
|
|
7290
7371
|
message: standing.kind === "source" ? `not checked: ${cli.name} runs from source, not from an install` : `not checked: ${install.package} links to ${standing.copy.realDir}, so the workspace decides its version`
|
|
7291
7372
|
};
|
|
7292
7373
|
}
|
|
7374
|
+
function searchedFrom(cwd, root) {
|
|
7375
|
+
if (root === void 0) return `at or above ${cwd}`;
|
|
7376
|
+
return root.dir === cwd ? `at ${cwd}` : `from ${cwd} up to ${root.dir}`;
|
|
7377
|
+
}
|
|
7293
7378
|
function problem(message, fix) {
|
|
7294
7379
|
return fix === void 0 ? {
|
|
7295
7380
|
state: "problem",
|
|
@@ -7306,11 +7391,12 @@ function dependencyInstallFinding(cli, install, { cwd, root, standing }) {
|
|
|
7306
7391
|
switch (standing.kind) {
|
|
7307
7392
|
case "source":
|
|
7308
7393
|
case "linked": return skippedFor(cli, install, standing);
|
|
7309
|
-
case "missing": return problem(`no package
|
|
7310
|
-
case "
|
|
7394
|
+
case "missing": return problem(`no package ${searchedFrom(cwd, root)} depends on ${install.package}`, lines?.add(packagesOf(install)));
|
|
7395
|
+
case "listed": return problem(`${standing.manifest} lists ${install.package}, but no copy of it is installed at or above ${cwd}`, lines?.install);
|
|
7396
|
+
case "unreadable": return problem(`${standing.manifest} is not a package.json with a version: ${standing.reason}`, lines?.install);
|
|
7311
7397
|
case "other": {
|
|
7312
7398
|
const { copy, runningFrom } = standing;
|
|
7313
|
-
return problem(`${cli.name} runs from ${runningFrom}, not from this project's ${install.package} ${copy.version} at ${copy.realDir}`,
|
|
7399
|
+
return problem(`${cli.name} runs from ${runningFrom}, not from this project's ${install.package} ${copy.version} at ${copy.realDir}`, `${runAsOf(cli)} doctor`);
|
|
7314
7400
|
}
|
|
7315
7401
|
case "installed": {
|
|
7316
7402
|
const { copy } = standing;
|
|
@@ -7409,9 +7495,93 @@ function fixFor(cliName, copies, source) {
|
|
|
7409
7495
|
}
|
|
7410
7496
|
async function skillReport(cli, version, ctx) {
|
|
7411
7497
|
const copies = await skillCopies(cli.name, version, ctx);
|
|
7498
|
+
const source = cli.skill !== void 0 && "source" in cli.skill ? cli.skill.source : void 0;
|
|
7412
7499
|
return {
|
|
7413
7500
|
copies,
|
|
7414
|
-
fix: fixFor(cli.name, copies,
|
|
7501
|
+
fix: fixFor(cli.name, copies, source)
|
|
7502
|
+
};
|
|
7503
|
+
}
|
|
7504
|
+
//#endregion
|
|
7505
|
+
//#region ../agent-cli/dist/built-ins/packaged-skill.js
|
|
7506
|
+
/** The folder, relative to the package root, of a CLI's Packaged skill, if it declares one. */
|
|
7507
|
+
function packagedFolderOf(cli) {
|
|
7508
|
+
return cli.skill !== void 0 && "packaged" in cli.skill ? cli.skill.packaged : void 0;
|
|
7509
|
+
}
|
|
7510
|
+
/** The project root and the Skill copy under it, as `skill install` writes it. */
|
|
7511
|
+
async function skillCopyOf(cli, cwd) {
|
|
7512
|
+
const root = await projectRootOf(cwd);
|
|
7513
|
+
return {
|
|
7514
|
+
root,
|
|
7515
|
+
copy: join(root, ".claude", "skills", cli.name)
|
|
7516
|
+
};
|
|
7517
|
+
}
|
|
7518
|
+
/** Every file under `dir`, relative to it and sorted; none when `dir` is missing. */
|
|
7519
|
+
async function filesIn(dir) {
|
|
7520
|
+
return (await orMissing(readdir(dir, {
|
|
7521
|
+
recursive: true,
|
|
7522
|
+
withFileTypes: true
|
|
7523
|
+
})) ?? []).filter((entry) => entry.isFile()).map((entry) => relative(dir, join(entry.parentPath, entry.name))).sort();
|
|
7524
|
+
}
|
|
7525
|
+
/** The versions of the package's skill in `source` and of its copy in `copy`. */
|
|
7526
|
+
async function skillVersionsOf(source, copy) {
|
|
7527
|
+
const [shipped, installed] = await Promise.all([readFile(join(source, "SKILL.md"), "utf8"), orMissing(readFile(join(copy, "SKILL.md"), "utf8"))]);
|
|
7528
|
+
const packageVersion = skillVersionIn(shipped) ?? null;
|
|
7529
|
+
const copyVersion = installed === void 0 ? void 0 : skillVersionIn(installed) ?? null;
|
|
7530
|
+
return {
|
|
7531
|
+
packageVersion,
|
|
7532
|
+
copyVersion,
|
|
7533
|
+
same: copyVersion === packageVersion
|
|
7534
|
+
};
|
|
7535
|
+
}
|
|
7536
|
+
async function personalCopyOf(name, copy, ctx) {
|
|
7537
|
+
const home = join(homeOf(ctx.env), ".claude", "skills", name);
|
|
7538
|
+
const real = await orMissing(realpath(home));
|
|
7539
|
+
return real === void 0 || real === await orMissing(realpath(copy)) ? void 0 : home;
|
|
7540
|
+
}
|
|
7541
|
+
/** What the `skill` row of a Packaged skill carries under `--json` beside its message. */
|
|
7542
|
+
const packagedSkillDetailsSchema = object({
|
|
7543
|
+
copy: string(),
|
|
7544
|
+
copyVersion: string().nullable(),
|
|
7545
|
+
packageVersion: string().nullable(),
|
|
7546
|
+
/** A personal copy, which Claude Code prefers over the project's. */
|
|
7547
|
+
home: string().optional()
|
|
7548
|
+
});
|
|
7549
|
+
function versionLabel(version) {
|
|
7550
|
+
return version ?? "(no metadata.version)";
|
|
7551
|
+
}
|
|
7552
|
+
function compared(name, { copy, copyVersion, packageVersion }, same) {
|
|
7553
|
+
const stated = `${copy} ${versionLabel(copyVersion)}`;
|
|
7554
|
+
return same ? `${stated}, the version ${name} ships` : `${stated} differs from the ${versionLabel(packageVersion)} that ${name} ships`;
|
|
7555
|
+
}
|
|
7556
|
+
/** The `skill` row of a Packaged skill: the project root's copy against the package's, and any under home. */
|
|
7557
|
+
async function packagedSkillFinding(cli, folder, ctx) {
|
|
7558
|
+
const { copy } = await skillCopyOf(cli, ctx.cwd);
|
|
7559
|
+
const versions = await skillVersionsOf(join(ctx.packageRoot, folder), copy);
|
|
7560
|
+
const { packageVersion } = versions;
|
|
7561
|
+
const copyVersion = versions.copyVersion ?? null;
|
|
7562
|
+
const home = await personalCopyOf(cli.name, copy, ctx);
|
|
7563
|
+
const skill = {
|
|
7564
|
+
copy,
|
|
7565
|
+
copyVersion,
|
|
7566
|
+
packageVersion,
|
|
7567
|
+
...home === void 0 ? {} : { home }
|
|
7568
|
+
};
|
|
7569
|
+
const head = versions.copyVersion === void 0 ? `no copy of the ${cli.name} skill at ${copy}` : compared(cli.name, skill, versions.same);
|
|
7570
|
+
const message = home === void 0 ? head : `${head}; remove ${home}: Claude Code prefers a personal skill over the project's`;
|
|
7571
|
+
if (!versions.same) return {
|
|
7572
|
+
state: "problem",
|
|
7573
|
+
message,
|
|
7574
|
+
fix: `${runAsOf(cli)} skill install`,
|
|
7575
|
+
skill
|
|
7576
|
+
};
|
|
7577
|
+
return home === void 0 ? {
|
|
7578
|
+
state: "ok",
|
|
7579
|
+
message,
|
|
7580
|
+
skill
|
|
7581
|
+
} : {
|
|
7582
|
+
state: "problem",
|
|
7583
|
+
message,
|
|
7584
|
+
skill
|
|
7415
7585
|
};
|
|
7416
7586
|
}
|
|
7417
7587
|
//#endregion
|
|
@@ -7581,9 +7751,12 @@ function describeCopy({ path, version, state }) {
|
|
|
7581
7751
|
return `${path}${version === void 0 ? "" : ` ${version}`} (${SKILL_STATE_NOTES[state]})`;
|
|
7582
7752
|
}
|
|
7583
7753
|
function skillRow(cli) {
|
|
7754
|
+
const packaged = packagedFolderOf(cli);
|
|
7584
7755
|
return {
|
|
7585
7756
|
name: "skill",
|
|
7757
|
+
advisory: true,
|
|
7586
7758
|
run: async (ctx) => {
|
|
7759
|
+
if (packaged !== void 0) return packagedSkillFinding(cli, packaged, ctx);
|
|
7587
7760
|
const { copies, fix } = await skillReport(cli, cli.version, ctx);
|
|
7588
7761
|
const message = copies.length === 0 ? `no copy of the ${cli.name} skill is installed` : copies.map(describeCopy).join("; ");
|
|
7589
7762
|
return fix === void 0 ? ok(message) : {
|
|
@@ -7624,7 +7797,8 @@ const reportSchema = object({
|
|
|
7624
7797
|
]),
|
|
7625
7798
|
message: string(),
|
|
7626
7799
|
fix: string().optional(),
|
|
7627
|
-
code: string().optional()
|
|
7800
|
+
code: string().optional(),
|
|
7801
|
+
skill: packagedSkillDetailsSchema.optional()
|
|
7628
7802
|
})),
|
|
7629
7803
|
next: array(string())
|
|
7630
7804
|
});
|
|
@@ -7694,10 +7868,10 @@ function doctorCommand(cli, launch) {
|
|
|
7694
7868
|
name: "doctor",
|
|
7695
7869
|
description: "check the setup and say what fixes each problem",
|
|
7696
7870
|
examples: [{
|
|
7697
|
-
command: `${cli
|
|
7871
|
+
command: `${runAsOf(cli)} doctor`,
|
|
7698
7872
|
note: "check the setup and list the commands that fix it"
|
|
7699
7873
|
}, {
|
|
7700
|
-
command: `${cli
|
|
7874
|
+
command: `${runAsOf(cli)} doctor --json`,
|
|
7701
7875
|
note: "get the rows and fixes as data"
|
|
7702
7876
|
}],
|
|
7703
7877
|
effect: "read",
|
|
@@ -7725,687 +7899,841 @@ function doctorCommand(cli, launch) {
|
|
|
7725
7899
|
});
|
|
7726
7900
|
}
|
|
7727
7901
|
//#endregion
|
|
7728
|
-
//#region ../agent-cli/dist/
|
|
7729
|
-
const
|
|
7730
|
-
const
|
|
7731
|
-
|
|
7732
|
-
const
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7902
|
+
//#region ../agent-cli/dist/agent.js
|
|
7903
|
+
const AGENT_NAME = /^[a-zA-Z0-9_-]+$/u;
|
|
7904
|
+
const isSet = (name) => (env) => (env[name] ?? "") !== "";
|
|
7905
|
+
const equals = (name, value) => (env) => env[name] === value;
|
|
7906
|
+
const MARKERS = [
|
|
7907
|
+
(env) => AGENT_NAME.test(env.AI_AGENT ?? ""),
|
|
7908
|
+
equals("AGENT", "amp"),
|
|
7909
|
+
isSet("CODEX_SANDBOX"),
|
|
7910
|
+
isSet("CODEX_CI"),
|
|
7911
|
+
isSet("CODEX_THREAD_ID"),
|
|
7912
|
+
isSet("GEMINI_CLI"),
|
|
7913
|
+
isSet("COPILOT_CLI"),
|
|
7914
|
+
isSet("OPENCODE"),
|
|
7915
|
+
isSet("ANTIGRAVITY_AGENT"),
|
|
7916
|
+
isSet("AUGMENT_AGENT"),
|
|
7917
|
+
isSet("REPL_ID"),
|
|
7918
|
+
isSet("CLAUDE_CODE_IS_COWORK"),
|
|
7919
|
+
isSet("CLAUDECODE"),
|
|
7920
|
+
isSet("CLAUDE_CODE"),
|
|
7921
|
+
isSet("CURSOR_TRACE_ID"),
|
|
7922
|
+
isSet("CURSOR_AGENT"),
|
|
7923
|
+
equals("CURSOR_EXTENSION_HOST_ROLE", "agent-exec"),
|
|
7924
|
+
equals("TERM_PROGRAM", "kiro"),
|
|
7925
|
+
(env) => ["/.pi/agent", "\\.pi\\agent"].some((dir) => (env.PATH ?? "").includes(dir)),
|
|
7926
|
+
isSet("GOOSE_PROVIDER")
|
|
7927
|
+
];
|
|
7928
|
+
/**
|
|
7929
|
+
* Whether a Coding agent, not a person, is running the Agent CLI. Some Coding agents run commands
|
|
7930
|
+
* in a PTY, so a TTY alone doesn't mean a person is watching.
|
|
7931
|
+
*/
|
|
7932
|
+
function isAgent(env) {
|
|
7933
|
+
return MARKERS.some((marker) => marker(env));
|
|
7740
7934
|
}
|
|
7741
|
-
|
|
7742
|
-
|
|
7935
|
+
//#endregion
|
|
7936
|
+
//#region ../agent-cli/dist/errno.js
|
|
7937
|
+
const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
7938
|
+
const DISK_FULL = "DISK_FULL";
|
|
7939
|
+
const NOT_FOUND = "NOT_FOUND";
|
|
7940
|
+
const ALREADY_EXISTS = "ALREADY_EXISTS";
|
|
7941
|
+
const NOT_A_FILE = "NOT_A_FILE";
|
|
7942
|
+
function permissionHint({ path, access, callerLine, env, platform }) {
|
|
7943
|
+
if (platform === "win32") return `a program may hold ${path} open: close it, then run \`${callerLine}\` again`;
|
|
7944
|
+
if (!isAgent(env)) return `check the permissions of ${path} and its folder`;
|
|
7945
|
+
return `your sandbox may block it: ask your user to run \`! ${callerLine}\`, or ${access === "read" ? `to allow reading ${path} in the sandbox` : `to allow ${path} in the sandbox's write list`}`;
|
|
7743
7946
|
}
|
|
7744
|
-
function
|
|
7745
|
-
return
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
hint
|
|
7947
|
+
function fileErrorOf(scope) {
|
|
7948
|
+
return (error, at) => classifyErrno(error, {
|
|
7949
|
+
...at,
|
|
7950
|
+
...scope
|
|
7749
7951
|
});
|
|
7750
7952
|
}
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
}
|
|
7953
|
+
function classifyErrno(error, context) {
|
|
7954
|
+
const { path } = context;
|
|
7955
|
+
switch (errnoOf(error)) {
|
|
7956
|
+
case "EACCES":
|
|
7957
|
+
case "EPERM":
|
|
7958
|
+
case "EROFS":
|
|
7959
|
+
case "EBUSY": return new CliError({
|
|
7960
|
+
code: PERMISSION_DENIED,
|
|
7961
|
+
message: `permission denied: ${path}`,
|
|
7962
|
+
hint: permissionHint(context)
|
|
7963
|
+
}, { cause: error });
|
|
7964
|
+
case "ENOSPC":
|
|
7965
|
+
case "EDQUOT": return new CliError({
|
|
7966
|
+
code: DISK_FULL,
|
|
7967
|
+
message: `no space left on the disk holding ${path}`,
|
|
7968
|
+
hint: `free space on that disk, then run \`${context.callerLine}\` again`
|
|
7969
|
+
}, { cause: error });
|
|
7970
|
+
case "ENOENT":
|
|
7971
|
+
case "ENOTDIR":
|
|
7972
|
+
case "ENAMETOOLONG": return new CliError({
|
|
7973
|
+
code: NOT_FOUND,
|
|
7974
|
+
message: `not found: ${path}`,
|
|
7975
|
+
hint: `check that ${path} and its folder exist`
|
|
7976
|
+
}, { cause: error });
|
|
7977
|
+
case "EEXIST": return new CliError({
|
|
7978
|
+
code: ALREADY_EXISTS,
|
|
7979
|
+
message: `already exists: ${path}`,
|
|
7980
|
+
hint: `pass another path, or remove ${path}`
|
|
7981
|
+
}, { cause: error });
|
|
7982
|
+
case "EISDIR": return new CliError({
|
|
7983
|
+
code: NOT_A_FILE,
|
|
7984
|
+
message: `not a file: ${path} is a folder`,
|
|
7985
|
+
hint: `pass the path of a file, not of the folder ${path}`
|
|
7986
|
+
}, { cause: error });
|
|
7987
|
+
default: return error;
|
|
7785
7988
|
}
|
|
7786
|
-
|
|
7787
|
-
|
|
7989
|
+
}
|
|
7990
|
+
//#endregion
|
|
7991
|
+
//#region ../agent-cli/dist/files.js
|
|
7992
|
+
const TEMP_FILE = /^\..+\.agent-cli-(\d+)\.tmp$/;
|
|
7993
|
+
/** Where the Runtime writes `path` before moving it into place; a dead writer's is swept. */
|
|
7994
|
+
function tempPathOf(path) {
|
|
7995
|
+
return join(dirname(path), `.${basename(path)}.agent-cli-${process.pid}.tmp`);
|
|
7996
|
+
}
|
|
7997
|
+
function isAlive(pid) {
|
|
7998
|
+
try {
|
|
7999
|
+
process.kill(pid, 0);
|
|
8000
|
+
return true;
|
|
8001
|
+
} catch (error) {
|
|
8002
|
+
return errnoOf(error) === "EPERM";
|
|
7788
8003
|
}
|
|
7789
|
-
|
|
7790
|
-
|
|
8004
|
+
}
|
|
8005
|
+
async function sweep(folder) {
|
|
8006
|
+
const dead = (await readdir(folder)).filter((name) => {
|
|
8007
|
+
const pid = TEMP_FILE.exec(name)?.[1];
|
|
8008
|
+
return pid !== void 0 && !isAlive(Number(pid));
|
|
8009
|
+
});
|
|
8010
|
+
await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
|
|
8011
|
+
}
|
|
8012
|
+
/** The file a symlink at `path` points to, so a replacing write keeps the link. */
|
|
8013
|
+
async function targetOf$1(path) {
|
|
8014
|
+
try {
|
|
8015
|
+
return await realpath(path);
|
|
8016
|
+
} catch (error) {
|
|
8017
|
+
if (isMissingFile(error)) return path;
|
|
8018
|
+
throw error;
|
|
7791
8019
|
}
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
controller.abort(reason);
|
|
7798
|
-
});
|
|
7799
|
-
return controller.signal;
|
|
8020
|
+
}
|
|
8021
|
+
async function writeWithMode(path, data, mode) {
|
|
8022
|
+
if (mode === void 0) {
|
|
8023
|
+
await writeFile(path, data);
|
|
8024
|
+
return;
|
|
7800
8025
|
}
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
if (this.signal.aborted) this.#finishing(name);
|
|
7804
|
-
const step = work();
|
|
7805
|
-
const settled = step.then(() => {}, () => {});
|
|
7806
|
-
this.#steps.set(settled, name);
|
|
7807
|
-
try {
|
|
7808
|
-
return await step;
|
|
7809
|
-
} finally {
|
|
7810
|
-
this.#steps.delete(settled);
|
|
7811
|
-
this.#startGrace();
|
|
7812
|
-
}
|
|
7813
|
-
}
|
|
7814
|
-
/** Waits for the Command, its must-finish steps, and no longer than the grace once interrupted. */
|
|
7815
|
-
async settle(work) {
|
|
7816
|
-
try {
|
|
7817
|
-
const value = await Promise.race([work, this.#graceOver]);
|
|
7818
|
-
if (this.signal.aborted || value === GRACE_OVER) throw this.signal.reason;
|
|
7819
|
-
return value;
|
|
7820
|
-
} finally {
|
|
7821
|
-
await this.#stepsDone();
|
|
7822
|
-
this.close();
|
|
7823
|
-
}
|
|
7824
|
-
}
|
|
7825
|
-
/**
|
|
7826
|
-
* The error a run ends with: an interrupt or the run deadline outranks what the Command threw,
|
|
7827
|
-
* unless the Command threw its own `INTERRUPTED`, saying what it finished before it stopped.
|
|
7828
|
-
* Otherwise each leaf that is not a `CliError` and whose cause chain holds a deadline's or timeout's
|
|
7829
|
-
* abort is reported as that abort, so a declared error keeps its own code.
|
|
7830
|
-
*/
|
|
7831
|
-
errorAfter(error) {
|
|
7832
|
-
if (this.signal.aborted) return error instanceof CliError && error.code === "INTERRUPTED" ? error : this.signal.reason;
|
|
7833
|
-
const leaves = errorsOf(error).map((leaf) => this.#abortIn(leaf) ?? leaf);
|
|
7834
|
-
return leaves.length === 1 ? leaves[0] : new AggregateError(leaves);
|
|
7835
|
-
}
|
|
7836
|
-
#abortIn(error) {
|
|
7837
|
-
if (error instanceof CliError) return void 0;
|
|
7838
|
-
return causesOf(error).find((each) => each instanceof CliError && this.#abortReasons.has(each));
|
|
7839
|
-
}
|
|
7840
|
-
#onStop() {
|
|
7841
|
-
for (const name of this.#steps.values()) this.#finishing(name);
|
|
7842
|
-
this.#startGrace();
|
|
7843
|
-
}
|
|
7844
|
-
#finishing(name) {
|
|
7845
|
-
this.#progress(`finishing ${name}`);
|
|
7846
|
-
}
|
|
7847
|
-
#startGrace() {
|
|
7848
|
-
if (this.signal.aborted && this.#steps.size === 0) {
|
|
7849
|
-
this.#graceCancel.abort();
|
|
7850
|
-
this.#graceCancel = new AbortController();
|
|
7851
|
-
after(this.#clock, GRACE_MS, this.#graceCancel.signal, () => this.#endGrace(GRACE_OVER));
|
|
7852
|
-
}
|
|
7853
|
-
}
|
|
7854
|
-
async #stepsDone() {
|
|
7855
|
-
while (this.#steps.size > 0) await Promise.all(this.#steps.keys());
|
|
7856
|
-
}
|
|
7857
|
-
/** Stops the run's timers and its hold on the caller's signal, for a run that never settles. */
|
|
7858
|
-
close() {
|
|
7859
|
-
this.#graceCancel.abort();
|
|
7860
|
-
this.#disposed.abort();
|
|
7861
|
-
this.#outer?.removeEventListener("abort", this.#onOuterAbort);
|
|
7862
|
-
}
|
|
7863
|
-
};
|
|
7864
|
-
//#endregion
|
|
7865
|
-
//#region ../agent-cli/dist/built-ins/upgrades.js
|
|
7866
|
-
/** A skipped release's Upgrade steps, or its whole section when it lists none. */
|
|
7867
|
-
const upgradeSchema = union([object({
|
|
7868
|
-
version: string(),
|
|
7869
|
-
steps: array(string())
|
|
7870
|
-
}), object({
|
|
7871
|
-
version: string(),
|
|
7872
|
-
notes: string()
|
|
7873
|
-
})]);
|
|
7874
|
-
const RELEASE_HEADING = /^## v?(\S+)/;
|
|
7875
|
-
const UPGRADE_HEADING = /^### Upgrade from v\S+$/;
|
|
7876
|
-
const HEADING = /^#{1,6} /;
|
|
7877
|
-
const ITEM = /^[-*] /;
|
|
7878
|
-
const NOTHING_TO_DO = /^nothing to do\.?$/i;
|
|
7879
|
-
function releasesIn(changelog) {
|
|
7880
|
-
const releases = [];
|
|
7881
|
-
let current;
|
|
7882
|
-
for (const line of changelog.split(/\r?\n/)) {
|
|
7883
|
-
const heading = RELEASE_HEADING.exec(line);
|
|
7884
|
-
if (heading !== null) {
|
|
7885
|
-
const version = heading[1] ?? "";
|
|
7886
|
-
current = isVersion(version) ? {
|
|
7887
|
-
version,
|
|
7888
|
-
lines: []
|
|
7889
|
-
} : void 0;
|
|
7890
|
-
if (current !== void 0) releases.push(current);
|
|
7891
|
-
} else current?.lines.push(line);
|
|
7892
|
-
}
|
|
7893
|
-
return releases;
|
|
7894
|
-
}
|
|
7895
|
-
function listedSteps(lines) {
|
|
7896
|
-
const start = lines.findIndex((line) => UPGRADE_HEADING.test(line.trimEnd()));
|
|
7897
|
-
if (start === -1) return [];
|
|
7898
|
-
const end = lines.findIndex((line, i) => i > start && HEADING.test(line));
|
|
7899
|
-
const body = lines.slice(start + 1, end === -1 ? void 0 : end).filter((line) => line.trim() !== "");
|
|
7900
|
-
const starts = body.flatMap((line, i) => ITEM.test(line) ? [i] : []);
|
|
7901
|
-
return starts.map((first, i) => body.slice(first, starts[i + 1]).map((line) => line.trim()).join(" ").replace(ITEM, ""));
|
|
8026
|
+
await writeFile(path, data, { mode });
|
|
8027
|
+
await chmod(path, mode);
|
|
7902
8028
|
}
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
}
|
|
8029
|
+
/** What `link` answers on a file system without hard links, such as exFAT. */
|
|
8030
|
+
const NO_HARD_LINKS = /* @__PURE__ */ new Set([
|
|
8031
|
+
"EPERM",
|
|
8032
|
+
"ENOTSUP",
|
|
8033
|
+
"EXDEV"
|
|
8034
|
+
]);
|
|
8035
|
+
/** A link fails atomically when `path` is taken, so no reader ever sees half a file there. */
|
|
8036
|
+
async function linkNew(temp, path, data, mode) {
|
|
8037
|
+
try {
|
|
8038
|
+
await link(temp, path);
|
|
8039
|
+
} catch (error) {
|
|
8040
|
+
if (!NO_HARD_LINKS.has(errnoOf(error))) throw error;
|
|
8041
|
+
await createNew(path, data, mode);
|
|
8042
|
+
}
|
|
7914
8043
|
}
|
|
7915
|
-
/**
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
*/
|
|
7919
|
-
async function upgradesOf(packageDir, from, to) {
|
|
7920
|
-
let changelog;
|
|
8044
|
+
/** A file this call created and could not finish is removed, so a rerun does not find half of it. */
|
|
8045
|
+
async function createNew(path, data, mode) {
|
|
8046
|
+
const handle = await open(path, "wx", mode);
|
|
7921
8047
|
try {
|
|
7922
|
-
|
|
8048
|
+
await handle.writeFile(data);
|
|
8049
|
+
if (mode !== void 0) await handle.chmod(mode);
|
|
7923
8050
|
} catch (error) {
|
|
7924
|
-
|
|
8051
|
+
await handle.close();
|
|
8052
|
+
await rm(path, { force: true });
|
|
7925
8053
|
throw error;
|
|
7926
8054
|
}
|
|
7927
|
-
|
|
7928
|
-
}
|
|
7929
|
-
//#endregion
|
|
7930
|
-
//#region ../agent-cli/dist/built-ins/update.js
|
|
7931
|
-
const updateSchema = object({
|
|
7932
|
-
from: string(),
|
|
7933
|
-
latest: string(),
|
|
7934
|
-
installed: object({
|
|
7935
|
-
version: string(),
|
|
7936
|
-
prefix: string()
|
|
7937
|
-
}).nullable(),
|
|
7938
|
-
upgrades: array(upgradeSchema),
|
|
7939
|
-
next: array(string())
|
|
7940
|
-
});
|
|
7941
|
-
function nothingToInstall({ from, latest }) {
|
|
7942
|
-
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`;
|
|
8055
|
+
await handle.close();
|
|
7943
8056
|
}
|
|
7944
|
-
|
|
7945
|
-
|
|
8057
|
+
const RENAME_ATTEMPTS = 10;
|
|
8058
|
+
const RENAME_BACKOFF_MS = 10;
|
|
8059
|
+
const HELD_BY_ANOTHER_PROGRAM = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
|
|
8060
|
+
async function renameRetrying(from, to) {
|
|
8061
|
+
for (let attempt = 1;; attempt++) try {
|
|
8062
|
+
await rename(from, to);
|
|
8063
|
+
return;
|
|
8064
|
+
} catch (error) {
|
|
8065
|
+
if (!(process.platform === "win32" && HELD_BY_ANOTHER_PROGRAM.has(errnoOf(error))) || attempt === RENAME_ATTEMPTS) throw error;
|
|
8066
|
+
await setTimeout$1(attempt * RENAME_BACKOFF_MS);
|
|
8067
|
+
}
|
|
7946
8068
|
}
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
const
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
8069
|
+
/** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
|
|
8070
|
+
function realFiles() {
|
|
8071
|
+
const swept = /* @__PURE__ */ new Set();
|
|
8072
|
+
return {
|
|
8073
|
+
exists: async (path) => {
|
|
8074
|
+
try {
|
|
8075
|
+
await access(path);
|
|
8076
|
+
return true;
|
|
8077
|
+
} catch (error) {
|
|
8078
|
+
if (isNothingThere(error)) return false;
|
|
8079
|
+
throw error;
|
|
8080
|
+
}
|
|
8081
|
+
},
|
|
8082
|
+
write: async (path, data, options = {}) => {
|
|
8083
|
+
await mkdir(dirname(path), { recursive: true });
|
|
8084
|
+
const target = options.exclusive === true ? path : await targetOf$1(path);
|
|
8085
|
+
const folder = dirname(target);
|
|
8086
|
+
if (!swept.has(folder)) {
|
|
8087
|
+
await sweep(folder);
|
|
8088
|
+
swept.add(folder);
|
|
8089
|
+
}
|
|
8090
|
+
const temp = tempPathOf(target);
|
|
8091
|
+
try {
|
|
8092
|
+
await writeWithMode(temp, data, options.mode);
|
|
8093
|
+
if (options.exclusive === true) {
|
|
8094
|
+
await linkNew(temp, target, data, options.mode);
|
|
8095
|
+
await rm(temp);
|
|
8096
|
+
} else await renameRetrying(temp, target);
|
|
8097
|
+
} catch (error) {
|
|
8098
|
+
await rm(temp, { force: true });
|
|
8099
|
+
throw error;
|
|
8100
|
+
}
|
|
8101
|
+
},
|
|
8102
|
+
read: (path) => readFile(path),
|
|
8103
|
+
list: (folder) => readdir(folder),
|
|
8104
|
+
remove: (path) => rm(path, {
|
|
8105
|
+
recursive: true,
|
|
8106
|
+
force: true
|
|
8107
|
+
}),
|
|
8108
|
+
mkdir: async (path, options) => {
|
|
8109
|
+
const mode = options?.mode;
|
|
8110
|
+
if (options?.exclusive !== true) {
|
|
8111
|
+
await mkdir(path, {
|
|
8112
|
+
recursive: true,
|
|
8113
|
+
mode
|
|
8114
|
+
});
|
|
8115
|
+
return;
|
|
8116
|
+
}
|
|
8117
|
+
await mkdir(dirname(path), {
|
|
8118
|
+
recursive: true,
|
|
8119
|
+
mode
|
|
8120
|
+
});
|
|
8121
|
+
await mkdir(path, { mode });
|
|
8122
|
+
},
|
|
8123
|
+
stat: async (path) => {
|
|
8124
|
+
try {
|
|
8125
|
+
const { size, dev, ino } = await stat(path, { bigint: true });
|
|
8126
|
+
return {
|
|
8127
|
+
size: Number(size),
|
|
8128
|
+
id: `${dev}:${ino}`
|
|
8129
|
+
};
|
|
8130
|
+
} catch (error) {
|
|
8131
|
+
if (isNothingThere(error)) return void 0;
|
|
8132
|
+
throw error;
|
|
8133
|
+
}
|
|
8134
|
+
}
|
|
8135
|
+
};
|
|
7956
8136
|
}
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
8137
|
+
/**
|
|
8138
|
+
* `files` resolving paths against `cwd`, its failures classified by errno; `list` drops the
|
|
8139
|
+
* Runtime's temp files and sorts.
|
|
8140
|
+
*/
|
|
8141
|
+
function classifiedFiles(files, { cwd, fileError }) {
|
|
8142
|
+
async function at(access, path, work) {
|
|
8143
|
+
const target = resolve(cwd, path);
|
|
8144
|
+
try {
|
|
8145
|
+
return await work(target);
|
|
8146
|
+
} catch (error) {
|
|
8147
|
+
throw fileError(error, {
|
|
8148
|
+
path: target,
|
|
8149
|
+
access
|
|
8150
|
+
});
|
|
8151
|
+
}
|
|
8152
|
+
}
|
|
8153
|
+
return {
|
|
8154
|
+
exists: (path) => at("read", path, (target) => files.exists(target)),
|
|
8155
|
+
write: (path, data, options) => at("write", path, (target) => files.write(target, data, options)),
|
|
8156
|
+
read: (path) => at("read", path, (target) => files.read(target)),
|
|
8157
|
+
list: (folder) => at("read", folder, async (target) => (await files.list(target)).filter((name) => !TEMP_FILE.test(name)).sort()),
|
|
8158
|
+
remove: (path) => at("write", path, (target) => files.remove(target)),
|
|
8159
|
+
mkdir: (path, options) => at("write", path, (target) => files.mkdir(target, options)),
|
|
8160
|
+
stat: (path) => at("read", path, (target) => files.stat(target))
|
|
8161
|
+
};
|
|
7960
8162
|
}
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
8163
|
+
//#endregion
|
|
8164
|
+
//#region ../agent-cli/dist/outputs.js
|
|
8165
|
+
const OUTPUT_EXISTS = "OUTPUT_EXISTS";
|
|
8166
|
+
/** Refuses a run that would overwrite existing outputs unless the caller passed `--force`. */
|
|
8167
|
+
async function checkOutputs({ invocation, commandLine, force, files }, outputs) {
|
|
8168
|
+
if (outputs === void 0 || force) return;
|
|
8169
|
+
const found = await Promise.all(outputs.map((path) => files.exists(path)));
|
|
8170
|
+
const existing = outputs.filter((_path, index) => found[index]);
|
|
8171
|
+
if (existing.length === 0) return;
|
|
8172
|
+
const them = existing.length === 1 ? "it" : "them";
|
|
8173
|
+
throw new CliError({
|
|
8174
|
+
code: OUTPUT_EXISTS,
|
|
8175
|
+
message: `\`${commandLine}\` would overwrite ${existing.join(", ")}`,
|
|
8176
|
+
hint: `to replace ${them}, run \`${commandLineOf(invocation, { add: [FORCE_FLAG.flag] })}\``
|
|
7966
8177
|
});
|
|
7967
8178
|
}
|
|
7968
|
-
/**
|
|
7969
|
-
function
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
const { from, latest, prefix } = plan;
|
|
7995
|
-
if (standingOf$1(from, latest) !== "behind") return {
|
|
7996
|
-
from,
|
|
7997
|
-
latest,
|
|
7998
|
-
installed: null,
|
|
7999
|
-
upgrades: [],
|
|
8000
|
-
next: await skillNext(cli, latest, ctx)
|
|
8001
|
-
};
|
|
8002
|
-
ctx.signal.throwIfAborted();
|
|
8003
|
-
ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
|
|
8004
|
-
await ctx.runToEnd(`installing ${plan.package}@${latest}`, () => installGlobally(latest, globalNpm(cli, install, ctx, node)));
|
|
8005
|
-
const update = {
|
|
8006
|
-
from,
|
|
8007
|
-
latest,
|
|
8008
|
-
installed: {
|
|
8009
|
-
version: latest,
|
|
8010
|
-
prefix
|
|
8011
|
-
},
|
|
8012
|
-
upgrades: await upgradesOf(plan.packageDir, from, latest),
|
|
8013
|
-
next: await skillNext(cli, latest, ctx)
|
|
8014
|
-
};
|
|
8015
|
-
if (ctx.signal.aborted) {
|
|
8016
|
-
ctx.deliver(update);
|
|
8017
|
-
throw interruptedAfterInstall(plan);
|
|
8018
|
-
}
|
|
8019
|
-
return update;
|
|
8179
|
+
/** Guards the writes to declared outputs of `files`, whose paths resolve against `cwd`. */
|
|
8180
|
+
function outputsGuard(files, cwd) {
|
|
8181
|
+
const unwritten = /* @__PURE__ */ new Set();
|
|
8182
|
+
let forceLine = "";
|
|
8183
|
+
const exclusiveWrite = async (path, data, options) => {
|
|
8184
|
+
const target = resolve(cwd, path);
|
|
8185
|
+
unwritten.delete(target);
|
|
8186
|
+
try {
|
|
8187
|
+
await files.write(path, data, {
|
|
8188
|
+
...options,
|
|
8189
|
+
exclusive: true
|
|
8190
|
+
});
|
|
8191
|
+
} catch (error) {
|
|
8192
|
+
unwritten.add(target);
|
|
8193
|
+
if (!(error instanceof CliError) || error.code !== "ALREADY_EXISTS") throw error;
|
|
8194
|
+
throw new CliError({
|
|
8195
|
+
code: ALREADY_EXISTS,
|
|
8196
|
+
message: error.message,
|
|
8197
|
+
hint: `to replace it, run \`${forceLine}\``
|
|
8198
|
+
}, { cause: error });
|
|
8199
|
+
}
|
|
8200
|
+
};
|
|
8201
|
+
return {
|
|
8202
|
+
files: {
|
|
8203
|
+
...files,
|
|
8204
|
+
write: (path, data, options) => unwritten.has(resolve(cwd, path)) ? exclusiveWrite(path, data, options) : files.write(path, data, options)
|
|
8020
8205
|
},
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8206
|
+
guard: (outputs, line) => {
|
|
8207
|
+
forceLine = line;
|
|
8208
|
+
for (const path of outputs) unwritten.add(resolve(cwd, path));
|
|
8209
|
+
}
|
|
8210
|
+
};
|
|
8211
|
+
}
|
|
8212
|
+
/**
|
|
8213
|
+
* Creates each output folder and a temp file in it, so a sandbox denial fails before `apply`
|
|
8214
|
+
* spends anything. The temp file's name is the Runtime's, so a crashed run's leftover is swept.
|
|
8215
|
+
*/
|
|
8216
|
+
async function probeOutputs({ files, cwd, fileError }, outputs) {
|
|
8217
|
+
const folders = new Set(outputs.map((path) => dirname(resolve(cwd, path))));
|
|
8218
|
+
await Promise.all([...folders].map(async (folder) => {
|
|
8219
|
+
const probe = tempPathOf(join(folder, "outputs-probe"));
|
|
8220
|
+
try {
|
|
8221
|
+
await files.mkdir(folder);
|
|
8222
|
+
await files.write(probe, "");
|
|
8223
|
+
await files.remove(probe);
|
|
8224
|
+
} catch (error) {
|
|
8225
|
+
throw fileError(error, {
|
|
8226
|
+
path: folder,
|
|
8227
|
+
access: "write"
|
|
8228
|
+
});
|
|
8229
|
+
}
|
|
8230
|
+
}));
|
|
8024
8231
|
}
|
|
8025
8232
|
//#endregion
|
|
8026
|
-
//#region ../agent-cli/dist/built-ins/
|
|
8027
|
-
|
|
8028
|
-
|
|
8233
|
+
//#region ../agent-cli/dist/built-ins/skill-install.js
|
|
8234
|
+
const installSchema = object({
|
|
8235
|
+
copy: string(),
|
|
8236
|
+
written: array(string()),
|
|
8237
|
+
removed: array(string()),
|
|
8238
|
+
agentsMd: object({
|
|
8239
|
+
path: string(),
|
|
8240
|
+
changed: boolean()
|
|
8241
|
+
}),
|
|
8242
|
+
handEdits: array(string())
|
|
8243
|
+
});
|
|
8244
|
+
async function sameContent(left, right) {
|
|
8245
|
+
const [a, b] = await Promise.all([readFile(left), readFile(right)]);
|
|
8246
|
+
return a.equals(b);
|
|
8247
|
+
}
|
|
8248
|
+
/** One paragraph telling every session that the CLI exists, how to run it and which skill to load. */
|
|
8249
|
+
function agentsBlock(cli) {
|
|
8250
|
+
const { install } = cli;
|
|
8251
|
+
const where = install?.mode === "dependency" ? `, and only from a package that has \`${install.package}\` in its dependencies; from anywhere else npx reports the package missing` : "";
|
|
8252
|
+
const what = cli.description.replace(/\.$/, "");
|
|
8253
|
+
return `\`${cli.name}\`: ${what}. Run it as \`${runAsOf(cli)}\`${where}. Load the \`${cli.name}\` skill before using it.`;
|
|
8254
|
+
}
|
|
8255
|
+
function markersOf(name) {
|
|
8029
8256
|
return {
|
|
8030
|
-
|
|
8031
|
-
|
|
8257
|
+
begin: `<!-- BEGIN:${name} -->`,
|
|
8258
|
+
end: `<!-- END:${name} -->`
|
|
8259
|
+
};
|
|
8260
|
+
}
|
|
8261
|
+
/** `text` with the block between its markers, appended when they are missing; nothing else changes. */
|
|
8262
|
+
function withBlock(text, block, { begin, end }) {
|
|
8263
|
+
const whole = `${begin}\n${block}\n${end}`;
|
|
8264
|
+
const start = text.indexOf(begin);
|
|
8265
|
+
const stop = start === -1 ? -1 : text.indexOf(end, start);
|
|
8266
|
+
if (stop !== -1) return `${text.slice(0, start)}${whole}${text.slice(stop + end.length)}`;
|
|
8267
|
+
if (text === "") return `${whole}\n`;
|
|
8268
|
+
return `${text}${text.endsWith("\n") ? "" : "\n"}\n${whole}\n`;
|
|
8269
|
+
}
|
|
8270
|
+
function blockChangeOf(existing, block, markers) {
|
|
8271
|
+
if (existing === void 0) return "create";
|
|
8272
|
+
if (withBlock(existing, block, markers) === existing) return null;
|
|
8273
|
+
return existing.includes(markers.begin) ? "rewrite" : "add";
|
|
8274
|
+
}
|
|
8275
|
+
async function handEditsAt(root) {
|
|
8276
|
+
const claudeMd = join(root, "CLAUDE.md");
|
|
8277
|
+
const text = await orMissing(readFile(claudeMd, "utf8"));
|
|
8278
|
+
if (text === void 0 || text.split(/\r?\n/).some((line) => line.trim() === "@AGENTS.md")) return [];
|
|
8279
|
+
return [`add a line \`@AGENTS.md\` to ${claudeMd}, so Claude Code reads AGENTS.md`];
|
|
8280
|
+
}
|
|
8281
|
+
async function planOf(cli, folder, cwd, packageRoot) {
|
|
8282
|
+
const source = join(packageRoot, folder);
|
|
8283
|
+
const { root, copy } = await skillCopyOf(cli, cwd);
|
|
8284
|
+
const [shipped, present] = await Promise.all([filesIn(source), filesIn(copy)]);
|
|
8285
|
+
const differs = await Promise.all(shipped.map(async (file) => present.includes(file) && !await sameContent(join(source, file), join(copy, file))));
|
|
8286
|
+
const agentsPath = join(root, "AGENTS.md");
|
|
8287
|
+
const block = agentsBlock(cli);
|
|
8288
|
+
return {
|
|
8289
|
+
source,
|
|
8290
|
+
copy,
|
|
8291
|
+
write: shipped.filter((file) => !present.includes(file)),
|
|
8292
|
+
replace: shipped.filter((_file, index) => differs[index]),
|
|
8293
|
+
remove: present.filter((file) => !shipped.includes(file)),
|
|
8294
|
+
sameVersion: (await skillVersionsOf(source, copy)).same,
|
|
8295
|
+
agentsMd: {
|
|
8296
|
+
path: agentsPath,
|
|
8297
|
+
block,
|
|
8298
|
+
change: blockChangeOf(await orMissing(readFile(agentsPath, "utf8")), block, markersOf(cli.name))
|
|
8299
|
+
},
|
|
8300
|
+
handEdits: await handEditsAt(root)
|
|
8032
8301
|
};
|
|
8033
8302
|
}
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
const
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8303
|
+
const BLOCK_VERBS = {
|
|
8304
|
+
create: "create",
|
|
8305
|
+
add: "add the block to",
|
|
8306
|
+
rewrite: "rewrite the block in"
|
|
8307
|
+
};
|
|
8308
|
+
function renderPlan(cli, plan) {
|
|
8309
|
+
const files = [
|
|
8310
|
+
...plan.write.map((file) => ` write ${file}`),
|
|
8311
|
+
...plan.replace.map((file) => ` replace ${file}`),
|
|
8312
|
+
...plan.remove.map((file) => ` remove ${file}`)
|
|
8313
|
+
];
|
|
8314
|
+
const { path, block, change } = plan.agentsMd;
|
|
8315
|
+
return [
|
|
8316
|
+
files.length === 0 ? `the ${cli.name} skill at ${plan.copy} already matches the package` : `would copy the ${cli.name} skill to ${plan.copy}:`,
|
|
8317
|
+
...files,
|
|
8318
|
+
change === null ? `${path} already holds the ${cli.name} block` : `would ${BLOCK_VERBS[change]} ${path}:\n${block}`,
|
|
8319
|
+
...plan.handEdits.map((edit) => `by hand: ${edit}`)
|
|
8320
|
+
].join("\n");
|
|
8321
|
+
}
|
|
8322
|
+
function renderInstalled(cli, data) {
|
|
8323
|
+
const { written, removed, agentsMd } = data;
|
|
8324
|
+
return [
|
|
8325
|
+
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}`,
|
|
8326
|
+
agentsMd.changed ? `wrote the ${cli.name} block in ${agentsMd.path}` : `${agentsMd.path} already holds the ${cli.name} block`,
|
|
8327
|
+
...data.handEdits.map((edit) => `by hand: ${edit}`)
|
|
8328
|
+
].join("\n");
|
|
8329
|
+
}
|
|
8330
|
+
function editedRemovals(plan, ctx) {
|
|
8331
|
+
const them = plan.remove.length === 1 ? "it" : "them";
|
|
8332
|
+
return new CliError({
|
|
8333
|
+
code: OUTPUT_EXISTS,
|
|
8334
|
+
message: `\`${ctx.commandLine()}\` would remove ${plan.remove.map((file) => join(plan.copy, file)).join(", ")}`,
|
|
8335
|
+
hint: `to remove ${them}, run \`${ctx.commandLine({ add: [FORCE_FLAG.flag] })}\``
|
|
8053
8336
|
});
|
|
8054
8337
|
}
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
const
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8338
|
+
/** The built-in `skill install`, for a CLI whose skill ships in its package. */
|
|
8339
|
+
function skillInstallCommand(cli, folder) {
|
|
8340
|
+
const runAs = runAsOf(cli);
|
|
8341
|
+
return defineCommand$1({
|
|
8342
|
+
name: "install",
|
|
8343
|
+
description: "copy this CLI's skill into the project and write its AGENTS.md block",
|
|
8344
|
+
examples: [{
|
|
8345
|
+
command: `${runAs} skill install`,
|
|
8346
|
+
note: "install the skill this version ships"
|
|
8347
|
+
}, {
|
|
8348
|
+
command: `${runAs} skill install --dry-run`,
|
|
8349
|
+
note: "show what it would change"
|
|
8350
|
+
}],
|
|
8351
|
+
effect: "write",
|
|
8352
|
+
output: installSchema,
|
|
8353
|
+
force: "replace or remove hand-edited files of a skill copy at the package's version",
|
|
8354
|
+
plan: (_input, ctx) => planOf(cli, folder, ctx.cwd, ctx.packageRoot),
|
|
8355
|
+
outputs: (plan) => plan.sameVersion ? [...plan.write, ...plan.replace].map((file) => join(plan.copy, file)) : [],
|
|
8356
|
+
apply: async (plan, ctx) => {
|
|
8357
|
+
if (plan.sameVersion && plan.remove.length > 0 && !ctx.force) throw editedRemovals(plan, ctx);
|
|
8358
|
+
for (const file of [...plan.write, ...plan.replace]) await ctx.files.write(join(plan.copy, file), await readFile(join(plan.source, file)));
|
|
8359
|
+
for (const file of plan.remove) await ctx.files.remove(join(plan.copy, file));
|
|
8360
|
+
const { path, block, change } = plan.agentsMd;
|
|
8361
|
+
if (change !== null) {
|
|
8362
|
+
const existing = await orMissing(readFile(path, "utf8")) ?? "";
|
|
8363
|
+
await ctx.files.write(path, withBlock(existing, block, markersOf(cli.name)));
|
|
8364
|
+
}
|
|
8365
|
+
return {
|
|
8366
|
+
copy: plan.copy,
|
|
8367
|
+
written: [...plan.write, ...plan.replace].sort(),
|
|
8368
|
+
removed: [...plan.remove],
|
|
8369
|
+
agentsMd: {
|
|
8370
|
+
path,
|
|
8371
|
+
changed: change !== null
|
|
8372
|
+
},
|
|
8373
|
+
handEdits: [...plan.handEdits]
|
|
8374
|
+
};
|
|
8375
|
+
},
|
|
8376
|
+
render: (data) => renderInstalled(cli, data),
|
|
8377
|
+
renderPlan: (plan) => renderPlan(cli, plan)
|
|
8065
8378
|
});
|
|
8066
8379
|
}
|
|
8067
8380
|
//#endregion
|
|
8068
|
-
//#region ../agent-cli/dist/
|
|
8069
|
-
const
|
|
8070
|
-
const
|
|
8071
|
-
|
|
8072
|
-
const
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
isSet("OPENCODE"),
|
|
8081
|
-
isSet("ANTIGRAVITY_AGENT"),
|
|
8082
|
-
isSet("AUGMENT_AGENT"),
|
|
8083
|
-
isSet("REPL_ID"),
|
|
8084
|
-
isSet("CLAUDE_CODE_IS_COWORK"),
|
|
8085
|
-
isSet("CLAUDECODE"),
|
|
8086
|
-
isSet("CLAUDE_CODE"),
|
|
8087
|
-
isSet("CURSOR_TRACE_ID"),
|
|
8088
|
-
isSet("CURSOR_AGENT"),
|
|
8089
|
-
equals("CURSOR_EXTENSION_HOST_ROLE", "agent-exec"),
|
|
8090
|
-
equals("TERM_PROGRAM", "kiro"),
|
|
8091
|
-
(env) => ["/.pi/agent", "\\.pi\\agent"].some((dir) => (env.PATH ?? "").includes(dir)),
|
|
8092
|
-
isSet("GOOSE_PROVIDER")
|
|
8093
|
-
];
|
|
8094
|
-
/**
|
|
8095
|
-
* Whether a Coding agent, not a person, is running the Agent CLI. Some Coding agents run commands
|
|
8096
|
-
* in a PTY, so a TTY alone doesn't mean a person is watching.
|
|
8097
|
-
*/
|
|
8098
|
-
function isAgent(env) {
|
|
8099
|
-
return MARKERS.some((marker) => marker(env));
|
|
8381
|
+
//#region ../agent-cli/dist/interrupts.js
|
|
8382
|
+
const INTERRUPTED = "INTERRUPTED";
|
|
8383
|
+
const DEADLINE_EXCEEDED = "DEADLINE_EXCEEDED";
|
|
8384
|
+
/** How long an interrupted Command may take to settle before the run ends without it. */
|
|
8385
|
+
const GRACE_MS = 3e3;
|
|
8386
|
+
const GRACE_OVER = Symbol("grace over");
|
|
8387
|
+
function interruptedError(callerLine) {
|
|
8388
|
+
return new CliError({
|
|
8389
|
+
code: INTERRUPTED,
|
|
8390
|
+
message: "interrupted before it finished",
|
|
8391
|
+
hint: `to finish it, run \`${callerLine}\` again, unless it was stopped on purpose; if a tool timeout stopped it, give it longer`
|
|
8392
|
+
});
|
|
8100
8393
|
}
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
8104
|
-
const DISK_FULL = "DISK_FULL";
|
|
8105
|
-
const NOT_FOUND = "NOT_FOUND";
|
|
8106
|
-
const ALREADY_EXISTS = "ALREADY_EXISTS";
|
|
8107
|
-
const NOT_A_FILE = "NOT_A_FILE";
|
|
8108
|
-
function permissionHint({ path, access, callerLine, env, platform }) {
|
|
8109
|
-
if (platform === "win32") return `a program may hold ${path} open: close it, then run \`${callerLine}\` again`;
|
|
8110
|
-
if (!isAgent(env)) return `check the permissions of ${path} and its folder`;
|
|
8111
|
-
return `your sandbox may block it: ask your user to run \`! ${callerLine}\`, or ${access === "read" ? `to allow reading ${path} in the sandbox` : `to allow ${path} in the sandbox's write list`}`;
|
|
8394
|
+
function waitHint(callerLine, bound) {
|
|
8395
|
+
return bound === "timeout" ? `the call may have been billed: ask your user before running \`${callerLine}\` again` : `run \`${callerLine}\` again later, when the service it waits on may answer faster`;
|
|
8112
8396
|
}
|
|
8113
|
-
function
|
|
8114
|
-
return
|
|
8115
|
-
|
|
8116
|
-
|
|
8397
|
+
function deadlineError(commandLine, ms, hint, bound = "deadline") {
|
|
8398
|
+
return new CliError({
|
|
8399
|
+
code: DEADLINE_EXCEEDED,
|
|
8400
|
+
message: `${commandLine} ran past its ${ms} ms ${bound}`,
|
|
8401
|
+
hint
|
|
8117
8402
|
});
|
|
8118
8403
|
}
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
}
|
|
8153
|
-
|
|
8404
|
+
/** One Command run's cancellation: its signal, its deadlines and the steps it must finish. */
|
|
8405
|
+
var RunControl = class {
|
|
8406
|
+
signal;
|
|
8407
|
+
#controller = new AbortController();
|
|
8408
|
+
#commandLine;
|
|
8409
|
+
#callerLine;
|
|
8410
|
+
#outer;
|
|
8411
|
+
#onOuterAbort = () => this.#controller.abort(interruptedError(this.#callerLine));
|
|
8412
|
+
/** Each running must-finish step, tracked until it settles, with its name. */
|
|
8413
|
+
#steps = /* @__PURE__ */ new Map();
|
|
8414
|
+
#progress;
|
|
8415
|
+
#clock;
|
|
8416
|
+
#disposed = new AbortController();
|
|
8417
|
+
/** The reasons this run's deadlines and timeouts aborted with, to tell them in a Command's error. */
|
|
8418
|
+
#abortReasons = /* @__PURE__ */ new WeakSet();
|
|
8419
|
+
#graceCancel = new AbortController();
|
|
8420
|
+
#endGrace = () => {};
|
|
8421
|
+
#graceOver = new Promise((resolve) => {
|
|
8422
|
+
this.#endGrace = resolve;
|
|
8423
|
+
});
|
|
8424
|
+
constructor({ commandLine, callerLine, outer, clock, progress, runDeadline }) {
|
|
8425
|
+
this.#commandLine = commandLine;
|
|
8426
|
+
this.#callerLine = callerLine;
|
|
8427
|
+
this.#outer = outer;
|
|
8428
|
+
this.#clock = clock;
|
|
8429
|
+
this.#progress = progress;
|
|
8430
|
+
this.signal = this.#controller.signal;
|
|
8431
|
+
this.signal.addEventListener("abort", () => this.#onStop(), { once: true });
|
|
8432
|
+
if (outer?.aborted === true) this.#onOuterAbort();
|
|
8433
|
+
else outer?.addEventListener("abort", this.#onOuterAbort, { once: true });
|
|
8434
|
+
if (runDeadline !== void 0) {
|
|
8435
|
+
const { ms, hint } = runDeadline;
|
|
8436
|
+
after(clock, ms, this.#disposed.signal, () => this.#controller.abort(deadlineError(commandLine, ms, hint())));
|
|
8437
|
+
}
|
|
8438
|
+
}
|
|
8439
|
+
deadline(ms) {
|
|
8440
|
+
return AbortSignal.any([this.signal, this.#abortsAfter(ms, "deadline")]);
|
|
8441
|
+
}
|
|
8442
|
+
timeout(ms) {
|
|
8443
|
+
return this.#abortsAfter(ms, "timeout");
|
|
8444
|
+
}
|
|
8445
|
+
#abortsAfter(ms, bound) {
|
|
8446
|
+
const controller = new AbortController();
|
|
8447
|
+
after(this.#clock, ms, this.#disposed.signal, () => {
|
|
8448
|
+
const reason = deadlineError(this.#commandLine, ms, waitHint(this.#callerLine, bound), bound);
|
|
8449
|
+
this.#abortReasons.add(reason);
|
|
8450
|
+
controller.abort(reason);
|
|
8451
|
+
});
|
|
8452
|
+
return controller.signal;
|
|
8453
|
+
}
|
|
8454
|
+
async runToEnd(name, work) {
|
|
8455
|
+
this.#graceCancel.abort();
|
|
8456
|
+
if (this.signal.aborted) this.#finishing(name);
|
|
8457
|
+
const step = work();
|
|
8458
|
+
const settled = step.then(() => {}, () => {});
|
|
8459
|
+
this.#steps.set(settled, name);
|
|
8460
|
+
try {
|
|
8461
|
+
return await step;
|
|
8462
|
+
} finally {
|
|
8463
|
+
this.#steps.delete(settled);
|
|
8464
|
+
this.#startGrace();
|
|
8465
|
+
}
|
|
8466
|
+
}
|
|
8467
|
+
/** Waits for the Command, its must-finish steps, and no longer than the grace once interrupted. */
|
|
8468
|
+
async settle(work) {
|
|
8469
|
+
try {
|
|
8470
|
+
const value = await Promise.race([work, this.#graceOver]);
|
|
8471
|
+
if (this.signal.aborted || value === GRACE_OVER) throw this.signal.reason;
|
|
8472
|
+
return value;
|
|
8473
|
+
} finally {
|
|
8474
|
+
await this.#stepsDone();
|
|
8475
|
+
this.close();
|
|
8476
|
+
}
|
|
8477
|
+
}
|
|
8478
|
+
/**
|
|
8479
|
+
* The error a run ends with: an interrupt or the run deadline outranks what the Command threw,
|
|
8480
|
+
* unless the Command threw its own `INTERRUPTED`, saying what it finished before it stopped.
|
|
8481
|
+
* Otherwise each leaf that is not a `CliError` and whose cause chain holds a deadline's or timeout's
|
|
8482
|
+
* abort is reported as that abort, so a declared error keeps its own code.
|
|
8483
|
+
*/
|
|
8484
|
+
errorAfter(error) {
|
|
8485
|
+
if (this.signal.aborted) return error instanceof CliError && error.code === "INTERRUPTED" ? error : this.signal.reason;
|
|
8486
|
+
const leaves = errorsOf(error).map((leaf) => this.#abortIn(leaf) ?? leaf);
|
|
8487
|
+
return leaves.length === 1 ? leaves[0] : new AggregateError(leaves);
|
|
8488
|
+
}
|
|
8489
|
+
#abortIn(error) {
|
|
8490
|
+
if (error instanceof CliError) return void 0;
|
|
8491
|
+
return causesOf(error).find((each) => each instanceof CliError && this.#abortReasons.has(each));
|
|
8154
8492
|
}
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
}
|
|
8493
|
+
#onStop() {
|
|
8494
|
+
for (const name of this.#steps.values()) this.#finishing(name);
|
|
8495
|
+
this.#startGrace();
|
|
8496
|
+
}
|
|
8497
|
+
#finishing(name) {
|
|
8498
|
+
this.#progress(`finishing ${name}`);
|
|
8499
|
+
}
|
|
8500
|
+
#startGrace() {
|
|
8501
|
+
if (this.signal.aborted && this.#steps.size === 0) {
|
|
8502
|
+
this.#graceCancel.abort();
|
|
8503
|
+
this.#graceCancel = new AbortController();
|
|
8504
|
+
after(this.#clock, GRACE_MS, this.#graceCancel.signal, () => this.#endGrace(GRACE_OVER));
|
|
8505
|
+
}
|
|
8506
|
+
}
|
|
8507
|
+
async #stepsDone() {
|
|
8508
|
+
while (this.#steps.size > 0) await Promise.all(this.#steps.keys());
|
|
8509
|
+
}
|
|
8510
|
+
/** Stops the run's timers and its hold on the caller's signal, for a run that never settles. */
|
|
8511
|
+
close() {
|
|
8512
|
+
this.#graceCancel.abort();
|
|
8513
|
+
this.#disposed.abort();
|
|
8514
|
+
this.#outer?.removeEventListener("abort", this.#onOuterAbort);
|
|
8515
|
+
}
|
|
8516
|
+
};
|
|
8168
8517
|
//#endregion
|
|
8169
|
-
//#region ../agent-cli/dist/
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
}
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8518
|
+
//#region ../agent-cli/dist/built-ins/upgrades.js
|
|
8519
|
+
/** A skipped release's Upgrade steps, or its whole section when it lists none. */
|
|
8520
|
+
const upgradeSchema = union([object({
|
|
8521
|
+
version: string(),
|
|
8522
|
+
steps: array(string())
|
|
8523
|
+
}), object({
|
|
8524
|
+
version: string(),
|
|
8525
|
+
notes: string()
|
|
8526
|
+
})]);
|
|
8527
|
+
const RELEASE_HEADING = /^## v?(\S+)/;
|
|
8528
|
+
const UPGRADE_HEADING = /^### Upgrade from v\S+$/;
|
|
8529
|
+
const HEADING = /^#{1,6} /;
|
|
8530
|
+
const ITEM = /^[-*] /;
|
|
8531
|
+
const NOTHING_TO_DO = /^nothing to do\.?$/i;
|
|
8532
|
+
function releasesIn(changelog) {
|
|
8533
|
+
const releases = [];
|
|
8534
|
+
let current;
|
|
8535
|
+
for (const line of changelog.split(/\r?\n/)) {
|
|
8536
|
+
const heading = RELEASE_HEADING.exec(line);
|
|
8537
|
+
if (heading !== null) {
|
|
8538
|
+
const version = heading[1] ?? "";
|
|
8539
|
+
current = isVersion(version) ? {
|
|
8540
|
+
version,
|
|
8541
|
+
lines: []
|
|
8542
|
+
} : void 0;
|
|
8543
|
+
if (current !== void 0) releases.push(current);
|
|
8544
|
+
} else current?.lines.push(line);
|
|
8181
8545
|
}
|
|
8546
|
+
return releases;
|
|
8182
8547
|
}
|
|
8183
|
-
|
|
8184
|
-
const
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8548
|
+
function listedSteps(lines) {
|
|
8549
|
+
const start = lines.findIndex((line) => UPGRADE_HEADING.test(line.trimEnd()));
|
|
8550
|
+
if (start === -1) return [];
|
|
8551
|
+
const end = lines.findIndex((line, i) => i > start && HEADING.test(line));
|
|
8552
|
+
const body = lines.slice(start + 1, end === -1 ? void 0 : end).filter((line) => line.trim() !== "");
|
|
8553
|
+
const starts = body.flatMap((line, i) => ITEM.test(line) ? [i] : []);
|
|
8554
|
+
return starts.map((first, i) => body.slice(first, starts[i + 1]).map((line) => line.trim()).join(" ").replace(ITEM, ""));
|
|
8189
8555
|
}
|
|
8190
|
-
|
|
8191
|
-
|
|
8556
|
+
function upgradeOf({ version, lines }) {
|
|
8557
|
+
const listed = listedSteps(lines);
|
|
8558
|
+
if (listed.length === 0) return [{
|
|
8559
|
+
version,
|
|
8560
|
+
notes: lines.join("\n").trim()
|
|
8561
|
+
}];
|
|
8562
|
+
const todo = listed.filter((step) => !NOTHING_TO_DO.test(step));
|
|
8563
|
+
return todo.length === 0 ? [] : [{
|
|
8564
|
+
version,
|
|
8565
|
+
steps: todo
|
|
8566
|
+
}];
|
|
8567
|
+
}
|
|
8568
|
+
/**
|
|
8569
|
+
* What each release after `from` up to `to` asks of the operator, oldest first, from the
|
|
8570
|
+
* CHANGELOG.md in `packageDir`; none when the package ships no CHANGELOG.
|
|
8571
|
+
*/
|
|
8572
|
+
async function upgradesOf(packageDir, from, to) {
|
|
8573
|
+
let changelog;
|
|
8192
8574
|
try {
|
|
8193
|
-
|
|
8575
|
+
changelog = await readFile(join(packageDir, "CHANGELOG.md"), "utf8");
|
|
8194
8576
|
} catch (error) {
|
|
8195
|
-
if (isMissingFile(error)) return
|
|
8577
|
+
if (isMissingFile(error)) return [];
|
|
8196
8578
|
throw error;
|
|
8197
8579
|
}
|
|
8580
|
+
return releasesIn(changelog).filter(({ version }) => compareVersions(version, from) > 0 && compareVersions(version, to) <= 0).sort((a, b) => compareVersions(a.version, b.version)).flatMap(upgradeOf);
|
|
8198
8581
|
}
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8582
|
+
//#endregion
|
|
8583
|
+
//#region ../agent-cli/dist/built-ins/update.js
|
|
8584
|
+
const updateSchema = object({
|
|
8585
|
+
from: string(),
|
|
8586
|
+
latest: string(),
|
|
8587
|
+
installed: object({
|
|
8588
|
+
version: string(),
|
|
8589
|
+
prefix: string()
|
|
8590
|
+
}).nullable(),
|
|
8591
|
+
upgrades: array(upgradeSchema),
|
|
8592
|
+
next: array(string())
|
|
8593
|
+
});
|
|
8594
|
+
function nothingToInstall({ from, latest }) {
|
|
8595
|
+
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`;
|
|
8206
8596
|
}
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
"EPERM",
|
|
8210
|
-
"ENOTSUP",
|
|
8211
|
-
"EXDEV"
|
|
8212
|
-
]);
|
|
8213
|
-
/** A link fails atomically when `path` is taken, so no reader ever sees half a file there. */
|
|
8214
|
-
async function linkNew(temp, path, data, mode) {
|
|
8215
|
-
try {
|
|
8216
|
-
await link(temp, path);
|
|
8217
|
-
} catch (error) {
|
|
8218
|
-
if (!NO_HARD_LINKS.has(errnoOf(error))) throw error;
|
|
8219
|
-
await createNew(path, data, mode);
|
|
8220
|
-
}
|
|
8597
|
+
function renderUpgrade(upgrade) {
|
|
8598
|
+
return "steps" in upgrade ? [`\nupgrade steps for ${upgrade.version}:`, ...upgrade.steps.map((step) => `- ${step}`)].join("\n") : `\nchanges in ${upgrade.version}:\n${upgrade.notes}`;
|
|
8221
8599
|
}
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
const
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
throw error;
|
|
8232
|
-
}
|
|
8233
|
-
await handle.close();
|
|
8600
|
+
function renderUpdate(cli, install, update) {
|
|
8601
|
+
const { installed } = update;
|
|
8602
|
+
const head = installed === null ? [nothingToInstall(update)] : [`installed ${install.package}@${installed.version} with the npm under ${installed.prefix}, replacing ${update.from}`, ...update.upgrades.map(renderUpgrade)];
|
|
8603
|
+
const next = update.next.map((command) => `next: ${command}, so the ${cli.name} skill describes ${update.latest}`);
|
|
8604
|
+
return (next.length === 0 ? head : [
|
|
8605
|
+
...head,
|
|
8606
|
+
"",
|
|
8607
|
+
...next
|
|
8608
|
+
]).join("\n");
|
|
8234
8609
|
}
|
|
8235
|
-
|
|
8236
|
-
const
|
|
8237
|
-
const
|
|
8238
|
-
|
|
8239
|
-
for (let attempt = 1;; attempt++) try {
|
|
8240
|
-
await rename(from, to);
|
|
8241
|
-
return;
|
|
8242
|
-
} catch (error) {
|
|
8243
|
-
if (!(process.platform === "win32" && HELD_BY_ANOTHER_PROGRAM.has(errnoOf(error))) || attempt === RENAME_ATTEMPTS) throw error;
|
|
8244
|
-
await setTimeout$1(attempt * RENAME_BACKOFF_MS);
|
|
8245
|
-
}
|
|
8610
|
+
async function skillNext(cli, latest, ctx) {
|
|
8611
|
+
const packaged = packagedFolderOf(cli);
|
|
8612
|
+
const finding = packaged === void 0 ? await skillReport(cli, latest, ctx) : await packagedSkillFinding(cli, packaged, ctx);
|
|
8613
|
+
return "fix" in finding && finding.fix !== void 0 ? [finding.fix] : [];
|
|
8246
8614
|
}
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
}
|
|
8279
|
-
},
|
|
8280
|
-
read: (path) => readFile(path),
|
|
8281
|
-
list: (folder) => readdir(folder),
|
|
8282
|
-
remove: (path) => rm(path, {
|
|
8283
|
-
recursive: true,
|
|
8284
|
-
force: true
|
|
8285
|
-
}),
|
|
8286
|
-
mkdir: async (path, options) => {
|
|
8287
|
-
const mode = options?.mode;
|
|
8288
|
-
if (options?.exclusive !== true) {
|
|
8289
|
-
await mkdir(path, {
|
|
8290
|
-
recursive: true,
|
|
8291
|
-
mode
|
|
8292
|
-
});
|
|
8293
|
-
return;
|
|
8294
|
-
}
|
|
8295
|
-
await mkdir(dirname(path), {
|
|
8296
|
-
recursive: true,
|
|
8297
|
-
mode
|
|
8298
|
-
});
|
|
8299
|
-
await mkdir(path, { mode });
|
|
8615
|
+
function interruptedAfterInstall(plan) {
|
|
8616
|
+
return new CliError({
|
|
8617
|
+
code: INTERRUPTED,
|
|
8618
|
+
message: `interrupted, but ${plan.package}@${plan.latest} had already been installed with the npm under ${plan.prefix}, replacing ${plan.from}`,
|
|
8619
|
+
hint: "nothing to run again: run the commands under next"
|
|
8620
|
+
});
|
|
8621
|
+
}
|
|
8622
|
+
/** The built-in `update`, for a CLI installed globally with npm. */
|
|
8623
|
+
function updateCommand(cli, install, { bin, node }) {
|
|
8624
|
+
return defineCommand$1({
|
|
8625
|
+
name: "update",
|
|
8626
|
+
description: "install the latest version of this CLI from its registry",
|
|
8627
|
+
examples: [{
|
|
8628
|
+
command: `${cli.name} update`,
|
|
8629
|
+
note: "install the latest version and show what changed"
|
|
8630
|
+
}, {
|
|
8631
|
+
command: `${cli.name} update --dry-run`,
|
|
8632
|
+
note: "show the version it would install"
|
|
8633
|
+
}],
|
|
8634
|
+
effect: "write",
|
|
8635
|
+
output: updateSchema,
|
|
8636
|
+
plan: async (_input, ctx) => {
|
|
8637
|
+
const npm = globalNpm(cli, install, ctx, node);
|
|
8638
|
+
const copy = await globalInstallOf(bin, npm);
|
|
8639
|
+
return {
|
|
8640
|
+
package: install.package,
|
|
8641
|
+
from: cli.version,
|
|
8642
|
+
latest: await latestVersion(npm),
|
|
8643
|
+
prefix: copy.prefix,
|
|
8644
|
+
packageDir: copy.packageDir
|
|
8645
|
+
};
|
|
8300
8646
|
},
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8647
|
+
apply: async (plan, ctx) => {
|
|
8648
|
+
const { from, latest, prefix } = plan;
|
|
8649
|
+
if (standingOf$1(from, latest) !== "behind") return {
|
|
8650
|
+
from,
|
|
8651
|
+
latest,
|
|
8652
|
+
installed: null,
|
|
8653
|
+
upgrades: [],
|
|
8654
|
+
next: await skillNext(cli, latest, ctx)
|
|
8655
|
+
};
|
|
8656
|
+
ctx.signal.throwIfAborted();
|
|
8657
|
+
ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
|
|
8658
|
+
await ctx.runToEnd(`installing ${plan.package}@${latest}`, () => installGlobally(latest, globalNpm(cli, install, ctx, node)));
|
|
8659
|
+
const update = {
|
|
8660
|
+
from,
|
|
8661
|
+
latest,
|
|
8662
|
+
installed: {
|
|
8663
|
+
version: latest,
|
|
8664
|
+
prefix
|
|
8665
|
+
},
|
|
8666
|
+
upgrades: await upgradesOf(plan.packageDir, from, latest),
|
|
8667
|
+
next: await skillNext(cli, latest, ctx)
|
|
8668
|
+
};
|
|
8669
|
+
if (ctx.signal.aborted) {
|
|
8670
|
+
ctx.deliver(update);
|
|
8671
|
+
throw interruptedAfterInstall(plan);
|
|
8311
8672
|
}
|
|
8312
|
-
|
|
8313
|
-
|
|
8673
|
+
return update;
|
|
8674
|
+
},
|
|
8675
|
+
render: (update) => renderUpdate(cli, install, update),
|
|
8676
|
+
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)
|
|
8677
|
+
});
|
|
8314
8678
|
}
|
|
8679
|
+
//#endregion
|
|
8680
|
+
//#region ../agent-cli/dist/built-ins/commands.js
|
|
8315
8681
|
/**
|
|
8316
|
-
*
|
|
8317
|
-
*
|
|
8682
|
+
* The Commands the Runtime adds to a multi-Command CLI: `update` only for a Global install, and
|
|
8683
|
+
* `skill install` only for a Packaged skill.
|
|
8318
8684
|
*/
|
|
8319
|
-
function
|
|
8320
|
-
|
|
8321
|
-
const target = resolve(cwd, path);
|
|
8322
|
-
try {
|
|
8323
|
-
return await work(target);
|
|
8324
|
-
} catch (error) {
|
|
8325
|
-
throw fileError(error, {
|
|
8326
|
-
path: target,
|
|
8327
|
-
access
|
|
8328
|
-
});
|
|
8329
|
-
}
|
|
8330
|
-
}
|
|
8685
|
+
function builtInCommands(cli, launch) {
|
|
8686
|
+
const packaged = packagedFolderOf(cli);
|
|
8331
8687
|
return {
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
list: (folder) => at("read", folder, async (target) => (await files.list(target)).filter((name) => !TEMP_FILE.test(name)).sort()),
|
|
8336
|
-
remove: (path) => at("write", path, (target) => files.remove(target)),
|
|
8337
|
-
mkdir: (path, options) => at("write", path, (target) => files.mkdir(target, options)),
|
|
8338
|
-
stat: (path) => at("read", path, (target) => files.stat(target))
|
|
8688
|
+
doctor: doctorCommand(cli, launch),
|
|
8689
|
+
...cli.install?.mode === "global" ? { update: updateCommand(cli, cli.install, launch) } : {},
|
|
8690
|
+
...packaged === void 0 ? {} : { skill: { install: skillInstallCommand(cli, packaged) } }
|
|
8339
8691
|
};
|
|
8340
8692
|
}
|
|
8341
8693
|
//#endregion
|
|
8342
|
-
//#region ../agent-cli/dist/
|
|
8343
|
-
const
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
if (
|
|
8347
|
-
const
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
throw new
|
|
8352
|
-
code:
|
|
8353
|
-
message: `\`${commandLine}\`
|
|
8354
|
-
hint
|
|
8694
|
+
//#region ../agent-cli/dist/confirm.js
|
|
8695
|
+
const CONFIRMATION_REQUIRED = "CONFIRMATION_REQUIRED";
|
|
8696
|
+
const CONFIRMATION_MISMATCH = "CONFIRMATION_MISMATCH";
|
|
8697
|
+
function checkConfirmation({ invocation, commandLine, given }, expected) {
|
|
8698
|
+
if (given === expected) return;
|
|
8699
|
+
const hint = `to confirm, run \`${commandLineOf(invocation, {
|
|
8700
|
+
remove: [CONFIRM_FLAG.flag],
|
|
8701
|
+
add: [CONFIRM_FLAG.flag, expected]
|
|
8702
|
+
})}\``;
|
|
8703
|
+
if (given === void 0) throw new UsageError({
|
|
8704
|
+
code: CONFIRMATION_REQUIRED,
|
|
8705
|
+
message: `\`${commandLine}\` is destructive and needs ${CONFIRM_FLAG.flag}`,
|
|
8706
|
+
hint
|
|
8707
|
+
});
|
|
8708
|
+
throw new UsageError({
|
|
8709
|
+
code: CONFIRMATION_MISMATCH,
|
|
8710
|
+
message: `${CONFIRM_FLAG.flag} ${JSON.stringify(given)} does not match the target of \`${commandLine}\``,
|
|
8711
|
+
hint
|
|
8355
8712
|
});
|
|
8356
8713
|
}
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
});
|
|
8369
|
-
} catch (error) {
|
|
8370
|
-
unwritten.add(target);
|
|
8371
|
-
if (!(error instanceof CliError) || error.code !== "ALREADY_EXISTS") throw error;
|
|
8372
|
-
throw new CliError({
|
|
8373
|
-
code: ALREADY_EXISTS,
|
|
8374
|
-
message: error.message,
|
|
8375
|
-
hint: `to replace it, run \`${forceLine}\``
|
|
8376
|
-
}, { cause: error });
|
|
8377
|
-
}
|
|
8378
|
-
};
|
|
8379
|
-
return {
|
|
8380
|
-
files: {
|
|
8381
|
-
...files,
|
|
8382
|
-
write: (path, data, options) => unwritten.has(resolve(cwd, path)) ? exclusiveWrite(path, data, options) : files.write(path, data, options)
|
|
8383
|
-
},
|
|
8384
|
-
guard: (outputs, line) => {
|
|
8385
|
-
forceLine = line;
|
|
8386
|
-
for (const path of outputs) unwritten.add(resolve(cwd, path));
|
|
8387
|
-
}
|
|
8388
|
-
};
|
|
8714
|
+
//#endregion
|
|
8715
|
+
//#region ../agent-cli/dist/cost.js
|
|
8716
|
+
const NOT_CONFIRMED = "NOT_CONFIRMED";
|
|
8717
|
+
/** Refuses a run whose Plan states a cost the caller has not approved with `--yes`. */
|
|
8718
|
+
function checkCost({ invocation, commandLine, yes }, cost) {
|
|
8719
|
+
if (cost === void 0 || yes) return;
|
|
8720
|
+
throw new CliError({
|
|
8721
|
+
code: NOT_CONFIRMED,
|
|
8722
|
+
message: `\`${commandLine}\` needs ${YES_FLAG.flag}: ${cost}`,
|
|
8723
|
+
hint: `to approve the cost, run \`${commandLineOf(invocation, { add: [YES_FLAG.flag] })}\``
|
|
8724
|
+
});
|
|
8389
8725
|
}
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
*/
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
await files.remove(probe);
|
|
8402
|
-
} catch (error) {
|
|
8403
|
-
throw fileError(error, {
|
|
8404
|
-
path: folder,
|
|
8405
|
-
access: "write"
|
|
8406
|
-
});
|
|
8407
|
-
}
|
|
8408
|
-
}));
|
|
8726
|
+
//#endregion
|
|
8727
|
+
//#region ../agent-cli/dist/operator.js
|
|
8728
|
+
const NEEDS_OPERATOR = "NEEDS_OPERATOR";
|
|
8729
|
+
/** Fails with `NEEDS_OPERATOR` when a Coding agent runs an Operator Command. */
|
|
8730
|
+
function checkOperator({ operator, commandLine }, env, callerLine) {
|
|
8731
|
+
if (!operator || !isAgent(env)) return;
|
|
8732
|
+
throw new CliError({
|
|
8733
|
+
code: NEEDS_OPERATOR,
|
|
8734
|
+
message: `\`${commandLine}\` must be run by a person at a terminal, not a Coding agent`,
|
|
8735
|
+
hint: `ask the operator to type: ! ${callerLine}`
|
|
8736
|
+
});
|
|
8409
8737
|
}
|
|
8410
8738
|
//#endregion
|
|
8411
8739
|
//#region ../agent-cli/dist/exit-classes.js
|
|
@@ -8508,7 +8836,10 @@ function checkOwnCodes(cli) {
|
|
|
8508
8836
|
}
|
|
8509
8837
|
function defineCli$1(cli) {
|
|
8510
8838
|
checkOwnCodes(cli);
|
|
8511
|
-
if (cli.commands === void 0)
|
|
8839
|
+
if (cli.commands === void 0) {
|
|
8840
|
+
if (packagedFolderOf(cli) !== void 0) throw new Error(`${cli.name} declares a Packaged skill, which only a CLI with several Commands can install`);
|
|
8841
|
+
return cli;
|
|
8842
|
+
}
|
|
8512
8843
|
const { commands } = cli;
|
|
8513
8844
|
const taken = Object.keys(builtInCommands(cli, {
|
|
8514
8845
|
bin: void 0,
|
|
@@ -8675,7 +9006,7 @@ function examplesSection(examples, style) {
|
|
|
8675
9006
|
}
|
|
8676
9007
|
/** The CLI's own help: a listing or its single Command's help, ending with exit codes and examples. */
|
|
8677
9008
|
function cliHelp(cli, root, style) {
|
|
8678
|
-
return lines(...root.kind === "command" ? commandLines(root, style, { examples: false }) : listingLines(`${
|
|
9009
|
+
return lines(...root.kind === "command" ? commandLines(root, style, { examples: false }) : listingLines(`${cli.name} ${cli.version}`, root, style, cli.description), ...section("Exit codes", exitCodeRows(cli), style), ...examplesSection(rootExamples(root), style));
|
|
8679
9010
|
}
|
|
8680
9011
|
function groupHelp(group, style) {
|
|
8681
9012
|
return lines(...listingLines(group.commandLine, group, style));
|
|
@@ -8773,12 +9104,13 @@ function errorsData(errors) {
|
|
|
8773
9104
|
details: declaration.details === void 0 ? null : toJSONSchema(declaration.details, { unrepresentable: "any" })
|
|
8774
9105
|
}]));
|
|
8775
9106
|
}
|
|
8776
|
-
function cliHelpJson({ version, description, errors }, root) {
|
|
9107
|
+
function cliHelpJson({ name, version, description, errors }, root) {
|
|
8777
9108
|
return printed$1({
|
|
8778
|
-
name
|
|
9109
|
+
name,
|
|
8779
9110
|
version,
|
|
8780
9111
|
description,
|
|
8781
9112
|
...nodeData(root),
|
|
9113
|
+
name,
|
|
8782
9114
|
errors: errorsData(errors ?? {})
|
|
8783
9115
|
});
|
|
8784
9116
|
}
|
|
@@ -8806,7 +9138,14 @@ function printed(value, render, { json, invocation, style, fields }) {
|
|
|
8806
9138
|
}
|
|
8807
9139
|
/** A progress message as stderr carries it: one JSON object per line under `--json` (ADR 0006). */
|
|
8808
9140
|
function progressLine(message, json) {
|
|
8809
|
-
return
|
|
9141
|
+
return stderrLine("progress", message, json);
|
|
9142
|
+
}
|
|
9143
|
+
/** A Notice as stderr carries it, a JSON line like progress under `--json`. */
|
|
9144
|
+
function noticeLine(text, json) {
|
|
9145
|
+
return stderrLine("notice", text, json);
|
|
9146
|
+
}
|
|
9147
|
+
function stderrLine(key, text, json) {
|
|
9148
|
+
return json ? `${JSON.stringify({ [key]: text })}\n` : `${text}\n`;
|
|
8810
9149
|
}
|
|
8811
9150
|
function errorText(error, format) {
|
|
8812
9151
|
const text = formatRuntimeError(error, {
|
|
@@ -8827,7 +9166,7 @@ function startedOutput(outcome, format, spill) {
|
|
|
8827
9166
|
fields: void 0
|
|
8828
9167
|
}, spill);
|
|
8829
9168
|
}
|
|
8830
|
-
/** A serve Command stopped after it started:
|
|
9169
|
+
/** A serve Command stopped, or ended itself, after it started: that is how it is meant to end. */
|
|
8831
9170
|
const STOPPED_RESULT = {
|
|
8832
9171
|
stdout: "",
|
|
8833
9172
|
stderr: "",
|
|
@@ -9053,6 +9392,10 @@ function dryRunOutcome(command, plan) {
|
|
|
9053
9392
|
].join("\n")
|
|
9054
9393
|
};
|
|
9055
9394
|
}
|
|
9395
|
+
function serveContextOf(ctx, end) {
|
|
9396
|
+
const serveCtx = Object.create(ctx);
|
|
9397
|
+
return Object.assign(serveCtx, { end });
|
|
9398
|
+
}
|
|
9056
9399
|
async function execute(command, input, ctx, { dryRun, confirm, yes, page, invocation, commandLine, started, files, guardOutputs }) {
|
|
9057
9400
|
await checkTools(command.tools ?? [], ctx.env, ctx.commandLine());
|
|
9058
9401
|
if (command.rows !== void 0) return outcomeOf(await command.run({
|
|
@@ -9061,8 +9404,14 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, page, invoca
|
|
|
9061
9404
|
}, ctx), (data, style) => command.render(data, style));
|
|
9062
9405
|
if (command.effect === "read") return outcomeOf(await command.run(input, ctx), (data, style) => command.render(data, style));
|
|
9063
9406
|
if (command.effect === "serve") {
|
|
9064
|
-
|
|
9065
|
-
|
|
9407
|
+
let end = () => {};
|
|
9408
|
+
const ended = new Promise((resolve) => {
|
|
9409
|
+
end = resolve;
|
|
9410
|
+
});
|
|
9411
|
+
const outcome = outcomeOf(await command.start(input, serveContextOf(ctx, end)), (value, style) => command.render(value, style));
|
|
9412
|
+
await started(outcome);
|
|
9413
|
+
await Promise.race([ended, untilAborted(ctx.signal)]);
|
|
9414
|
+
return outcome;
|
|
9066
9415
|
}
|
|
9067
9416
|
const plan = await command.plan(input, ctx);
|
|
9068
9417
|
if (dryRun) return dryRunOutcome(command, plan);
|
|
@@ -9404,6 +9753,13 @@ function selfOf(name, bin) {
|
|
|
9404
9753
|
bin
|
|
9405
9754
|
];
|
|
9406
9755
|
}
|
|
9756
|
+
const unfakedSpawnDetached = async () => {
|
|
9757
|
+
throw new Error("runCli was given a fake spawn but no spawnDetached: pass spawnDetached to runCli, so the test starts no real child");
|
|
9758
|
+
};
|
|
9759
|
+
function spawnDetachedOf({ spawn, spawnDetached }) {
|
|
9760
|
+
if (spawnDetached !== void 0) return spawnDetached;
|
|
9761
|
+
return spawn === void 0 ? realSpawnDetached : unfakedSpawnDetached;
|
|
9762
|
+
}
|
|
9407
9763
|
function portsOf(cli, replacement) {
|
|
9408
9764
|
if (replacement !== void 0) return () => replacement;
|
|
9409
9765
|
return cli.ports ?? (() => ({}));
|
|
@@ -9437,11 +9793,19 @@ function portsBaseOf(scope, signal) {
|
|
|
9437
9793
|
}, {
|
|
9438
9794
|
fileError,
|
|
9439
9795
|
callerLine
|
|
9796
|
+
}),
|
|
9797
|
+
spawnDetached: contextSpawnDetached(spawnDetachedOf(options), {
|
|
9798
|
+
cwd,
|
|
9799
|
+
env,
|
|
9800
|
+
signal
|
|
9801
|
+
}, {
|
|
9802
|
+
fileError,
|
|
9803
|
+
callerLine
|
|
9440
9804
|
})
|
|
9441
9805
|
};
|
|
9442
9806
|
}
|
|
9443
9807
|
function contextOf(portsBase, control, parts) {
|
|
9444
|
-
const { ports, project, force, commandLine, deliver } = parts;
|
|
9808
|
+
const { ports, project, force, commandLine, deliver, notice } = parts;
|
|
9445
9809
|
return {
|
|
9446
9810
|
...portsBase,
|
|
9447
9811
|
deadline: (ms) => control.deadline(ms),
|
|
@@ -9454,7 +9818,8 @@ function contextOf(portsBase, control, parts) {
|
|
|
9454
9818
|
force,
|
|
9455
9819
|
commandLine,
|
|
9456
9820
|
poll: (check, { intervalMs, signal: stop }) => poll(portsBase.clock, check, intervalMs, stop === void 0 ? control.signal : AbortSignal.any([control.signal, stop])),
|
|
9457
|
-
deliver
|
|
9821
|
+
deliver,
|
|
9822
|
+
notice
|
|
9458
9823
|
};
|
|
9459
9824
|
}
|
|
9460
9825
|
/** Leaves the Side task running, so the caller writes the result before it waits for it. */
|
|
@@ -9571,6 +9936,7 @@ async function runCommand(cli, routed, options, write, baseFormat) {
|
|
|
9571
9936
|
project,
|
|
9572
9937
|
force: flags.force,
|
|
9573
9938
|
commandLine: typedLine,
|
|
9939
|
+
notice: (text) => write.stderr(noticeLine(text, routed.json)),
|
|
9574
9940
|
deliver: (data) => {
|
|
9575
9941
|
delivered = deliveredOutcome(command, data);
|
|
9576
9942
|
}
|
|
@@ -9586,22 +9952,27 @@ async function runCommand(cli, routed, options, write, baseFormat) {
|
|
|
9586
9952
|
let served = false;
|
|
9587
9953
|
let settled;
|
|
9588
9954
|
try {
|
|
9955
|
+
const outcome = await control.settle(execute(command, input, ctx, {
|
|
9956
|
+
dryRun: flags.dryRun,
|
|
9957
|
+
confirm: flags.confirm,
|
|
9958
|
+
yes: flags.yes,
|
|
9959
|
+
page: flags.page,
|
|
9960
|
+
invocation,
|
|
9961
|
+
commandLine,
|
|
9962
|
+
started: async (outcome) => {
|
|
9963
|
+
write.stdout(await startedOutput(outcome, format, spill));
|
|
9964
|
+
served = true;
|
|
9965
|
+
},
|
|
9966
|
+
files,
|
|
9967
|
+
guardOutputs: outputs.guard
|
|
9968
|
+
}));
|
|
9969
|
+
if (served) return {
|
|
9970
|
+
result: STOPPED_RESULT,
|
|
9971
|
+
finishSideTask
|
|
9972
|
+
};
|
|
9589
9973
|
settled = {
|
|
9590
9974
|
ok: true,
|
|
9591
|
-
outcome
|
|
9592
|
-
dryRun: flags.dryRun,
|
|
9593
|
-
confirm: flags.confirm,
|
|
9594
|
-
yes: flags.yes,
|
|
9595
|
-
page: flags.page,
|
|
9596
|
-
invocation,
|
|
9597
|
-
commandLine,
|
|
9598
|
-
started: async (outcome) => {
|
|
9599
|
-
write.stdout(await startedOutput(outcome, format, spill));
|
|
9600
|
-
served = true;
|
|
9601
|
-
},
|
|
9602
|
-
files,
|
|
9603
|
-
guardOutputs: outputs.guard
|
|
9604
|
-
}))
|
|
9975
|
+
outcome
|
|
9605
9976
|
};
|
|
9606
9977
|
} catch (error) {
|
|
9607
9978
|
if (served && control.signal.aborted) return {
|
|
@@ -9693,12 +10064,13 @@ async function runWithProgress(cli, argv, options, write, stopNotice) {
|
|
|
9693
10064
|
return result;
|
|
9694
10065
|
}
|
|
9695
10066
|
async function routeAndRun(cli, argv, options, write, stopNotice) {
|
|
10067
|
+
const runAs = runAsOf(cli);
|
|
9696
10068
|
const format = {
|
|
9697
10069
|
cliName: cli.name,
|
|
9698
10070
|
env: options.env,
|
|
9699
10071
|
style: styleFor(options.env, options.stdoutIsTTY === true),
|
|
9700
10072
|
invocation: {
|
|
9701
|
-
|
|
10073
|
+
runAs,
|
|
9702
10074
|
argv,
|
|
9703
10075
|
flags: []
|
|
9704
10076
|
},
|
|
@@ -9707,9 +10079,9 @@ async function routeAndRun(cli, argv, options, write, stopNotice) {
|
|
|
9707
10079
|
stop: options.signal
|
|
9708
10080
|
};
|
|
9709
10081
|
const root = cli.commands === void 0 ? {
|
|
9710
|
-
...commandSpec(
|
|
10082
|
+
...commandSpec(runAs, cli.command),
|
|
9711
10083
|
description: cli.description
|
|
9712
|
-
} : groupSpec(
|
|
10084
|
+
} : groupSpec(runAs, cli.commands, builtInCommands(cli, {
|
|
9713
10085
|
bin: options.bin,
|
|
9714
10086
|
node: options.node
|
|
9715
10087
|
}));
|
|
@@ -9776,16 +10148,131 @@ async function runProcess(cli) {
|
|
|
9776
10148
|
}
|
|
9777
10149
|
//#endregion
|
|
9778
10150
|
//#region package.json
|
|
9779
|
-
var version$1 = "0.
|
|
10151
|
+
var version$1 = "0.14.0";
|
|
9780
10152
|
var description = "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.";
|
|
9781
10153
|
//#endregion
|
|
9782
10154
|
//#region src/build.ts
|
|
9783
10155
|
const build = {
|
|
9784
|
-
"sha": "
|
|
10156
|
+
"sha": "f114cf0",
|
|
9785
10157
|
"dirty": false,
|
|
9786
|
-
"builtAt": "2026-10-
|
|
10158
|
+
"builtAt": "2026-10-27T02:06:04.000Z"
|
|
9787
10159
|
};
|
|
9788
10160
|
//#endregion
|
|
10161
|
+
//#region src/fill.ts
|
|
10162
|
+
/** `text` with each `{{key}}` replaced by its value; `quoted` escapes values that land inside a double-quoted string. */
|
|
10163
|
+
function fill(text, values, quoted) {
|
|
10164
|
+
return text.replaceAll(/\{\{(\w+)\}\}/g, (placeholder, key) => {
|
|
10165
|
+
const value = Object.hasOwn(values, key) ? values[key] : void 0;
|
|
10166
|
+
if (value === void 0) throw new Error(`template placeholder ${placeholder} has no value`);
|
|
10167
|
+
return quoted ? JSON.stringify(value).slice(1, -1) : value;
|
|
10168
|
+
});
|
|
10169
|
+
}
|
|
10170
|
+
//#endregion
|
|
10171
|
+
//#region src/install-mode.ts
|
|
10172
|
+
const PARTIALS_DIR = fileURLToPath(new URL("../partials/", import.meta.url));
|
|
10173
|
+
const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
10174
|
+
const COMMANDS_ANCHOR = " commands: {";
|
|
10175
|
+
const GLOBAL_INSTALL = ` install: {
|
|
10176
|
+
mode: "global",
|
|
10177
|
+
package: manifest.name,
|
|
10178
|
+
registry: manifest.publishConfig.registry,
|
|
10179
|
+
},
|
|
10180
|
+
`;
|
|
10181
|
+
const NO_SKILL_HUB = "<!-- Set `skill.source` in `defineCli` and `skillHub` in `scripts/release.ts` to the git URL of the Skill Hub each release commits the skill to, then put here the `! npx -y skills add` line that `node dist/bin.js doctor` gives as the `skill` row's fix. -->";
|
|
10182
|
+
function installModeOf(skillHub, dependency, binName) {
|
|
10183
|
+
if (skillHub !== void 0 && dependency) throw new UsageError({
|
|
10184
|
+
code: "SKILL_HOMES_CONFLICT",
|
|
10185
|
+
message: "--skill-hub and --dependency each name where the skill comes from: a Dependency install ships it inside the package",
|
|
10186
|
+
hint: "pass --dependency for a CLI each project pins, or --skill-hub for a global one whose skill each release commits to a Skill Hub"
|
|
10187
|
+
});
|
|
10188
|
+
if ((skillHub !== void 0 || dependency) && !SKILL_NAME.test(binName)) throw new UsageError({
|
|
10189
|
+
code: "SKILL_NAME_INVALID",
|
|
10190
|
+
message: `${binName} cannot name a skill, whose names are lowercase letters, digits and single hyphens`,
|
|
10191
|
+
hint: `choose a name such as my-cli, or leave out ${dependency ? "--dependency" : "--skill-hub"}`
|
|
10192
|
+
});
|
|
10193
|
+
return dependency ? { mode: "dependency" } : {
|
|
10194
|
+
mode: "global",
|
|
10195
|
+
skillHub
|
|
10196
|
+
};
|
|
10197
|
+
}
|
|
10198
|
+
async function partialsOf(mode, values) {
|
|
10199
|
+
const partial = async (name) => fill(await readFile(join(PARTIALS_DIR, mode, `${name}.md`), "utf8"), values, false).trimEnd();
|
|
10200
|
+
const [installLines, skillCheck, installSteps, updateSteps, sandbox] = await Promise.all([
|
|
10201
|
+
partial("installLines"),
|
|
10202
|
+
partial("skillCheck"),
|
|
10203
|
+
partial("installSteps"),
|
|
10204
|
+
partial("updateSteps"),
|
|
10205
|
+
partial("sandbox")
|
|
10206
|
+
]);
|
|
10207
|
+
return {
|
|
10208
|
+
installLines,
|
|
10209
|
+
skillCheck,
|
|
10210
|
+
installSteps,
|
|
10211
|
+
updateSteps,
|
|
10212
|
+
sandbox
|
|
10213
|
+
};
|
|
10214
|
+
}
|
|
10215
|
+
async function globalSetup(skillHub, packageName, binName) {
|
|
10216
|
+
const skillLine = skillHub === void 0 ? NO_SKILL_HUB : `\`! npx -y skills add ${skillHub} --skill ${binName} -g\``;
|
|
10217
|
+
const runAs = runAsOf({ name: binName });
|
|
10218
|
+
const url = JSON.stringify(skillHub);
|
|
10219
|
+
return {
|
|
10220
|
+
values: {
|
|
10221
|
+
runAs,
|
|
10222
|
+
doctorBin: "",
|
|
10223
|
+
...await partialsOf("global", {
|
|
10224
|
+
packageName,
|
|
10225
|
+
binName,
|
|
10226
|
+
runAs,
|
|
10227
|
+
skillLine
|
|
10228
|
+
})
|
|
10229
|
+
},
|
|
10230
|
+
edits: skillHub === void 0 ? [] : [{
|
|
10231
|
+
file: "src/cli.ts",
|
|
10232
|
+
find: COMMANDS_ANCHOR,
|
|
10233
|
+
replace: ` skill: { source: ${url} },\n${COMMANDS_ANCHOR}`
|
|
10234
|
+
}, {
|
|
10235
|
+
file: "scripts/release.ts",
|
|
10236
|
+
find: " smoke: {",
|
|
10237
|
+
replace: ` skillHub: ${url},\n smoke: {`
|
|
10238
|
+
}],
|
|
10239
|
+
files: []
|
|
10240
|
+
};
|
|
10241
|
+
}
|
|
10242
|
+
async function dependencySetup(packageName, binName) {
|
|
10243
|
+
const runAs = runAsOf({
|
|
10244
|
+
name: binName,
|
|
10245
|
+
install: {
|
|
10246
|
+
mode: "dependency",
|
|
10247
|
+
package: packageName
|
|
10248
|
+
}
|
|
10249
|
+
});
|
|
10250
|
+
return {
|
|
10251
|
+
values: {
|
|
10252
|
+
runAs,
|
|
10253
|
+
doctorBin: "src/bin.ts",
|
|
10254
|
+
...await partialsOf("dependency", {
|
|
10255
|
+
packageName,
|
|
10256
|
+
binName,
|
|
10257
|
+
runAs
|
|
10258
|
+
})
|
|
10259
|
+
},
|
|
10260
|
+
edits: [{
|
|
10261
|
+
file: "src/cli.ts",
|
|
10262
|
+
find: GLOBAL_INSTALL,
|
|
10263
|
+
replace: ` install: { mode: "dependency", package: manifest.name },\n`
|
|
10264
|
+
}, {
|
|
10265
|
+
file: "src/cli.ts",
|
|
10266
|
+
find: COMMANDS_ANCHOR,
|
|
10267
|
+
replace: ` skill: { packaged: "skills/${binName}" },\n${COMMANDS_ANCHOR}`
|
|
10268
|
+
}],
|
|
10269
|
+
files: ["skills"]
|
|
10270
|
+
};
|
|
10271
|
+
}
|
|
10272
|
+
async function installSetupOf(install, packageName, binName) {
|
|
10273
|
+
return install.mode === "global" ? globalSetup(install.skillHub, packageName, binName) : dependencySetup(packageName, binName);
|
|
10274
|
+
}
|
|
10275
|
+
//#endregion
|
|
9789
10276
|
//#region src/steps.ts
|
|
9790
10277
|
const stepErrors = defineErrors({ STEP_FAILED: { class: "operator" } });
|
|
9791
10278
|
const INSTALL = [{
|
|
@@ -9895,13 +10382,6 @@ async function templateFiles(values) {
|
|
|
9895
10382
|
}));
|
|
9896
10383
|
}
|
|
9897
10384
|
const QUOTED_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".ts"]);
|
|
9898
|
-
function fill(text, values, quoted) {
|
|
9899
|
-
return text.replaceAll(/\{\{(\w+)\}\}/g, (placeholder, key) => {
|
|
9900
|
-
if (!Object.hasOwn(values, key)) throw new Error(`template placeholder ${placeholder} has no value`);
|
|
9901
|
-
const value = values[key];
|
|
9902
|
-
return quoted ? JSON.stringify(value).slice(1, -1) : value;
|
|
9903
|
-
});
|
|
9904
|
-
}
|
|
9905
10385
|
async function writeTemplate(root, files, values) {
|
|
9906
10386
|
for (const file of files) {
|
|
9907
10387
|
const text = await readFile(join(TEMPLATE_DIR, file.source), "utf8");
|
|
@@ -9917,17 +10397,6 @@ const version = version$1;
|
|
|
9917
10397
|
//#region src/create.ts
|
|
9918
10398
|
const { defineCommand } = agentCli();
|
|
9919
10399
|
const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?([a-z0-9][a-z0-9._-]*)$/;
|
|
9920
|
-
const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
9921
|
-
const NO_SKILL_HUB = "<!-- Set `skill.source` in `defineCli` and `skillHub` in `scripts/release.ts` to the git URL of the Skill Hub each release commits the skill to, then put here the `! npx -y skills add` line that `node dist/bin.js doctor` gives as the `skill` row's fix. -->";
|
|
9922
|
-
const SKILL_HUB_ANCHORS = [{
|
|
9923
|
-
file: "src/cli.ts",
|
|
9924
|
-
before: " commands: {",
|
|
9925
|
-
line: (hub) => ` skill: { source: ${JSON.stringify(hub)} },\n`
|
|
9926
|
-
}, {
|
|
9927
|
-
file: "scripts/release.ts",
|
|
9928
|
-
before: " smoke: {",
|
|
9929
|
-
line: (hub) => ` skillHub: ${JSON.stringify(hub)},\n`
|
|
9930
|
-
}];
|
|
9931
10400
|
function errorCode(error) {
|
|
9932
10401
|
return error instanceof Error && "code" in error ? error.code : void 0;
|
|
9933
10402
|
}
|
|
@@ -9940,30 +10409,24 @@ async function isFree(path) {
|
|
|
9940
10409
|
throw error;
|
|
9941
10410
|
}
|
|
9942
10411
|
}
|
|
9943
|
-
function
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
const path = join(root, file);
|
|
9949
|
-
const text = await readFile(path, "utf8");
|
|
9950
|
-
if (!text.includes(before)) throw new Error(`the template's ${file} has no \`${before}\` line to put the Skill Hub before`);
|
|
9951
|
-
await writeFile(path, text.replace(before, `${line(skillHub)}${before}`));
|
|
9952
|
-
}
|
|
10412
|
+
async function applyEdit(root, { file, find, replace }) {
|
|
10413
|
+
const path = join(root, file);
|
|
10414
|
+
const text = await readFile(path, "utf8");
|
|
10415
|
+
if (!text.includes(find)) throw new Error(`the template's ${file} has no \`${find.trim()}\` to replace`);
|
|
10416
|
+
await writeFile(path, text.replace(find, replace));
|
|
9953
10417
|
}
|
|
9954
10418
|
const MANIFEST = record(string(), unknown());
|
|
9955
|
-
async function
|
|
10419
|
+
async function editManifest(root, edit) {
|
|
9956
10420
|
const path = join(root, "package.json");
|
|
9957
10421
|
const manifest = MANIFEST.parse(JSON.parse(await readFile(path, "utf8")));
|
|
9958
|
-
const entries = Object.entries(manifest)
|
|
10422
|
+
const entries = edit(Object.entries(manifest));
|
|
9959
10423
|
await writeFile(path, `${JSON.stringify(Object.fromEntries(entries), null, 2)}\n`);
|
|
9960
10424
|
}
|
|
9961
|
-
function
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
});
|
|
10425
|
+
async function setRepository(root, repository) {
|
|
10426
|
+
await editManifest(root, (entries) => entries.flatMap((entry) => entry[0] === "description" ? [entry, ["repository", repository]] : [entry]));
|
|
10427
|
+
}
|
|
10428
|
+
async function addFiles(root, files) {
|
|
10429
|
+
await editManifest(root, (entries) => entries.map(([key, value]) => key === "files" && Array.isArray(value) ? [key, [...value, ...files]] : [key, value]));
|
|
9967
10430
|
}
|
|
9968
10431
|
async function ensureFree(path) {
|
|
9969
10432
|
if (!await isFree(path)) throw new UsageError({
|
|
@@ -9986,20 +10449,22 @@ const create = defineCommand({
|
|
|
9986
10449
|
install: boolean().default(true).describe("run pnpm install"),
|
|
9987
10450
|
git: boolean().default(true).describe("create a git repository with an initial commit"),
|
|
9988
10451
|
repository: url({ protocol: /^https$/ }).optional().describe("the https URL of the CLI's git repository"),
|
|
9989
|
-
skillHub: url({ protocol: /^https$/ }).refine((url) => new URL(url).search === "" && new URL(url).hash === "", "must name the hub's repository with no query or fragment").refine((url) => new URL(url).pathname.endsWith(".git"), "must end in .git").optional().describe("the https git URL of the Skill Hub each release commits the CLI's skill to")
|
|
10452
|
+
skillHub: url({ protocol: /^https$/ }).refine((url) => new URL(url).search === "" && new URL(url).hash === "", "must name the hub's repository with no query or fragment").refine((url) => new URL(url).pathname.endsWith(".git"), "must end in .git").optional().describe("the https git URL of the Skill Hub each release commits the CLI's skill to"),
|
|
10453
|
+
dependency: boolean().default(false).describe("install the CLI per project, as a devDependency, with its skill shipped inside the package")
|
|
9990
10454
|
}),
|
|
9991
|
-
plan: async ({ name, description, install, git, repository, skillHub }, ctx) => {
|
|
10455
|
+
plan: async ({ name, description, install, git, repository, skillHub, dependency }, ctx) => {
|
|
9992
10456
|
const binName = PACKAGE_NAME.exec(name)?.[1] ?? name;
|
|
9993
|
-
|
|
10457
|
+
const mode = installModeOf(skillHub, dependency, binName);
|
|
9994
10458
|
const path = resolve(ctx.cwd, binName);
|
|
9995
10459
|
await ensureFree(path);
|
|
10460
|
+
const setup = await installSetupOf(mode, name, binName);
|
|
9996
10461
|
const values = {
|
|
9997
10462
|
packageName: name,
|
|
9998
10463
|
binName,
|
|
9999
10464
|
description,
|
|
10000
10465
|
debugVariable: debugVariable(binName),
|
|
10001
10466
|
runtimeVersion: version,
|
|
10002
|
-
|
|
10467
|
+
...setup.values
|
|
10003
10468
|
};
|
|
10004
10469
|
return {
|
|
10005
10470
|
path,
|
|
@@ -10011,14 +10476,16 @@ const create = defineCommand({
|
|
|
10011
10476
|
}),
|
|
10012
10477
|
install,
|
|
10013
10478
|
repository,
|
|
10014
|
-
|
|
10479
|
+
edits: setup.edits,
|
|
10480
|
+
manifestFiles: setup.files
|
|
10015
10481
|
};
|
|
10016
10482
|
},
|
|
10017
10483
|
apply: async (plan, ctx) => {
|
|
10018
10484
|
await mkdir(plan.path, { recursive: true });
|
|
10019
10485
|
await writeTemplate(plan.path, plan.files, plan.values);
|
|
10020
10486
|
if (plan.repository !== void 0) await setRepository(plan.path, plan.repository);
|
|
10021
|
-
|
|
10487
|
+
for (const edit of plan.edits) await applyEdit(plan.path, edit);
|
|
10488
|
+
if (plan.manifestFiles.length > 0) await addFiles(plan.path, plan.manifestFiles);
|
|
10022
10489
|
await runSteps(plan.steps, plan.path, ctx);
|
|
10023
10490
|
return {
|
|
10024
10491
|
path: plan.path,
|