@duyquangnvx/create-agent-cli 0.13.1 → 0.15.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 +29 -0
- package/README.md +1 -1
- package/dist/bin.js +1268 -727
- 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
|
}
|
|
@@ -5860,13 +5869,22 @@ function debugTrace(error, format) {
|
|
|
5860
5869
|
function errorsOf(error) {
|
|
5861
5870
|
return error instanceof AggregateError && error.errors.length > 0 ? error.errors.flatMap(errorsOf) : [error];
|
|
5862
5871
|
}
|
|
5872
|
+
/** The `details` field that `error`'s code declares for text mode to print, when it holds a string. */
|
|
5873
|
+
function printedDetail(error, format) {
|
|
5874
|
+
if (!(error instanceof CliError)) return void 0;
|
|
5875
|
+
const key = format.errors[error.code]?.print;
|
|
5876
|
+
const { details } = error;
|
|
5877
|
+
if (key === void 0 || typeof details !== "object" || details === null) return void 0;
|
|
5878
|
+
const value = Reflect.get(details, key);
|
|
5879
|
+
return typeof value === "string" ? value : void 0;
|
|
5880
|
+
}
|
|
5863
5881
|
function errorLines(error, format) {
|
|
5864
5882
|
const fields = fieldsOf$1(error, format);
|
|
5865
|
-
const
|
|
5883
|
+
const blocks = [printedDetail(error, format), debugTrace(error, format)].filter((block) => block !== void 0);
|
|
5866
5884
|
const { style } = format;
|
|
5867
5885
|
const label = style.red(format.isBug(error) ? "internal error:" : "error:");
|
|
5868
|
-
const
|
|
5869
|
-
return `${label} ${fields.message}\n${
|
|
5886
|
+
const between = blocks.map((block) => `${block}\n`).join("");
|
|
5887
|
+
return `${label} ${fields.message}\n${between}${style.cyan("hint:")} ${fields.hint}\n`;
|
|
5870
5888
|
}
|
|
5871
5889
|
function formatRuntimeError(error, format) {
|
|
5872
5890
|
const errors = errorsOf(error);
|
|
@@ -6142,8 +6160,8 @@ const SHELL_SAFE = /^[\w@%+=:,./-]+$/;
|
|
|
6142
6160
|
function quoted$1(token) {
|
|
6143
6161
|
return SHELL_SAFE.test(token) ? token : `'${token.replaceAll("'", `'\\''`)}'`;
|
|
6144
6162
|
}
|
|
6145
|
-
function pasteable(
|
|
6146
|
-
const line = [
|
|
6163
|
+
function pasteable(runAs, tokens, stdinArg) {
|
|
6164
|
+
const line = [runAs, ...tokens.map(quoted$1)].join(" ");
|
|
6147
6165
|
return stdinArg === void 0 ? line : `${line} < <${stdinArg} file>`;
|
|
6148
6166
|
}
|
|
6149
6167
|
function split(argv) {
|
|
@@ -6173,10 +6191,10 @@ function without(flag, takes, tokens) {
|
|
|
6173
6191
|
* The caller's command line as typed, ready to paste, with each flag in `remove` dropped along with
|
|
6174
6192
|
* its value, the words in `add` placed before any `--`, and the stdin arg's file redirected in.
|
|
6175
6193
|
*/
|
|
6176
|
-
function commandLineOf({
|
|
6194
|
+
function commandLineOf({ runAs, argv, flags, stdinArg }, { add = [], remove = [] } = {}) {
|
|
6177
6195
|
const { options, operands } = split(argv);
|
|
6178
6196
|
const takes = new Map(flags.map((known) => [known.flag, known.takes]));
|
|
6179
|
-
return pasteable(
|
|
6197
|
+
return pasteable(runAs, [
|
|
6180
6198
|
...remove.reduce((tokens, flag) => without(flag, takes.get(flag) ?? "nothing", tokens), options),
|
|
6181
6199
|
...add,
|
|
6182
6200
|
...operands
|
|
@@ -6189,9 +6207,9 @@ function renamed(token, renames) {
|
|
|
6189
6207
|
return value.length === 0 ? name : `${name}=${value.join("=")}`;
|
|
6190
6208
|
}
|
|
6191
6209
|
/** The caller's command line, ready to paste, with each flag in `renames` typed as its new name. */
|
|
6192
|
-
function commandLineRenaming({
|
|
6210
|
+
function commandLineRenaming({ runAs, argv, stdinArg }, renames) {
|
|
6193
6211
|
const { options, operands } = split(argv);
|
|
6194
|
-
return pasteable(
|
|
6212
|
+
return pasteable(runAs, [...options.map((token) => renamed(token, renames)), ...operands], stdinArg);
|
|
6195
6213
|
}
|
|
6196
6214
|
//#endregion
|
|
6197
6215
|
//#region ../agent-cli/dist/usage.js
|
|
@@ -6478,6 +6496,15 @@ function isCommand(node) {
|
|
|
6478
6496
|
return typeof node.effect === "string";
|
|
6479
6497
|
}
|
|
6480
6498
|
//#endregion
|
|
6499
|
+
//#region ../agent-cli/dist/run-as.js
|
|
6500
|
+
/**
|
|
6501
|
+
* The Run-as: how every command line the Runtime gives its caller starts. A Dependency install runs
|
|
6502
|
+
* the copy the project pins, so it is `npx -q --no-install <bin>`; otherwise the bare name (ADR 0011).
|
|
6503
|
+
*/
|
|
6504
|
+
function runAsOf(cli) {
|
|
6505
|
+
return cli.install?.mode === "dependency" ? `npx -q --no-install ${cli.name}` : cli.name;
|
|
6506
|
+
}
|
|
6507
|
+
//#endregion
|
|
6481
6508
|
//#region ../agent-cli/dist/project.js
|
|
6482
6509
|
const PROJECT_INVALID = "PROJECT_INVALID";
|
|
6483
6510
|
function invalid(path, problem, callerLine, options) {
|
|
@@ -6717,11 +6744,7 @@ async function feed(stdin, input, signal) {
|
|
|
6717
6744
|
*/
|
|
6718
6745
|
async function spawnChild(command, args, { cwd, env, signal, onLine, stdin, detached = false }) {
|
|
6719
6746
|
signal.throwIfAborted();
|
|
6720
|
-
const launch =
|
|
6721
|
-
command,
|
|
6722
|
-
args,
|
|
6723
|
-
shell: false
|
|
6724
|
-
};
|
|
6747
|
+
const launch = await launchOf(command, args, env);
|
|
6725
6748
|
signal.throwIfAborted();
|
|
6726
6749
|
return new Promise((resolve, reject) => {
|
|
6727
6750
|
const child = spawn(launch.command, launch.args, {
|
|
@@ -6786,6 +6809,38 @@ async function spawnChild(command, args, { cwd, env, signal, onLine, stdin, deta
|
|
|
6786
6809
|
});
|
|
6787
6810
|
});
|
|
6788
6811
|
}
|
|
6812
|
+
async function launchOf(command, args, env) {
|
|
6813
|
+
return process.platform === "win32" ? windowsLaunch(command, args, env) : {
|
|
6814
|
+
command,
|
|
6815
|
+
args,
|
|
6816
|
+
shell: false
|
|
6817
|
+
};
|
|
6818
|
+
}
|
|
6819
|
+
/**
|
|
6820
|
+
* Starts a child in its own process group (its own console on Windows) with no stdio, and settles
|
|
6821
|
+
* once it has started; the parent neither waits for it nor keeps it from exiting.
|
|
6822
|
+
*/
|
|
6823
|
+
const realSpawnDetached = async (command, args, { cwd, env }) => {
|
|
6824
|
+
const launch = await launchOf(command, args, env);
|
|
6825
|
+
return new Promise((resolve, reject) => {
|
|
6826
|
+
const child = spawn(launch.command, launch.args, {
|
|
6827
|
+
cwd,
|
|
6828
|
+
env,
|
|
6829
|
+
shell: launch.shell,
|
|
6830
|
+
windowsHide: true,
|
|
6831
|
+
stdio: "ignore",
|
|
6832
|
+
detached: true
|
|
6833
|
+
});
|
|
6834
|
+
child.unref();
|
|
6835
|
+
child.once("error", reject);
|
|
6836
|
+
child.once("spawn", () => {
|
|
6837
|
+
child.off("error", reject);
|
|
6838
|
+
const { pid } = child;
|
|
6839
|
+
if (pid === void 0) reject(/* @__PURE__ */ new Error(`${command} started without a pid`));
|
|
6840
|
+
else resolve({ pid });
|
|
6841
|
+
});
|
|
6842
|
+
});
|
|
6843
|
+
};
|
|
6789
6844
|
const realSpawn = (command, args, options) => spawnChild(command, args, options);
|
|
6790
6845
|
function failedError(command, { exitCode, stderr }) {
|
|
6791
6846
|
const tail = lastLines(stderr, STDERR_TAIL_LINES$1);
|
|
@@ -6855,6 +6910,20 @@ function contextSpawn(run, defaults, scope) {
|
|
|
6855
6910
|
return result;
|
|
6856
6911
|
};
|
|
6857
6912
|
}
|
|
6913
|
+
/** `ctx.spawnDetached`: `run` bound to the run's cwd and env, its start failures classified by errno. */
|
|
6914
|
+
function contextSpawnDetached(run, defaults, scope) {
|
|
6915
|
+
return async (command, args = [], options = {}) => {
|
|
6916
|
+
defaults.signal.throwIfAborted();
|
|
6917
|
+
try {
|
|
6918
|
+
return await run(command, args, {
|
|
6919
|
+
cwd: options.cwd ?? defaults.cwd,
|
|
6920
|
+
env: options.env ?? defaults.env
|
|
6921
|
+
});
|
|
6922
|
+
} catch (error) {
|
|
6923
|
+
throw startError(command, error, scope);
|
|
6924
|
+
}
|
|
6925
|
+
};
|
|
6926
|
+
}
|
|
6858
6927
|
//#endregion
|
|
6859
6928
|
//#region ../agent-cli/dist/built-ins/semver.js
|
|
6860
6929
|
const VERSION = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
@@ -7177,17 +7246,19 @@ const LINES = {
|
|
|
7177
7246
|
pnpm: {
|
|
7178
7247
|
upgrade: (packages) => `pnpm up --latest ${packages.join(" ")}`,
|
|
7179
7248
|
add: (packages) => `pnpm add -D ${packages.join(" ")}`,
|
|
7180
|
-
|
|
7181
|
-
exec: (cli) => `pnpm exec ${cli}`
|
|
7249
|
+
install: "pnpm install"
|
|
7182
7250
|
},
|
|
7183
7251
|
npm: {
|
|
7184
7252
|
upgrade: (packages) => `npm install ${packages.map((name) => `${name}@latest`).join(" ")}`,
|
|
7185
7253
|
add: (packages) => `npm install -D ${packages.join(" ")}`,
|
|
7186
|
-
|
|
7187
|
-
exec: (cli) => `npx ${cli}`
|
|
7254
|
+
install: "npm install"
|
|
7188
7255
|
}
|
|
7189
7256
|
};
|
|
7190
|
-
const manifestSchema$1 = object({
|
|
7257
|
+
const manifestSchema$1 = object({
|
|
7258
|
+
packageManager: string().optional(),
|
|
7259
|
+
dependencies: record(string(), unknown()).optional(),
|
|
7260
|
+
devDependencies: record(string(), unknown()).optional()
|
|
7261
|
+
});
|
|
7191
7262
|
const packageSchema = object({ version: string() });
|
|
7192
7263
|
/** `dir` and every folder above it, nearest first. */
|
|
7193
7264
|
function ancestorsOf(dir) {
|
|
@@ -7203,7 +7274,7 @@ async function exists(path) {
|
|
|
7203
7274
|
throw error;
|
|
7204
7275
|
}
|
|
7205
7276
|
}
|
|
7206
|
-
async function
|
|
7277
|
+
async function manifestIn(dir) {
|
|
7207
7278
|
let text;
|
|
7208
7279
|
try {
|
|
7209
7280
|
text = await readFile(join(dir, "package.json"), "utf8");
|
|
@@ -7217,15 +7288,30 @@ async function declaredManager(dir) {
|
|
|
7217
7288
|
} catch {
|
|
7218
7289
|
return;
|
|
7219
7290
|
}
|
|
7220
|
-
|
|
7291
|
+
return manifestSchema$1.safeParse(manifest).data;
|
|
7292
|
+
}
|
|
7293
|
+
async function declaredManager(dir) {
|
|
7294
|
+
const name = (await manifestIn(dir))?.packageManager?.split("@")[0];
|
|
7221
7295
|
return MANAGERS.find((manager) => manager === name);
|
|
7222
7296
|
}
|
|
7297
|
+
async function manifestListing(name, cwd, root) {
|
|
7298
|
+
const dirs = ancestorsOf(cwd);
|
|
7299
|
+
const top = root === void 0 ? dirs.length : dirs.indexOf(root.dir) + 1;
|
|
7300
|
+
for (const dir of dirs.slice(0, top)) {
|
|
7301
|
+
const manifest = await manifestIn(dir);
|
|
7302
|
+
if (manifest?.dependencies?.[name] !== void 0 || manifest?.devDependencies?.[name] !== void 0) return join(dir, "package.json");
|
|
7303
|
+
}
|
|
7304
|
+
}
|
|
7223
7305
|
async function lockfileRootOf(cwd) {
|
|
7224
7306
|
for (const dir of ancestorsOf(cwd)) for (const [file, kind] of LOCKFILES) if (await exists(join(dir, file))) return {
|
|
7225
7307
|
dir,
|
|
7226
7308
|
manager: await declaredManager(dir) ?? kind
|
|
7227
7309
|
};
|
|
7228
7310
|
}
|
|
7311
|
+
/** The package manager's project root: the folder of the nearest lockfile, or `cwd` without one. */
|
|
7312
|
+
async function projectRootOf(cwd) {
|
|
7313
|
+
return (await lockfileRootOf(cwd))?.dir ?? cwd;
|
|
7314
|
+
}
|
|
7229
7315
|
async function versionIn(manifest) {
|
|
7230
7316
|
try {
|
|
7231
7317
|
return packageSchema.parse(JSON.parse(await readFile(manifest, "utf8"))).version;
|
|
@@ -7234,7 +7320,7 @@ async function versionIn(manifest) {
|
|
|
7234
7320
|
throw error;
|
|
7235
7321
|
}
|
|
7236
7322
|
}
|
|
7237
|
-
async function standingOf(install, bin, cwd) {
|
|
7323
|
+
async function standingOf(install, bin, cwd, root) {
|
|
7238
7324
|
if (bin === void 0) return { kind: "source" };
|
|
7239
7325
|
for (const projectDir of ancestorsOf(cwd)) {
|
|
7240
7326
|
const folder = join(projectDir, "node_modules", install.package);
|
|
@@ -7268,14 +7354,18 @@ async function standingOf(install, bin, cwd) {
|
|
|
7268
7354
|
runningFrom
|
|
7269
7355
|
};
|
|
7270
7356
|
}
|
|
7271
|
-
|
|
7357
|
+
const manifest = await manifestListing(install.package, cwd, root);
|
|
7358
|
+
return manifest === void 0 ? { kind: "missing" } : {
|
|
7359
|
+
kind: "listed",
|
|
7360
|
+
manifest
|
|
7361
|
+
};
|
|
7272
7362
|
}
|
|
7273
7363
|
async function surveyDependency(install, bin, cwd) {
|
|
7274
|
-
const
|
|
7364
|
+
const root = await lockfileRootOf(cwd);
|
|
7275
7365
|
return {
|
|
7276
7366
|
cwd,
|
|
7277
7367
|
root,
|
|
7278
|
-
standing
|
|
7368
|
+
standing: await standingOf(install, bin, cwd, root)
|
|
7279
7369
|
};
|
|
7280
7370
|
}
|
|
7281
7371
|
function packagesOf(install) {
|
|
@@ -7290,6 +7380,10 @@ function skippedFor(cli, install, standing) {
|
|
|
7290
7380
|
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
7381
|
};
|
|
7292
7382
|
}
|
|
7383
|
+
function searchedFrom(cwd, root) {
|
|
7384
|
+
if (root === void 0) return `at or above ${cwd}`;
|
|
7385
|
+
return root.dir === cwd ? `at ${cwd}` : `from ${cwd} up to ${root.dir}`;
|
|
7386
|
+
}
|
|
7293
7387
|
function problem(message, fix) {
|
|
7294
7388
|
return fix === void 0 ? {
|
|
7295
7389
|
state: "problem",
|
|
@@ -7306,11 +7400,12 @@ function dependencyInstallFinding(cli, install, { cwd, root, standing }) {
|
|
|
7306
7400
|
switch (standing.kind) {
|
|
7307
7401
|
case "source":
|
|
7308
7402
|
case "linked": return skippedFor(cli, install, standing);
|
|
7309
|
-
case "missing": return problem(`no package
|
|
7310
|
-
case "
|
|
7403
|
+
case "missing": return problem(`no package ${searchedFrom(cwd, root)} depends on ${install.package}`, lines?.add(packagesOf(install)));
|
|
7404
|
+
case "listed": return problem(`${standing.manifest} lists ${install.package}, but no copy of it is installed at or above ${cwd}`, lines?.install);
|
|
7405
|
+
case "unreadable": return problem(`${standing.manifest} is not a package.json with a version: ${standing.reason}`, lines?.install);
|
|
7311
7406
|
case "other": {
|
|
7312
7407
|
const { copy, runningFrom } = standing;
|
|
7313
|
-
return problem(`${cli.name} runs from ${runningFrom}, not from this project's ${install.package} ${copy.version} at ${copy.realDir}`,
|
|
7408
|
+
return problem(`${cli.name} runs from ${runningFrom}, not from this project's ${install.package} ${copy.version} at ${copy.realDir}`, `${runAsOf(cli)} doctor`);
|
|
7314
7409
|
}
|
|
7315
7410
|
case "installed": {
|
|
7316
7411
|
const { copy } = standing;
|
|
@@ -7349,6 +7444,87 @@ function dependencyRelease(cli, install, { cwd, root, standing }) {
|
|
|
7349
7444
|
};
|
|
7350
7445
|
}
|
|
7351
7446
|
//#endregion
|
|
7447
|
+
//#region ../agent-cli/dist/built-ins/agents-md.js
|
|
7448
|
+
/** One paragraph telling every session that the CLI exists, how to run it and which skill to load. */
|
|
7449
|
+
function agentsBlock(cli) {
|
|
7450
|
+
const { install } = cli;
|
|
7451
|
+
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` : "";
|
|
7452
|
+
const what = cli.description.replace(/\.$/, "");
|
|
7453
|
+
return `\`${cli.name}\`: ${what}. Run it as \`${runAsOf(cli)}\`${where}. Load the \`${cli.name}\` skill before using it.`;
|
|
7454
|
+
}
|
|
7455
|
+
function markersOf(name) {
|
|
7456
|
+
return {
|
|
7457
|
+
begin: `<!-- BEGIN:${name} -->`,
|
|
7458
|
+
end: `<!-- END:${name} -->`
|
|
7459
|
+
};
|
|
7460
|
+
}
|
|
7461
|
+
/** `text` with the CLI's block between its markers, appended when they are missing; nothing else changes. */
|
|
7462
|
+
function withBlock(cli, text) {
|
|
7463
|
+
const { begin, end } = markersOf(cli.name);
|
|
7464
|
+
const whole = `${begin}\n${agentsBlock(cli)}\n${end}`;
|
|
7465
|
+
const start = text.indexOf(begin);
|
|
7466
|
+
const stop = start === -1 ? -1 : text.indexOf(end, start);
|
|
7467
|
+
if (stop !== -1) return `${text.slice(0, start)}${whole}${text.slice(stop + end.length)}`;
|
|
7468
|
+
if (text === "") return `${whole}\n`;
|
|
7469
|
+
return `${text}${text.endsWith("\n") ? "" : "\n"}\n${whole}\n`;
|
|
7470
|
+
}
|
|
7471
|
+
async function agentsMdAt(cli, root) {
|
|
7472
|
+
const path = join(root, "AGENTS.md");
|
|
7473
|
+
const existing = await orMissing(readFile(path, "utf8"));
|
|
7474
|
+
const block = agentsBlock(cli);
|
|
7475
|
+
if (existing === void 0) return {
|
|
7476
|
+
path,
|
|
7477
|
+
block,
|
|
7478
|
+
change: "create"
|
|
7479
|
+
};
|
|
7480
|
+
if (withBlock(cli, existing) === existing) return {
|
|
7481
|
+
path,
|
|
7482
|
+
block,
|
|
7483
|
+
change: null
|
|
7484
|
+
};
|
|
7485
|
+
return {
|
|
7486
|
+
path,
|
|
7487
|
+
block,
|
|
7488
|
+
change: existing.includes(markersOf(cli.name).begin) ? "rewrite" : "add"
|
|
7489
|
+
};
|
|
7490
|
+
}
|
|
7491
|
+
async function handEditsAt(root) {
|
|
7492
|
+
const claudeMd = join(root, "CLAUDE.md");
|
|
7493
|
+
const text = await orMissing(readFile(claudeMd, "utf8"));
|
|
7494
|
+
if (text === void 0 || text.split(/\r?\n/).some((line) => line.trim() === "@AGENTS.md")) return [];
|
|
7495
|
+
return [`add a line \`@AGENTS.md\` to ${claudeMd}, so Claude Code reads AGENTS.md`];
|
|
7496
|
+
}
|
|
7497
|
+
function agentsRow(cli) {
|
|
7498
|
+
return {
|
|
7499
|
+
name: "agents",
|
|
7500
|
+
advisory: true,
|
|
7501
|
+
run: async (ctx) => {
|
|
7502
|
+
const root = await projectRootOf(ctx.cwd);
|
|
7503
|
+
const { path, change } = await agentsMdAt(cli, root);
|
|
7504
|
+
const fix = `${runAsOf(cli)} skill install`;
|
|
7505
|
+
if (change === "rewrite") return {
|
|
7506
|
+
state: "problem",
|
|
7507
|
+
message: `${path} holds a ${cli.name} block other than the one ${fix} writes`,
|
|
7508
|
+
fix
|
|
7509
|
+
};
|
|
7510
|
+
if (change !== null) return {
|
|
7511
|
+
state: "problem",
|
|
7512
|
+
message: `${path} holds no ${cli.name} block, so sessions in this repo do not know ${cli.name}`,
|
|
7513
|
+
fix
|
|
7514
|
+
};
|
|
7515
|
+
const holds = `${path} holds the ${cli.name} block`;
|
|
7516
|
+
const [edit] = await handEditsAt(root);
|
|
7517
|
+
return edit === void 0 ? {
|
|
7518
|
+
state: "ok",
|
|
7519
|
+
message: holds
|
|
7520
|
+
} : {
|
|
7521
|
+
state: "problem",
|
|
7522
|
+
message: `${holds}; ${edit}`
|
|
7523
|
+
};
|
|
7524
|
+
}
|
|
7525
|
+
};
|
|
7526
|
+
}
|
|
7527
|
+
//#endregion
|
|
7352
7528
|
//#region ../agent-cli/dist/built-ins/skills.js
|
|
7353
7529
|
const SKILL_DIRS = [[".claude", "skills"], [".agents", "skills"]];
|
|
7354
7530
|
const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
@@ -7409,11 +7585,111 @@ function fixFor(cliName, copies, source) {
|
|
|
7409
7585
|
}
|
|
7410
7586
|
async function skillReport(cli, version, ctx) {
|
|
7411
7587
|
const copies = await skillCopies(cli.name, version, ctx);
|
|
7588
|
+
const source = cli.skill !== void 0 && "source" in cli.skill ? cli.skill.source : void 0;
|
|
7412
7589
|
return {
|
|
7413
7590
|
copies,
|
|
7414
|
-
fix: fixFor(cli.name, copies,
|
|
7591
|
+
fix: fixFor(cli.name, copies, source)
|
|
7592
|
+
};
|
|
7593
|
+
}
|
|
7594
|
+
//#endregion
|
|
7595
|
+
//#region ../agent-cli/dist/built-ins/packaged-skill.js
|
|
7596
|
+
/** The folder, relative to the package root, of a CLI's Packaged skill, if it declares one. */
|
|
7597
|
+
function packagedFolderOf(cli) {
|
|
7598
|
+
return cli.skill !== void 0 && "packaged" in cli.skill ? cli.skill.packaged : void 0;
|
|
7599
|
+
}
|
|
7600
|
+
/** The project root and the Skill copy under it, as `skill install` writes it. */
|
|
7601
|
+
async function skillCopyOf(cli, cwd) {
|
|
7602
|
+
const root = await projectRootOf(cwd);
|
|
7603
|
+
return {
|
|
7604
|
+
root,
|
|
7605
|
+
copy: join(root, ".claude", "skills", cli.name)
|
|
7606
|
+
};
|
|
7607
|
+
}
|
|
7608
|
+
/** Every file under `dir`, relative to it and sorted; none when `dir` is missing. */
|
|
7609
|
+
async function filesIn(dir) {
|
|
7610
|
+
return (await orMissing(readdir(dir, {
|
|
7611
|
+
recursive: true,
|
|
7612
|
+
withFileTypes: true
|
|
7613
|
+
})) ?? []).filter((entry) => entry.isFile()).map((entry) => relative(dir, join(entry.parentPath, entry.name))).sort();
|
|
7614
|
+
}
|
|
7615
|
+
/** The versions of the package's skill in `source` and of its copy in `copy`. */
|
|
7616
|
+
async function skillVersionsOf(source, copy) {
|
|
7617
|
+
const [shipped, installed] = await Promise.all([readFile(join(source, "SKILL.md"), "utf8"), orMissing(readFile(join(copy, "SKILL.md"), "utf8"))]);
|
|
7618
|
+
const packageVersion = skillVersionIn(shipped) ?? null;
|
|
7619
|
+
const copyVersion = installed === void 0 ? void 0 : skillVersionIn(installed) ?? null;
|
|
7620
|
+
return {
|
|
7621
|
+
packageVersion,
|
|
7622
|
+
copyVersion,
|
|
7623
|
+
same: copyVersion === packageVersion
|
|
7624
|
+
};
|
|
7625
|
+
}
|
|
7626
|
+
async function personalCopyOf(name, copy, ctx) {
|
|
7627
|
+
const home = join(homeOf(ctx.env), ".claude", "skills", name);
|
|
7628
|
+
const real = await orMissing(realpath(home));
|
|
7629
|
+
return real === void 0 || real === await orMissing(realpath(copy)) ? void 0 : home;
|
|
7630
|
+
}
|
|
7631
|
+
/** What the `skill` row of a Packaged skill carries under `--json` beside its message. */
|
|
7632
|
+
const packagedSkillDetailsSchema = object({
|
|
7633
|
+
copy: string(),
|
|
7634
|
+
copyVersion: string().nullable(),
|
|
7635
|
+
packageVersion: string().nullable(),
|
|
7636
|
+
/** A personal copy, which Claude Code prefers over the project's. */
|
|
7637
|
+
home: string().optional()
|
|
7638
|
+
});
|
|
7639
|
+
function versionLabel(version) {
|
|
7640
|
+
return version ?? "(no metadata.version)";
|
|
7641
|
+
}
|
|
7642
|
+
function compared(name, { copy, copyVersion, packageVersion }, same) {
|
|
7643
|
+
const stated = `${copy} ${versionLabel(copyVersion)}`;
|
|
7644
|
+
return same ? `${stated}, the version ${name} ships` : `${stated} differs from the ${versionLabel(packageVersion)} that ${name} ships`;
|
|
7645
|
+
}
|
|
7646
|
+
/** The `skill` row of a Packaged skill: the project root's copy against the package's, and any under home. */
|
|
7647
|
+
async function packagedSkillFinding(cli, folder, ctx) {
|
|
7648
|
+
const { copy } = await skillCopyOf(cli, ctx.cwd);
|
|
7649
|
+
const versions = await skillVersionsOf(join(ctx.packageRoot, folder), copy);
|
|
7650
|
+
const { packageVersion } = versions;
|
|
7651
|
+
const copyVersion = versions.copyVersion ?? null;
|
|
7652
|
+
const home = await personalCopyOf(cli.name, copy, ctx);
|
|
7653
|
+
const skill = {
|
|
7654
|
+
copy,
|
|
7655
|
+
copyVersion,
|
|
7656
|
+
packageVersion,
|
|
7657
|
+
...home === void 0 ? {} : { home }
|
|
7658
|
+
};
|
|
7659
|
+
const head = versions.copyVersion === void 0 ? `no copy of the ${cli.name} skill at ${copy}` : compared(cli.name, skill, versions.same);
|
|
7660
|
+
const message = home === void 0 ? head : `${head}; remove ${home}: Claude Code prefers a personal skill over the project's`;
|
|
7661
|
+
if (!versions.same) return {
|
|
7662
|
+
state: "problem",
|
|
7663
|
+
message,
|
|
7664
|
+
fix: `${runAsOf(cli)} skill install`,
|
|
7665
|
+
skill
|
|
7666
|
+
};
|
|
7667
|
+
return home === void 0 ? {
|
|
7668
|
+
state: "ok",
|
|
7669
|
+
message,
|
|
7670
|
+
skill
|
|
7671
|
+
} : {
|
|
7672
|
+
state: "problem",
|
|
7673
|
+
message,
|
|
7674
|
+
skill
|
|
7415
7675
|
};
|
|
7416
7676
|
}
|
|
7677
|
+
/** Gives a Packaged skill's `skill` row problem as a Notice, since an agent rarely runs `doctor`. */
|
|
7678
|
+
async function noticeSkillCopy(cli, { builtIn, bin }, ctx) {
|
|
7679
|
+
if (builtIn || bin === void 0 || cli.commands === void 0) return;
|
|
7680
|
+
const folder = packagedFolderOf(cli);
|
|
7681
|
+
if (folder === void 0) return;
|
|
7682
|
+
let finding;
|
|
7683
|
+
try {
|
|
7684
|
+
finding = await packagedSkillFinding(cli, folder, ctx);
|
|
7685
|
+
} catch (error) {
|
|
7686
|
+
if (errnoOf(error) === void 0) throw error;
|
|
7687
|
+
ctx.notice(`could not check the ${cli.name} skill: ${messageOf(error)}`);
|
|
7688
|
+
return;
|
|
7689
|
+
}
|
|
7690
|
+
if (finding.state !== "problem") return;
|
|
7691
|
+
ctx.notice(finding.fix === void 0 ? finding.message : `${finding.message}; run \`${finding.fix}\``);
|
|
7692
|
+
}
|
|
7417
7693
|
//#endregion
|
|
7418
7694
|
//#region ../agent-cli/dist/built-ins/doctor-rows.js
|
|
7419
7695
|
function ok(message) {
|
|
@@ -7581,9 +7857,12 @@ function describeCopy({ path, version, state }) {
|
|
|
7581
7857
|
return `${path}${version === void 0 ? "" : ` ${version}`} (${SKILL_STATE_NOTES[state]})`;
|
|
7582
7858
|
}
|
|
7583
7859
|
function skillRow(cli) {
|
|
7860
|
+
const packaged = packagedFolderOf(cli);
|
|
7584
7861
|
return {
|
|
7585
7862
|
name: "skill",
|
|
7863
|
+
advisory: true,
|
|
7586
7864
|
run: async (ctx) => {
|
|
7865
|
+
if (packaged !== void 0) return packagedSkillFinding(cli, packaged, ctx);
|
|
7587
7866
|
const { copies, fix } = await skillReport(cli, cli.version, ctx);
|
|
7588
7867
|
const message = copies.length === 0 ? `no copy of the ${cli.name} skill is installed` : copies.map(describeCopy).join("; ");
|
|
7589
7868
|
return fix === void 0 ? ok(message) : {
|
|
@@ -7605,7 +7884,8 @@ function doctorChecks(cli, launch, read) {
|
|
|
7605
7884
|
...toolRows(cli.tools ?? [], cli.commands),
|
|
7606
7885
|
installRow(cli, launch, survey),
|
|
7607
7886
|
registryRow(cli, launch, survey),
|
|
7608
|
-
skillRow(cli)
|
|
7887
|
+
skillRow(cli),
|
|
7888
|
+
...packagedFolderOf(cli) === void 0 ? [] : [agentsRow(cli)]
|
|
7609
7889
|
];
|
|
7610
7890
|
}
|
|
7611
7891
|
//#endregion
|
|
@@ -7624,7 +7904,8 @@ const reportSchema = object({
|
|
|
7624
7904
|
]),
|
|
7625
7905
|
message: string(),
|
|
7626
7906
|
fix: string().optional(),
|
|
7627
|
-
code: string().optional()
|
|
7907
|
+
code: string().optional(),
|
|
7908
|
+
skill: packagedSkillDetailsSchema.optional()
|
|
7628
7909
|
})),
|
|
7629
7910
|
next: array(string())
|
|
7630
7911
|
});
|
|
@@ -7694,10 +7975,10 @@ function doctorCommand(cli, launch) {
|
|
|
7694
7975
|
name: "doctor",
|
|
7695
7976
|
description: "check the setup and say what fixes each problem",
|
|
7696
7977
|
examples: [{
|
|
7697
|
-
command: `${cli
|
|
7978
|
+
command: `${runAsOf(cli)} doctor`,
|
|
7698
7979
|
note: "check the setup and list the commands that fix it"
|
|
7699
7980
|
}, {
|
|
7700
|
-
command: `${cli
|
|
7981
|
+
command: `${runAsOf(cli)} doctor --json`,
|
|
7701
7982
|
note: "get the rows and fixes as data"
|
|
7702
7983
|
}],
|
|
7703
7984
|
effect: "read",
|
|
@@ -7725,687 +8006,802 @@ function doctorCommand(cli, launch) {
|
|
|
7725
8006
|
});
|
|
7726
8007
|
}
|
|
7727
8008
|
//#endregion
|
|
7728
|
-
//#region ../agent-cli/dist/
|
|
7729
|
-
const
|
|
7730
|
-
const
|
|
7731
|
-
|
|
7732
|
-
const
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
8009
|
+
//#region ../agent-cli/dist/agent.js
|
|
8010
|
+
const AGENT_NAME = /^[a-zA-Z0-9_-]+$/u;
|
|
8011
|
+
const isSet = (name) => (env) => (env[name] ?? "") !== "";
|
|
8012
|
+
const equals = (name, value) => (env) => env[name] === value;
|
|
8013
|
+
const MARKERS = [
|
|
8014
|
+
(env) => AGENT_NAME.test(env.AI_AGENT ?? ""),
|
|
8015
|
+
equals("AGENT", "amp"),
|
|
8016
|
+
isSet("CODEX_SANDBOX"),
|
|
8017
|
+
isSet("CODEX_CI"),
|
|
8018
|
+
isSet("CODEX_THREAD_ID"),
|
|
8019
|
+
isSet("GEMINI_CLI"),
|
|
8020
|
+
isSet("COPILOT_CLI"),
|
|
8021
|
+
isSet("OPENCODE"),
|
|
8022
|
+
isSet("ANTIGRAVITY_AGENT"),
|
|
8023
|
+
isSet("AUGMENT_AGENT"),
|
|
8024
|
+
isSet("REPL_ID"),
|
|
8025
|
+
isSet("CLAUDE_CODE_IS_COWORK"),
|
|
8026
|
+
isSet("CLAUDECODE"),
|
|
8027
|
+
isSet("CLAUDE_CODE"),
|
|
8028
|
+
isSet("CURSOR_TRACE_ID"),
|
|
8029
|
+
isSet("CURSOR_AGENT"),
|
|
8030
|
+
equals("CURSOR_EXTENSION_HOST_ROLE", "agent-exec"),
|
|
8031
|
+
equals("TERM_PROGRAM", "kiro"),
|
|
8032
|
+
(env) => ["/.pi/agent", "\\.pi\\agent"].some((dir) => (env.PATH ?? "").includes(dir)),
|
|
8033
|
+
isSet("GOOSE_PROVIDER")
|
|
8034
|
+
];
|
|
8035
|
+
/**
|
|
8036
|
+
* Whether a Coding agent, not a person, is running the Agent CLI. Some Coding agents run commands
|
|
8037
|
+
* in a PTY, so a TTY alone doesn't mean a person is watching.
|
|
8038
|
+
*/
|
|
8039
|
+
function isAgent(env) {
|
|
8040
|
+
return MARKERS.some((marker) => marker(env));
|
|
7740
8041
|
}
|
|
7741
|
-
|
|
7742
|
-
|
|
8042
|
+
//#endregion
|
|
8043
|
+
//#region ../agent-cli/dist/errno.js
|
|
8044
|
+
const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
8045
|
+
const DISK_FULL = "DISK_FULL";
|
|
8046
|
+
const NOT_FOUND = "NOT_FOUND";
|
|
8047
|
+
const ALREADY_EXISTS = "ALREADY_EXISTS";
|
|
8048
|
+
const NOT_A_FILE = "NOT_A_FILE";
|
|
8049
|
+
function permissionHint({ path, access, callerLine, env, platform }) {
|
|
8050
|
+
if (platform === "win32") return `a program may hold ${path} open: close it, then run \`${callerLine}\` again`;
|
|
8051
|
+
if (!isAgent(env)) return `check the permissions of ${path} and its folder`;
|
|
8052
|
+
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
8053
|
}
|
|
7744
|
-
function
|
|
7745
|
-
return
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
hint
|
|
8054
|
+
function fileErrorOf(scope) {
|
|
8055
|
+
return (error, at) => classifyErrno(error, {
|
|
8056
|
+
...at,
|
|
8057
|
+
...scope
|
|
7749
8058
|
});
|
|
7750
8059
|
}
|
|
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
|
-
}
|
|
8060
|
+
function classifyErrno(error, context) {
|
|
8061
|
+
const { path } = context;
|
|
8062
|
+
switch (errnoOf(error)) {
|
|
8063
|
+
case "EACCES":
|
|
8064
|
+
case "EPERM":
|
|
8065
|
+
case "EROFS":
|
|
8066
|
+
case "EBUSY": return new CliError({
|
|
8067
|
+
code: PERMISSION_DENIED,
|
|
8068
|
+
message: `permission denied: ${path}`,
|
|
8069
|
+
hint: permissionHint(context)
|
|
8070
|
+
}, { cause: error });
|
|
8071
|
+
case "ENOSPC":
|
|
8072
|
+
case "EDQUOT": return new CliError({
|
|
8073
|
+
code: DISK_FULL,
|
|
8074
|
+
message: `no space left on the disk holding ${path}`,
|
|
8075
|
+
hint: `free space on that disk, then run \`${context.callerLine}\` again`
|
|
8076
|
+
}, { cause: error });
|
|
8077
|
+
case "ENOENT":
|
|
8078
|
+
case "ENOTDIR":
|
|
8079
|
+
case "ENAMETOOLONG": return new CliError({
|
|
8080
|
+
code: NOT_FOUND,
|
|
8081
|
+
message: `not found: ${path}`,
|
|
8082
|
+
hint: `check that ${path} and its folder exist`
|
|
8083
|
+
}, { cause: error });
|
|
8084
|
+
case "EEXIST": return new CliError({
|
|
8085
|
+
code: ALREADY_EXISTS,
|
|
8086
|
+
message: `already exists: ${path}`,
|
|
8087
|
+
hint: `pass another path, or remove ${path}`
|
|
8088
|
+
}, { cause: error });
|
|
8089
|
+
case "EISDIR": return new CliError({
|
|
8090
|
+
code: NOT_A_FILE,
|
|
8091
|
+
message: `not a file: ${path} is a folder`,
|
|
8092
|
+
hint: `pass the path of a file, not of the folder ${path}`
|
|
8093
|
+
}, { cause: error });
|
|
8094
|
+
default: return error;
|
|
7785
8095
|
}
|
|
7786
|
-
|
|
7787
|
-
|
|
8096
|
+
}
|
|
8097
|
+
//#endregion
|
|
8098
|
+
//#region ../agent-cli/dist/files.js
|
|
8099
|
+
const TEMP_FILE = /^\..+\.agent-cli-(\d+)\.tmp$/;
|
|
8100
|
+
/** Where the Runtime writes `path` before moving it into place; a dead writer's is swept. */
|
|
8101
|
+
function tempPathOf(path) {
|
|
8102
|
+
return join(dirname(path), `.${basename(path)}.agent-cli-${process.pid}.tmp`);
|
|
8103
|
+
}
|
|
8104
|
+
function isAlive(pid) {
|
|
8105
|
+
try {
|
|
8106
|
+
process.kill(pid, 0);
|
|
8107
|
+
return true;
|
|
8108
|
+
} catch (error) {
|
|
8109
|
+
return errnoOf(error) === "EPERM";
|
|
7788
8110
|
}
|
|
7789
|
-
timeout(ms) {
|
|
7790
|
-
return this.#abortsAfter(ms, "timeout");
|
|
7791
|
-
}
|
|
7792
|
-
#abortsAfter(ms, bound) {
|
|
7793
|
-
const controller = new AbortController();
|
|
7794
|
-
after(this.#clock, ms, this.#disposed.signal, () => {
|
|
7795
|
-
const reason = deadlineError(this.#commandLine, ms, waitHint(this.#callerLine, bound), bound);
|
|
7796
|
-
this.#abortReasons.add(reason);
|
|
7797
|
-
controller.abort(reason);
|
|
7798
|
-
});
|
|
7799
|
-
return controller.signal;
|
|
7800
|
-
}
|
|
7801
|
-
async runToEnd(name, work) {
|
|
7802
|
-
this.#graceCancel.abort();
|
|
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
8111
|
}
|
|
7895
|
-
function
|
|
7896
|
-
const
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
return starts.map((first, i) => body.slice(first, starts[i + 1]).map((line) => line.trim()).join(" ").replace(ITEM, ""));
|
|
7902
|
-
}
|
|
7903
|
-
function upgradeOf({ version, lines }) {
|
|
7904
|
-
const listed = listedSteps(lines);
|
|
7905
|
-
if (listed.length === 0) return [{
|
|
7906
|
-
version,
|
|
7907
|
-
notes: lines.join("\n").trim()
|
|
7908
|
-
}];
|
|
7909
|
-
const todo = listed.filter((step) => !NOTHING_TO_DO.test(step));
|
|
7910
|
-
return todo.length === 0 ? [] : [{
|
|
7911
|
-
version,
|
|
7912
|
-
steps: todo
|
|
7913
|
-
}];
|
|
8112
|
+
async function sweep(folder) {
|
|
8113
|
+
const dead = (await readdir(folder)).filter((name) => {
|
|
8114
|
+
const pid = TEMP_FILE.exec(name)?.[1];
|
|
8115
|
+
return pid !== void 0 && !isAlive(Number(pid));
|
|
8116
|
+
});
|
|
8117
|
+
await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
|
|
7914
8118
|
}
|
|
7915
|
-
/**
|
|
7916
|
-
|
|
7917
|
-
* CHANGELOG.md in `packageDir`; none when the package ships no CHANGELOG.
|
|
7918
|
-
*/
|
|
7919
|
-
async function upgradesOf(packageDir, from, to) {
|
|
7920
|
-
let changelog;
|
|
8119
|
+
/** The file a symlink at `path` points to, so a replacing write keeps the link. */
|
|
8120
|
+
async function targetOf$1(path) {
|
|
7921
8121
|
try {
|
|
7922
|
-
|
|
8122
|
+
return await realpath(path);
|
|
7923
8123
|
} catch (error) {
|
|
7924
|
-
if (isMissingFile(error)) return
|
|
8124
|
+
if (isMissingFile(error)) return path;
|
|
7925
8125
|
throw error;
|
|
7926
8126
|
}
|
|
7927
|
-
return releasesIn(changelog).filter(({ version }) => compareVersions(version, from) > 0 && compareVersions(version, to) <= 0).sort((a, b) => compareVersions(a.version, b.version)).flatMap(upgradeOf);
|
|
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`;
|
|
7943
8127
|
}
|
|
7944
|
-
function
|
|
7945
|
-
|
|
8128
|
+
async function writeWithMode(path, data, mode) {
|
|
8129
|
+
if (mode === void 0) {
|
|
8130
|
+
await writeFile(path, data);
|
|
8131
|
+
return;
|
|
8132
|
+
}
|
|
8133
|
+
await writeFile(path, data, { mode });
|
|
8134
|
+
await chmod(path, mode);
|
|
7946
8135
|
}
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
8136
|
+
/** What `link` answers on a file system without hard links, such as exFAT. */
|
|
8137
|
+
const NO_HARD_LINKS = /* @__PURE__ */ new Set([
|
|
8138
|
+
"EPERM",
|
|
8139
|
+
"ENOTSUP",
|
|
8140
|
+
"EXDEV"
|
|
8141
|
+
]);
|
|
8142
|
+
/** A link fails atomically when `path` is taken, so no reader ever sees half a file there. */
|
|
8143
|
+
async function linkNew(temp, path, data, mode) {
|
|
8144
|
+
try {
|
|
8145
|
+
await link(temp, path);
|
|
8146
|
+
} catch (error) {
|
|
8147
|
+
if (!NO_HARD_LINKS.has(errnoOf(error))) throw error;
|
|
8148
|
+
await createNew(path, data, mode);
|
|
8149
|
+
}
|
|
7956
8150
|
}
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
8151
|
+
/** A file this call created and could not finish is removed, so a rerun does not find half of it. */
|
|
8152
|
+
async function createNew(path, data, mode) {
|
|
8153
|
+
const handle = await open(path, "wx", mode);
|
|
8154
|
+
try {
|
|
8155
|
+
await handle.writeFile(data);
|
|
8156
|
+
if (mode !== void 0) await handle.chmod(mode);
|
|
8157
|
+
} catch (error) {
|
|
8158
|
+
await handle.close();
|
|
8159
|
+
await rm(path, { force: true });
|
|
8160
|
+
throw error;
|
|
8161
|
+
}
|
|
8162
|
+
await handle.close();
|
|
7960
8163
|
}
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
8164
|
+
const RENAME_ATTEMPTS = 10;
|
|
8165
|
+
const RENAME_BACKOFF_MS = 10;
|
|
8166
|
+
const HELD_BY_ANOTHER_PROGRAM = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
|
|
8167
|
+
async function renameRetrying(from, to) {
|
|
8168
|
+
for (let attempt = 1;; attempt++) try {
|
|
8169
|
+
await rename(from, to);
|
|
8170
|
+
return;
|
|
8171
|
+
} catch (error) {
|
|
8172
|
+
if (!(process.platform === "win32" && HELD_BY_ANOTHER_PROGRAM.has(errnoOf(error))) || attempt === RENAME_ATTEMPTS) throw error;
|
|
8173
|
+
await setTimeout$1(attempt * RENAME_BACKOFF_MS);
|
|
8174
|
+
}
|
|
7967
8175
|
}
|
|
7968
|
-
/**
|
|
7969
|
-
function
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
effect: "write",
|
|
7981
|
-
output: updateSchema,
|
|
7982
|
-
plan: async (_input, ctx) => {
|
|
7983
|
-
const npm = globalNpm(cli, install, ctx, node);
|
|
7984
|
-
const copy = await globalInstallOf(bin, npm);
|
|
7985
|
-
return {
|
|
7986
|
-
package: install.package,
|
|
7987
|
-
from: cli.version,
|
|
7988
|
-
latest: await latestVersion(npm),
|
|
7989
|
-
prefix: copy.prefix,
|
|
7990
|
-
packageDir: copy.packageDir
|
|
7991
|
-
};
|
|
8176
|
+
/** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
|
|
8177
|
+
function realFiles() {
|
|
8178
|
+
const swept = /* @__PURE__ */ new Set();
|
|
8179
|
+
return {
|
|
8180
|
+
exists: async (path) => {
|
|
8181
|
+
try {
|
|
8182
|
+
await access(path);
|
|
8183
|
+
return true;
|
|
8184
|
+
} catch (error) {
|
|
8185
|
+
if (isNothingThere(error)) return false;
|
|
8186
|
+
throw error;
|
|
8187
|
+
}
|
|
7992
8188
|
},
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
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);
|
|
8189
|
+
write: async (path, data, options = {}) => {
|
|
8190
|
+
await mkdir(dirname(path), { recursive: true });
|
|
8191
|
+
const target = options.exclusive === true ? path : await targetOf$1(path);
|
|
8192
|
+
const folder = dirname(target);
|
|
8193
|
+
if (!swept.has(folder)) {
|
|
8194
|
+
await sweep(folder);
|
|
8195
|
+
swept.add(folder);
|
|
8196
|
+
}
|
|
8197
|
+
const temp = tempPathOf(target);
|
|
8198
|
+
try {
|
|
8199
|
+
await writeWithMode(temp, data, options.mode);
|
|
8200
|
+
if (options.exclusive === true) {
|
|
8201
|
+
await linkNew(temp, target, data, options.mode);
|
|
8202
|
+
await rm(temp);
|
|
8203
|
+
} else await renameRetrying(temp, target);
|
|
8204
|
+
} catch (error) {
|
|
8205
|
+
await rm(temp, { force: true });
|
|
8206
|
+
throw error;
|
|
8018
8207
|
}
|
|
8019
|
-
return update;
|
|
8020
8208
|
},
|
|
8021
|
-
|
|
8022
|
-
|
|
8209
|
+
read: (path) => readFile(path),
|
|
8210
|
+
list: (folder) => readdir(folder),
|
|
8211
|
+
remove: (path) => rm(path, {
|
|
8212
|
+
recursive: true,
|
|
8213
|
+
force: true
|
|
8214
|
+
}),
|
|
8215
|
+
mkdir: async (path, options) => {
|
|
8216
|
+
const mode = options?.mode;
|
|
8217
|
+
if (options?.exclusive !== true) {
|
|
8218
|
+
await mkdir(path, {
|
|
8219
|
+
recursive: true,
|
|
8220
|
+
mode
|
|
8221
|
+
});
|
|
8222
|
+
return;
|
|
8223
|
+
}
|
|
8224
|
+
await mkdir(dirname(path), {
|
|
8225
|
+
recursive: true,
|
|
8226
|
+
mode
|
|
8227
|
+
});
|
|
8228
|
+
await mkdir(path, { mode });
|
|
8229
|
+
},
|
|
8230
|
+
stat: async (path) => {
|
|
8231
|
+
try {
|
|
8232
|
+
const { size, dev, ino } = await stat(path, { bigint: true });
|
|
8233
|
+
return {
|
|
8234
|
+
size: Number(size),
|
|
8235
|
+
id: `${dev}:${ino}`
|
|
8236
|
+
};
|
|
8237
|
+
} catch (error) {
|
|
8238
|
+
if (isNothingThere(error)) return void 0;
|
|
8239
|
+
throw error;
|
|
8240
|
+
}
|
|
8241
|
+
}
|
|
8242
|
+
};
|
|
8243
|
+
}
|
|
8244
|
+
/**
|
|
8245
|
+
* `files` resolving paths against `cwd`, its failures classified by errno; `list` drops the
|
|
8246
|
+
* Runtime's temp files and sorts.
|
|
8247
|
+
*/
|
|
8248
|
+
function classifiedFiles(files, { cwd, fileError }) {
|
|
8249
|
+
async function at(access, path, work) {
|
|
8250
|
+
const target = resolve(cwd, path);
|
|
8251
|
+
try {
|
|
8252
|
+
return await work(target);
|
|
8253
|
+
} catch (error) {
|
|
8254
|
+
throw fileError(error, {
|
|
8255
|
+
path: target,
|
|
8256
|
+
access
|
|
8257
|
+
});
|
|
8258
|
+
}
|
|
8259
|
+
}
|
|
8260
|
+
return {
|
|
8261
|
+
exists: (path) => at("read", path, (target) => files.exists(target)),
|
|
8262
|
+
write: (path, data, options) => at("write", path, (target) => files.write(target, data, options)),
|
|
8263
|
+
read: (path) => at("read", path, (target) => files.read(target)),
|
|
8264
|
+
list: (folder) => at("read", folder, async (target) => (await files.list(target)).filter((name) => !TEMP_FILE.test(name)).sort()),
|
|
8265
|
+
remove: (path) => at("write", path, (target) => files.remove(target)),
|
|
8266
|
+
mkdir: (path, options) => at("write", path, (target) => files.mkdir(target, options)),
|
|
8267
|
+
stat: (path) => at("read", path, (target) => files.stat(target))
|
|
8268
|
+
};
|
|
8269
|
+
}
|
|
8270
|
+
//#endregion
|
|
8271
|
+
//#region ../agent-cli/dist/outputs.js
|
|
8272
|
+
const OUTPUT_EXISTS = "OUTPUT_EXISTS";
|
|
8273
|
+
/** Refuses a run that would overwrite existing outputs unless the caller passed `--force`. */
|
|
8274
|
+
async function checkOutputs({ invocation, commandLine, force, files }, outputs) {
|
|
8275
|
+
if (outputs === void 0 || force) return;
|
|
8276
|
+
const found = await Promise.all(outputs.map((path) => files.exists(path)));
|
|
8277
|
+
const existing = outputs.filter((_path, index) => found[index]);
|
|
8278
|
+
if (existing.length === 0) return;
|
|
8279
|
+
const them = existing.length === 1 ? "it" : "them";
|
|
8280
|
+
throw new CliError({
|
|
8281
|
+
code: OUTPUT_EXISTS,
|
|
8282
|
+
message: `\`${commandLine}\` would overwrite ${existing.join(", ")}`,
|
|
8283
|
+
hint: `to replace ${them}, run \`${commandLineOf(invocation, { add: [FORCE_FLAG.flag] })}\``
|
|
8023
8284
|
});
|
|
8024
8285
|
}
|
|
8286
|
+
/** Guards the writes to declared outputs of `files`, whose paths resolve against `cwd`. */
|
|
8287
|
+
function outputsGuard(files, cwd) {
|
|
8288
|
+
const unwritten = /* @__PURE__ */ new Set();
|
|
8289
|
+
let forceLine = "";
|
|
8290
|
+
const exclusiveWrite = async (path, data, options) => {
|
|
8291
|
+
const target = resolve(cwd, path);
|
|
8292
|
+
unwritten.delete(target);
|
|
8293
|
+
try {
|
|
8294
|
+
await files.write(path, data, {
|
|
8295
|
+
...options,
|
|
8296
|
+
exclusive: true
|
|
8297
|
+
});
|
|
8298
|
+
} catch (error) {
|
|
8299
|
+
unwritten.add(target);
|
|
8300
|
+
if (!(error instanceof CliError) || error.code !== "ALREADY_EXISTS") throw error;
|
|
8301
|
+
throw new CliError({
|
|
8302
|
+
code: ALREADY_EXISTS,
|
|
8303
|
+
message: error.message,
|
|
8304
|
+
hint: `to replace it, run \`${forceLine}\``
|
|
8305
|
+
}, { cause: error });
|
|
8306
|
+
}
|
|
8307
|
+
};
|
|
8308
|
+
return {
|
|
8309
|
+
files: {
|
|
8310
|
+
...files,
|
|
8311
|
+
write: (path, data, options) => unwritten.has(resolve(cwd, path)) ? exclusiveWrite(path, data, options) : files.write(path, data, options)
|
|
8312
|
+
},
|
|
8313
|
+
guard: (outputs, line) => {
|
|
8314
|
+
forceLine = line;
|
|
8315
|
+
for (const path of outputs) unwritten.add(resolve(cwd, path));
|
|
8316
|
+
}
|
|
8317
|
+
};
|
|
8318
|
+
}
|
|
8319
|
+
/**
|
|
8320
|
+
* Creates each output folder and a temp file in it, so a sandbox denial fails before `apply`
|
|
8321
|
+
* spends anything. The temp file's name is the Runtime's, so a crashed run's leftover is swept.
|
|
8322
|
+
*/
|
|
8323
|
+
async function probeOutputs({ files, cwd, fileError }, outputs) {
|
|
8324
|
+
const folders = new Set(outputs.map((path) => dirname(resolve(cwd, path))));
|
|
8325
|
+
await Promise.all([...folders].map(async (folder) => {
|
|
8326
|
+
const probe = tempPathOf(join(folder, "outputs-probe"));
|
|
8327
|
+
try {
|
|
8328
|
+
await files.mkdir(folder);
|
|
8329
|
+
await files.write(probe, "");
|
|
8330
|
+
await files.remove(probe);
|
|
8331
|
+
} catch (error) {
|
|
8332
|
+
throw fileError(error, {
|
|
8333
|
+
path: folder,
|
|
8334
|
+
access: "write"
|
|
8335
|
+
});
|
|
8336
|
+
}
|
|
8337
|
+
}));
|
|
8338
|
+
}
|
|
8025
8339
|
//#endregion
|
|
8026
|
-
//#region ../agent-cli/dist/built-ins/
|
|
8027
|
-
|
|
8028
|
-
|
|
8340
|
+
//#region ../agent-cli/dist/built-ins/skill-install.js
|
|
8341
|
+
const installSchema = object({
|
|
8342
|
+
copy: string(),
|
|
8343
|
+
written: array(string()),
|
|
8344
|
+
removed: array(string()),
|
|
8345
|
+
agentsMd: object({
|
|
8346
|
+
path: string(),
|
|
8347
|
+
changed: boolean()
|
|
8348
|
+
}),
|
|
8349
|
+
handEdits: array(string())
|
|
8350
|
+
});
|
|
8351
|
+
async function sameContent(left, right) {
|
|
8352
|
+
const [a, b] = await Promise.all([readFile(left), readFile(right)]);
|
|
8353
|
+
return a.equals(b);
|
|
8354
|
+
}
|
|
8355
|
+
async function planOf(cli, folder, cwd, packageRoot) {
|
|
8356
|
+
const source = join(packageRoot, folder);
|
|
8357
|
+
const { root, copy } = await skillCopyOf(cli, cwd);
|
|
8358
|
+
const [shipped, present] = await Promise.all([filesIn(source), filesIn(copy)]);
|
|
8359
|
+
const differs = await Promise.all(shipped.map(async (file) => present.includes(file) && !await sameContent(join(source, file), join(copy, file))));
|
|
8029
8360
|
return {
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8361
|
+
source,
|
|
8362
|
+
copy,
|
|
8363
|
+
write: shipped.filter((file) => !present.includes(file)),
|
|
8364
|
+
replace: shipped.filter((_file, index) => differs[index]),
|
|
8365
|
+
remove: present.filter((file) => !shipped.includes(file)),
|
|
8366
|
+
sameVersion: (await skillVersionsOf(source, copy)).same,
|
|
8367
|
+
agentsMd: await agentsMdAt(cli, root),
|
|
8368
|
+
handEdits: await handEditsAt(root)
|
|
8369
|
+
};
|
|
8370
|
+
}
|
|
8371
|
+
const BLOCK_VERBS = {
|
|
8372
|
+
create: "create",
|
|
8373
|
+
add: "add the block to",
|
|
8374
|
+
rewrite: "rewrite the block in"
|
|
8375
|
+
};
|
|
8376
|
+
function renderPlan(cli, plan) {
|
|
8377
|
+
const files = [
|
|
8378
|
+
...plan.write.map((file) => ` write ${file}`),
|
|
8379
|
+
...plan.replace.map((file) => ` replace ${file}`),
|
|
8380
|
+
...plan.remove.map((file) => ` remove ${file}`)
|
|
8381
|
+
];
|
|
8382
|
+
const { path, block, change } = plan.agentsMd;
|
|
8383
|
+
return [
|
|
8384
|
+
files.length === 0 ? `the ${cli.name} skill at ${plan.copy} already matches the package` : `would copy the ${cli.name} skill to ${plan.copy}:`,
|
|
8385
|
+
...files,
|
|
8386
|
+
change === null ? `${path} already holds the ${cli.name} block` : `would ${BLOCK_VERBS[change]} ${path}:\n${block}`,
|
|
8387
|
+
...plan.handEdits.map((edit) => `by hand: ${edit}`)
|
|
8388
|
+
].join("\n");
|
|
8389
|
+
}
|
|
8390
|
+
function renderInstalled(cli, data) {
|
|
8391
|
+
const { written, removed, agentsMd } = data;
|
|
8392
|
+
return [
|
|
8393
|
+
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}`,
|
|
8394
|
+
agentsMd.changed ? `wrote the ${cli.name} block in ${agentsMd.path}` : `${agentsMd.path} already holds the ${cli.name} block`,
|
|
8395
|
+
...data.handEdits.map((edit) => `by hand: ${edit}`)
|
|
8396
|
+
].join("\n");
|
|
8033
8397
|
}
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
const hint = `to confirm, run \`${commandLineOf(invocation, {
|
|
8041
|
-
remove: [CONFIRM_FLAG.flag],
|
|
8042
|
-
add: [CONFIRM_FLAG.flag, expected]
|
|
8043
|
-
})}\``;
|
|
8044
|
-
if (given === void 0) throw new UsageError({
|
|
8045
|
-
code: CONFIRMATION_REQUIRED,
|
|
8046
|
-
message: `\`${commandLine}\` is destructive and needs ${CONFIRM_FLAG.flag}`,
|
|
8047
|
-
hint
|
|
8048
|
-
});
|
|
8049
|
-
throw new UsageError({
|
|
8050
|
-
code: CONFIRMATION_MISMATCH,
|
|
8051
|
-
message: `${CONFIRM_FLAG.flag} ${JSON.stringify(given)} does not match the target of \`${commandLine}\``,
|
|
8052
|
-
hint
|
|
8398
|
+
function editedRemovals(plan, ctx) {
|
|
8399
|
+
const them = plan.remove.length === 1 ? "it" : "them";
|
|
8400
|
+
return new CliError({
|
|
8401
|
+
code: OUTPUT_EXISTS,
|
|
8402
|
+
message: `\`${ctx.commandLine()}\` would remove ${plan.remove.map((file) => join(plan.copy, file)).join(", ")}`,
|
|
8403
|
+
hint: `to remove ${them}, run \`${ctx.commandLine({ add: [FORCE_FLAG.flag] })}\``
|
|
8053
8404
|
});
|
|
8054
8405
|
}
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
const
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8406
|
+
/** The built-in `skill install`, for a CLI whose skill ships in its package. */
|
|
8407
|
+
function skillInstallCommand(cli, folder) {
|
|
8408
|
+
const runAs = runAsOf(cli);
|
|
8409
|
+
return defineCommand$1({
|
|
8410
|
+
name: "install",
|
|
8411
|
+
description: "copy this CLI's skill into the project and write its AGENTS.md block",
|
|
8412
|
+
examples: [{
|
|
8413
|
+
command: `${runAs} skill install`,
|
|
8414
|
+
note: "install the skill this version ships"
|
|
8415
|
+
}, {
|
|
8416
|
+
command: `${runAs} skill install --dry-run`,
|
|
8417
|
+
note: "show what it would change"
|
|
8418
|
+
}],
|
|
8419
|
+
effect: "write",
|
|
8420
|
+
output: installSchema,
|
|
8421
|
+
force: "replace or remove hand-edited files of a skill copy at the package's version",
|
|
8422
|
+
plan: (_input, ctx) => planOf(cli, folder, ctx.cwd, ctx.packageRoot),
|
|
8423
|
+
outputs: (plan) => plan.sameVersion ? [...plan.write, ...plan.replace].map((file) => join(plan.copy, file)) : [],
|
|
8424
|
+
apply: async (plan, ctx) => {
|
|
8425
|
+
if (plan.sameVersion && plan.remove.length > 0 && !ctx.force) throw editedRemovals(plan, ctx);
|
|
8426
|
+
for (const file of [...plan.write, ...plan.replace]) await ctx.files.write(join(plan.copy, file), await readFile(join(plan.source, file)));
|
|
8427
|
+
for (const file of plan.remove) await ctx.files.remove(join(plan.copy, file));
|
|
8428
|
+
const { path, change } = plan.agentsMd;
|
|
8429
|
+
if (change !== null) {
|
|
8430
|
+
const existing = await orMissing(readFile(path, "utf8")) ?? "";
|
|
8431
|
+
await ctx.files.write(path, withBlock(cli, existing));
|
|
8432
|
+
}
|
|
8433
|
+
return {
|
|
8434
|
+
copy: plan.copy,
|
|
8435
|
+
written: [...plan.write, ...plan.replace].sort(),
|
|
8436
|
+
removed: [...plan.remove],
|
|
8437
|
+
agentsMd: {
|
|
8438
|
+
path,
|
|
8439
|
+
changed: change !== null
|
|
8440
|
+
},
|
|
8441
|
+
handEdits: [...plan.handEdits]
|
|
8442
|
+
};
|
|
8443
|
+
},
|
|
8444
|
+
render: (data) => renderInstalled(cli, data),
|
|
8445
|
+
renderPlan: (plan) => renderPlan(cli, plan)
|
|
8065
8446
|
});
|
|
8066
8447
|
}
|
|
8067
8448
|
//#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));
|
|
8449
|
+
//#region ../agent-cli/dist/interrupts.js
|
|
8450
|
+
const INTERRUPTED = "INTERRUPTED";
|
|
8451
|
+
const DEADLINE_EXCEEDED = "DEADLINE_EXCEEDED";
|
|
8452
|
+
/** How long an interrupted Command may take to settle before the run ends without it. */
|
|
8453
|
+
const GRACE_MS = 3e3;
|
|
8454
|
+
const GRACE_OVER = Symbol("grace over");
|
|
8455
|
+
function interruptedError(callerLine) {
|
|
8456
|
+
return new CliError({
|
|
8457
|
+
code: INTERRUPTED,
|
|
8458
|
+
message: "interrupted before it finished",
|
|
8459
|
+
hint: `to finish it, run \`${callerLine}\` again, unless it was stopped on purpose; if a tool timeout stopped it, give it longer`
|
|
8460
|
+
});
|
|
8100
8461
|
}
|
|
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`}`;
|
|
8462
|
+
function waitHint(callerLine, bound) {
|
|
8463
|
+
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
8464
|
}
|
|
8113
|
-
function
|
|
8114
|
-
return
|
|
8115
|
-
|
|
8116
|
-
|
|
8465
|
+
function deadlineError(commandLine, ms, hint, bound = "deadline") {
|
|
8466
|
+
return new CliError({
|
|
8467
|
+
code: DEADLINE_EXCEEDED,
|
|
8468
|
+
message: `${commandLine} ran past its ${ms} ms ${bound}`,
|
|
8469
|
+
hint
|
|
8117
8470
|
});
|
|
8118
8471
|
}
|
|
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
|
-
|
|
8472
|
+
/** One Command run's cancellation: its signal, its deadlines and the steps it must finish. */
|
|
8473
|
+
var RunControl = class {
|
|
8474
|
+
signal;
|
|
8475
|
+
#controller = new AbortController();
|
|
8476
|
+
#commandLine;
|
|
8477
|
+
#callerLine;
|
|
8478
|
+
#outer;
|
|
8479
|
+
#onOuterAbort = () => this.#controller.abort(interruptedError(this.#callerLine));
|
|
8480
|
+
/** Each running must-finish step, tracked until it settles, with its name. */
|
|
8481
|
+
#steps = /* @__PURE__ */ new Map();
|
|
8482
|
+
#progress;
|
|
8483
|
+
#clock;
|
|
8484
|
+
#disposed = new AbortController();
|
|
8485
|
+
/** The reasons this run's deadlines and timeouts aborted with, to tell them in a Command's error. */
|
|
8486
|
+
#abortReasons = /* @__PURE__ */ new WeakSet();
|
|
8487
|
+
#graceCancel = new AbortController();
|
|
8488
|
+
#endGrace = () => {};
|
|
8489
|
+
#graceOver = new Promise((resolve) => {
|
|
8490
|
+
this.#endGrace = resolve;
|
|
8491
|
+
});
|
|
8492
|
+
constructor({ commandLine, callerLine, outer, clock, progress, runDeadline }) {
|
|
8493
|
+
this.#commandLine = commandLine;
|
|
8494
|
+
this.#callerLine = callerLine;
|
|
8495
|
+
this.#outer = outer;
|
|
8496
|
+
this.#clock = clock;
|
|
8497
|
+
this.#progress = progress;
|
|
8498
|
+
this.signal = this.#controller.signal;
|
|
8499
|
+
this.signal.addEventListener("abort", () => this.#onStop(), { once: true });
|
|
8500
|
+
if (outer?.aborted === true) this.#onOuterAbort();
|
|
8501
|
+
else outer?.addEventListener("abort", this.#onOuterAbort, { once: true });
|
|
8502
|
+
if (runDeadline !== void 0) {
|
|
8503
|
+
const { ms, hint } = runDeadline;
|
|
8504
|
+
after(clock, ms, this.#disposed.signal, () => this.#controller.abort(deadlineError(commandLine, ms, hint())));
|
|
8505
|
+
}
|
|
8506
|
+
}
|
|
8507
|
+
deadline(ms) {
|
|
8508
|
+
return AbortSignal.any([this.signal, this.#abortsAfter(ms, "deadline")]);
|
|
8509
|
+
}
|
|
8510
|
+
timeout(ms) {
|
|
8511
|
+
return this.#abortsAfter(ms, "timeout");
|
|
8512
|
+
}
|
|
8513
|
+
#abortsAfter(ms, bound) {
|
|
8514
|
+
const controller = new AbortController();
|
|
8515
|
+
after(this.#clock, ms, this.#disposed.signal, () => {
|
|
8516
|
+
const reason = deadlineError(this.#commandLine, ms, waitHint(this.#callerLine, bound), bound);
|
|
8517
|
+
this.#abortReasons.add(reason);
|
|
8518
|
+
controller.abort(reason);
|
|
8519
|
+
});
|
|
8520
|
+
return controller.signal;
|
|
8521
|
+
}
|
|
8522
|
+
async runToEnd(name, work) {
|
|
8523
|
+
this.#graceCancel.abort();
|
|
8524
|
+
if (this.signal.aborted) this.#finishing(name);
|
|
8525
|
+
const step = work();
|
|
8526
|
+
const settled = step.then(() => {}, () => {});
|
|
8527
|
+
this.#steps.set(settled, name);
|
|
8528
|
+
try {
|
|
8529
|
+
return await step;
|
|
8530
|
+
} finally {
|
|
8531
|
+
this.#steps.delete(settled);
|
|
8532
|
+
this.#startGrace();
|
|
8533
|
+
}
|
|
8534
|
+
}
|
|
8535
|
+
/** Waits for the Command, its must-finish steps, and no longer than the grace once interrupted. */
|
|
8536
|
+
async settle(work) {
|
|
8537
|
+
try {
|
|
8538
|
+
const value = await Promise.race([work, this.#graceOver]);
|
|
8539
|
+
if (this.signal.aborted || value === GRACE_OVER) throw this.signal.reason;
|
|
8540
|
+
return value;
|
|
8541
|
+
} finally {
|
|
8542
|
+
await this.#stepsDone();
|
|
8543
|
+
this.close();
|
|
8544
|
+
}
|
|
8545
|
+
}
|
|
8546
|
+
/**
|
|
8547
|
+
* The error a run ends with: an interrupt or the run deadline outranks what the Command threw,
|
|
8548
|
+
* unless the Command threw its own `INTERRUPTED`, saying what it finished before it stopped.
|
|
8549
|
+
* Otherwise each leaf that is not a `CliError` and whose cause chain holds a deadline's or timeout's
|
|
8550
|
+
* abort is reported as that abort, so a declared error keeps its own code.
|
|
8551
|
+
*/
|
|
8552
|
+
errorAfter(error) {
|
|
8553
|
+
if (this.signal.aborted) return error instanceof CliError && error.code === "INTERRUPTED" ? error : this.signal.reason;
|
|
8554
|
+
const leaves = errorsOf(error).map((leaf) => this.#abortIn(leaf) ?? leaf);
|
|
8555
|
+
return leaves.length === 1 ? leaves[0] : new AggregateError(leaves);
|
|
8556
|
+
}
|
|
8557
|
+
#abortIn(error) {
|
|
8558
|
+
if (error instanceof CliError) return void 0;
|
|
8559
|
+
return causesOf(error).find((each) => each instanceof CliError && this.#abortReasons.has(each));
|
|
8154
8560
|
}
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
}
|
|
8561
|
+
#onStop() {
|
|
8562
|
+
for (const name of this.#steps.values()) this.#finishing(name);
|
|
8563
|
+
this.#startGrace();
|
|
8564
|
+
}
|
|
8565
|
+
#finishing(name) {
|
|
8566
|
+
this.#progress(`finishing ${name}`);
|
|
8567
|
+
}
|
|
8568
|
+
#startGrace() {
|
|
8569
|
+
if (this.signal.aborted && this.#steps.size === 0) {
|
|
8570
|
+
this.#graceCancel.abort();
|
|
8571
|
+
this.#graceCancel = new AbortController();
|
|
8572
|
+
after(this.#clock, GRACE_MS, this.#graceCancel.signal, () => this.#endGrace(GRACE_OVER));
|
|
8573
|
+
}
|
|
8574
|
+
}
|
|
8575
|
+
async #stepsDone() {
|
|
8576
|
+
while (this.#steps.size > 0) await Promise.all(this.#steps.keys());
|
|
8577
|
+
}
|
|
8578
|
+
/** Stops the run's timers and its hold on the caller's signal, for a run that never settles. */
|
|
8579
|
+
close() {
|
|
8580
|
+
this.#graceCancel.abort();
|
|
8581
|
+
this.#disposed.abort();
|
|
8582
|
+
this.#outer?.removeEventListener("abort", this.#onOuterAbort);
|
|
8583
|
+
}
|
|
8584
|
+
};
|
|
8168
8585
|
//#endregion
|
|
8169
|
-
//#region ../agent-cli/dist/
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
}
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8586
|
+
//#region ../agent-cli/dist/built-ins/upgrades.js
|
|
8587
|
+
/** A skipped release's Upgrade steps, or its whole section when it lists none. */
|
|
8588
|
+
const upgradeSchema = union([object({
|
|
8589
|
+
version: string(),
|
|
8590
|
+
steps: array(string())
|
|
8591
|
+
}), object({
|
|
8592
|
+
version: string(),
|
|
8593
|
+
notes: string()
|
|
8594
|
+
})]);
|
|
8595
|
+
const RELEASE_HEADING = /^## v?(\S+)/;
|
|
8596
|
+
const UPGRADE_HEADING = /^### Upgrade from v\S+$/;
|
|
8597
|
+
const HEADING = /^#{1,6} /;
|
|
8598
|
+
const ITEM = /^[-*] /;
|
|
8599
|
+
const NOTHING_TO_DO = /^nothing to do\.?$/i;
|
|
8600
|
+
function releasesIn(changelog) {
|
|
8601
|
+
const releases = [];
|
|
8602
|
+
let current;
|
|
8603
|
+
for (const line of changelog.split(/\r?\n/)) {
|
|
8604
|
+
const heading = RELEASE_HEADING.exec(line);
|
|
8605
|
+
if (heading !== null) {
|
|
8606
|
+
const version = heading[1] ?? "";
|
|
8607
|
+
current = isVersion(version) ? {
|
|
8608
|
+
version,
|
|
8609
|
+
lines: []
|
|
8610
|
+
} : void 0;
|
|
8611
|
+
if (current !== void 0) releases.push(current);
|
|
8612
|
+
} else current?.lines.push(line);
|
|
8181
8613
|
}
|
|
8614
|
+
return releases;
|
|
8182
8615
|
}
|
|
8183
|
-
|
|
8184
|
-
const
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8616
|
+
function listedSteps(lines) {
|
|
8617
|
+
const start = lines.findIndex((line) => UPGRADE_HEADING.test(line.trimEnd()));
|
|
8618
|
+
if (start === -1) return [];
|
|
8619
|
+
const end = lines.findIndex((line, i) => i > start && HEADING.test(line));
|
|
8620
|
+
const body = lines.slice(start + 1, end === -1 ? void 0 : end).filter((line) => line.trim() !== "");
|
|
8621
|
+
const starts = body.flatMap((line, i) => ITEM.test(line) ? [i] : []);
|
|
8622
|
+
return starts.map((first, i) => body.slice(first, starts[i + 1]).map((line) => line.trim()).join(" ").replace(ITEM, ""));
|
|
8189
8623
|
}
|
|
8190
|
-
|
|
8191
|
-
|
|
8624
|
+
function upgradeOf({ version, lines }) {
|
|
8625
|
+
const listed = listedSteps(lines);
|
|
8626
|
+
if (listed.length === 0) return [{
|
|
8627
|
+
version,
|
|
8628
|
+
notes: lines.join("\n").trim()
|
|
8629
|
+
}];
|
|
8630
|
+
const todo = listed.filter((step) => !NOTHING_TO_DO.test(step));
|
|
8631
|
+
return todo.length === 0 ? [] : [{
|
|
8632
|
+
version,
|
|
8633
|
+
steps: todo
|
|
8634
|
+
}];
|
|
8635
|
+
}
|
|
8636
|
+
/**
|
|
8637
|
+
* What each release after `from` up to `to` asks of the operator, oldest first, from the
|
|
8638
|
+
* CHANGELOG.md in `packageDir`; none when the package ships no CHANGELOG.
|
|
8639
|
+
*/
|
|
8640
|
+
async function upgradesOf(packageDir, from, to) {
|
|
8641
|
+
let changelog;
|
|
8192
8642
|
try {
|
|
8193
|
-
|
|
8643
|
+
changelog = await readFile(join(packageDir, "CHANGELOG.md"), "utf8");
|
|
8194
8644
|
} catch (error) {
|
|
8195
|
-
if (isMissingFile(error)) return
|
|
8645
|
+
if (isMissingFile(error)) return [];
|
|
8196
8646
|
throw error;
|
|
8197
8647
|
}
|
|
8648
|
+
return releasesIn(changelog).filter(({ version }) => compareVersions(version, from) > 0 && compareVersions(version, to) <= 0).sort((a, b) => compareVersions(a.version, b.version)).flatMap(upgradeOf);
|
|
8198
8649
|
}
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8650
|
+
//#endregion
|
|
8651
|
+
//#region ../agent-cli/dist/built-ins/update.js
|
|
8652
|
+
const updateSchema = object({
|
|
8653
|
+
from: string(),
|
|
8654
|
+
latest: string(),
|
|
8655
|
+
installed: object({
|
|
8656
|
+
version: string(),
|
|
8657
|
+
prefix: string()
|
|
8658
|
+
}).nullable(),
|
|
8659
|
+
upgrades: array(upgradeSchema),
|
|
8660
|
+
next: array(string())
|
|
8661
|
+
});
|
|
8662
|
+
function nothingToInstall({ from, latest }) {
|
|
8663
|
+
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
8664
|
}
|
|
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
|
-
}
|
|
8665
|
+
function renderUpgrade(upgrade) {
|
|
8666
|
+
return "steps" in upgrade ? [`\nupgrade steps for ${upgrade.version}:`, ...upgrade.steps.map((step) => `- ${step}`)].join("\n") : `\nchanges in ${upgrade.version}:\n${upgrade.notes}`;
|
|
8221
8667
|
}
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
const
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
throw error;
|
|
8232
|
-
}
|
|
8233
|
-
await handle.close();
|
|
8668
|
+
function renderUpdate(cli, install, update) {
|
|
8669
|
+
const { installed } = update;
|
|
8670
|
+
const head = installed === null ? [nothingToInstall(update)] : [`installed ${install.package}@${installed.version} with the npm under ${installed.prefix}, replacing ${update.from}`, ...update.upgrades.map(renderUpgrade)];
|
|
8671
|
+
const next = update.next.map((command) => `next: ${command}, so the ${cli.name} skill describes ${update.latest}`);
|
|
8672
|
+
return (next.length === 0 ? head : [
|
|
8673
|
+
...head,
|
|
8674
|
+
"",
|
|
8675
|
+
...next
|
|
8676
|
+
]).join("\n");
|
|
8234
8677
|
}
|
|
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
|
-
}
|
|
8678
|
+
async function skillNext(cli, latest, ctx) {
|
|
8679
|
+
const packaged = packagedFolderOf(cli);
|
|
8680
|
+
const finding = packaged === void 0 ? await skillReport(cli, latest, ctx) : await packagedSkillFinding(cli, packaged, ctx);
|
|
8681
|
+
return "fix" in finding && finding.fix !== void 0 ? [finding.fix] : [];
|
|
8246
8682
|
}
|
|
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 });
|
|
8683
|
+
function interruptedAfterInstall(plan) {
|
|
8684
|
+
return new CliError({
|
|
8685
|
+
code: INTERRUPTED,
|
|
8686
|
+
message: `interrupted, but ${plan.package}@${plan.latest} had already been installed with the npm under ${plan.prefix}, replacing ${plan.from}`,
|
|
8687
|
+
hint: "nothing to run again: run the commands under next"
|
|
8688
|
+
});
|
|
8689
|
+
}
|
|
8690
|
+
/** The built-in `update`, for a CLI installed globally with npm. */
|
|
8691
|
+
function updateCommand(cli, install, { bin, node }) {
|
|
8692
|
+
return defineCommand$1({
|
|
8693
|
+
name: "update",
|
|
8694
|
+
description: "install the latest version of this CLI from its registry",
|
|
8695
|
+
examples: [{
|
|
8696
|
+
command: `${cli.name} update`,
|
|
8697
|
+
note: "install the latest version and show what changed"
|
|
8698
|
+
}, {
|
|
8699
|
+
command: `${cli.name} update --dry-run`,
|
|
8700
|
+
note: "show the version it would install"
|
|
8701
|
+
}],
|
|
8702
|
+
effect: "write",
|
|
8703
|
+
output: updateSchema,
|
|
8704
|
+
plan: async (_input, ctx) => {
|
|
8705
|
+
const npm = globalNpm(cli, install, ctx, node);
|
|
8706
|
+
const copy = await globalInstallOf(bin, npm);
|
|
8707
|
+
return {
|
|
8708
|
+
package: install.package,
|
|
8709
|
+
from: cli.version,
|
|
8710
|
+
latest: await latestVersion(npm),
|
|
8711
|
+
prefix: copy.prefix,
|
|
8712
|
+
packageDir: copy.packageDir
|
|
8713
|
+
};
|
|
8300
8714
|
},
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8715
|
+
apply: async (plan, ctx) => {
|
|
8716
|
+
const { from, latest, prefix } = plan;
|
|
8717
|
+
if (standingOf$1(from, latest) !== "behind") return {
|
|
8718
|
+
from,
|
|
8719
|
+
latest,
|
|
8720
|
+
installed: null,
|
|
8721
|
+
upgrades: [],
|
|
8722
|
+
next: await skillNext(cli, latest, ctx)
|
|
8723
|
+
};
|
|
8724
|
+
ctx.signal.throwIfAborted();
|
|
8725
|
+
ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
|
|
8726
|
+
await ctx.runToEnd(`installing ${plan.package}@${latest}`, () => installGlobally(latest, globalNpm(cli, install, ctx, node)));
|
|
8727
|
+
const update = {
|
|
8728
|
+
from,
|
|
8729
|
+
latest,
|
|
8730
|
+
installed: {
|
|
8731
|
+
version: latest,
|
|
8732
|
+
prefix
|
|
8733
|
+
},
|
|
8734
|
+
upgrades: await upgradesOf(plan.packageDir, from, latest),
|
|
8735
|
+
next: await skillNext(cli, latest, ctx)
|
|
8736
|
+
};
|
|
8737
|
+
if (ctx.signal.aborted) {
|
|
8738
|
+
ctx.deliver(update);
|
|
8739
|
+
throw interruptedAfterInstall(plan);
|
|
8311
8740
|
}
|
|
8312
|
-
|
|
8313
|
-
|
|
8741
|
+
return update;
|
|
8742
|
+
},
|
|
8743
|
+
render: (update) => renderUpdate(cli, install, update),
|
|
8744
|
+
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)
|
|
8745
|
+
});
|
|
8314
8746
|
}
|
|
8747
|
+
//#endregion
|
|
8748
|
+
//#region ../agent-cli/dist/built-ins/commands.js
|
|
8315
8749
|
/**
|
|
8316
|
-
*
|
|
8317
|
-
*
|
|
8750
|
+
* The Commands the Runtime adds to a multi-Command CLI: `update` only for a Global install, and
|
|
8751
|
+
* `skill install` only for a Packaged skill.
|
|
8318
8752
|
*/
|
|
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
|
-
}
|
|
8753
|
+
function builtInCommands(cli, launch) {
|
|
8754
|
+
const packaged = packagedFolderOf(cli);
|
|
8331
8755
|
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))
|
|
8756
|
+
doctor: doctorCommand(cli, launch),
|
|
8757
|
+
...cli.install?.mode === "global" ? { update: updateCommand(cli, cli.install, launch) } : {},
|
|
8758
|
+
...packaged === void 0 ? {} : { skill: { install: skillInstallCommand(cli, packaged) } }
|
|
8339
8759
|
};
|
|
8340
8760
|
}
|
|
8341
8761
|
//#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
|
|
8762
|
+
//#region ../agent-cli/dist/confirm.js
|
|
8763
|
+
const CONFIRMATION_REQUIRED = "CONFIRMATION_REQUIRED";
|
|
8764
|
+
const CONFIRMATION_MISMATCH = "CONFIRMATION_MISMATCH";
|
|
8765
|
+
function checkConfirmation({ invocation, commandLine, given }, expected) {
|
|
8766
|
+
if (given === expected) return;
|
|
8767
|
+
const hint = `to confirm, run \`${commandLineOf(invocation, {
|
|
8768
|
+
remove: [CONFIRM_FLAG.flag],
|
|
8769
|
+
add: [CONFIRM_FLAG.flag, expected]
|
|
8770
|
+
})}\``;
|
|
8771
|
+
if (given === void 0) throw new UsageError({
|
|
8772
|
+
code: CONFIRMATION_REQUIRED,
|
|
8773
|
+
message: `\`${commandLine}\` is destructive and needs ${CONFIRM_FLAG.flag}`,
|
|
8774
|
+
hint
|
|
8775
|
+
});
|
|
8776
|
+
throw new UsageError({
|
|
8777
|
+
code: CONFIRMATION_MISMATCH,
|
|
8778
|
+
message: `${CONFIRM_FLAG.flag} ${JSON.stringify(given)} does not match the target of \`${commandLine}\``,
|
|
8779
|
+
hint
|
|
8355
8780
|
});
|
|
8356
8781
|
}
|
|
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
|
-
};
|
|
8782
|
+
//#endregion
|
|
8783
|
+
//#region ../agent-cli/dist/cost.js
|
|
8784
|
+
const NOT_CONFIRMED = "NOT_CONFIRMED";
|
|
8785
|
+
/** Refuses a run whose Plan states a cost the caller has not approved with `--yes`. */
|
|
8786
|
+
function checkCost({ invocation, commandLine, yes }, cost) {
|
|
8787
|
+
if (cost === void 0 || yes) return;
|
|
8788
|
+
throw new CliError({
|
|
8789
|
+
code: NOT_CONFIRMED,
|
|
8790
|
+
message: `\`${commandLine}\` needs ${YES_FLAG.flag}: ${cost}`,
|
|
8791
|
+
hint: `to approve the cost, run \`${commandLineOf(invocation, { add: [YES_FLAG.flag] })}\``
|
|
8792
|
+
});
|
|
8389
8793
|
}
|
|
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
|
-
}));
|
|
8794
|
+
//#endregion
|
|
8795
|
+
//#region ../agent-cli/dist/operator.js
|
|
8796
|
+
const NEEDS_OPERATOR = "NEEDS_OPERATOR";
|
|
8797
|
+
/** Fails with `NEEDS_OPERATOR` when a Coding agent runs an Operator Command. */
|
|
8798
|
+
function checkOperator({ operator, commandLine }, env, callerLine) {
|
|
8799
|
+
if (!operator || !isAgent(env)) return;
|
|
8800
|
+
throw new CliError({
|
|
8801
|
+
code: NEEDS_OPERATOR,
|
|
8802
|
+
message: `\`${commandLine}\` must be run by a person at a terminal, not a Coding agent`,
|
|
8803
|
+
hint: `ask the operator to type: ! ${callerLine}`
|
|
8804
|
+
});
|
|
8409
8805
|
}
|
|
8410
8806
|
//#endregion
|
|
8411
8807
|
//#region ../agent-cli/dist/exit-classes.js
|
|
@@ -8508,7 +8904,10 @@ function checkOwnCodes(cli) {
|
|
|
8508
8904
|
}
|
|
8509
8905
|
function defineCli$1(cli) {
|
|
8510
8906
|
checkOwnCodes(cli);
|
|
8511
|
-
if (cli.commands === void 0)
|
|
8907
|
+
if (cli.commands === void 0) {
|
|
8908
|
+
if (packagedFolderOf(cli) !== void 0) throw new Error(`${cli.name} declares a Packaged skill, which only a CLI with several Commands can install`);
|
|
8909
|
+
return cli;
|
|
8910
|
+
}
|
|
8512
8911
|
const { commands } = cli;
|
|
8513
8912
|
const taken = Object.keys(builtInCommands(cli, {
|
|
8514
8913
|
bin: void 0,
|
|
@@ -8529,7 +8928,10 @@ function agentCli() {
|
|
|
8529
8928
|
}
|
|
8530
8929
|
//#endregion
|
|
8531
8930
|
//#region ../agent-cli/dist/error-table.js
|
|
8532
|
-
/**
|
|
8931
|
+
/**
|
|
8932
|
+
* Keeps each code's literal class and `details` schema, so `cliError` can type its throw sites;
|
|
8933
|
+
* `print` must name a string key of the code's `details`.
|
|
8934
|
+
*/
|
|
8533
8935
|
function defineErrors(table) {
|
|
8534
8936
|
return table;
|
|
8535
8937
|
}
|
|
@@ -8675,7 +9077,7 @@ function examplesSection(examples, style) {
|
|
|
8675
9077
|
}
|
|
8676
9078
|
/** The CLI's own help: a listing or its single Command's help, ending with exit codes and examples. */
|
|
8677
9079
|
function cliHelp(cli, root, style) {
|
|
8678
|
-
return lines(...root.kind === "command" ? commandLines(root, style, { examples: false }) : listingLines(`${
|
|
9080
|
+
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
9081
|
}
|
|
8680
9082
|
function groupHelp(group, style) {
|
|
8681
9083
|
return lines(...listingLines(group.commandLine, group, style));
|
|
@@ -8773,12 +9175,13 @@ function errorsData(errors) {
|
|
|
8773
9175
|
details: declaration.details === void 0 ? null : toJSONSchema(declaration.details, { unrepresentable: "any" })
|
|
8774
9176
|
}]));
|
|
8775
9177
|
}
|
|
8776
|
-
function cliHelpJson({ version, description, errors }, root) {
|
|
9178
|
+
function cliHelpJson({ name, version, description, errors }, root) {
|
|
8777
9179
|
return printed$1({
|
|
8778
|
-
name
|
|
9180
|
+
name,
|
|
8779
9181
|
version,
|
|
8780
9182
|
description,
|
|
8781
9183
|
...nodeData(root),
|
|
9184
|
+
name,
|
|
8782
9185
|
errors: errorsData(errors ?? {})
|
|
8783
9186
|
});
|
|
8784
9187
|
}
|
|
@@ -8806,7 +9209,14 @@ function printed(value, render, { json, invocation, style, fields }) {
|
|
|
8806
9209
|
}
|
|
8807
9210
|
/** A progress message as stderr carries it: one JSON object per line under `--json` (ADR 0006). */
|
|
8808
9211
|
function progressLine(message, json) {
|
|
8809
|
-
return
|
|
9212
|
+
return stderrLine("progress", message, json);
|
|
9213
|
+
}
|
|
9214
|
+
/** A Notice as stderr carries it, a JSON line like progress under `--json`. */
|
|
9215
|
+
function noticeLine(text, json) {
|
|
9216
|
+
return stderrLine("notice", text, json);
|
|
9217
|
+
}
|
|
9218
|
+
function stderrLine(key, text, json) {
|
|
9219
|
+
return json ? `${JSON.stringify({ [key]: text })}\n` : `${text}\n`;
|
|
8810
9220
|
}
|
|
8811
9221
|
function errorText(error, format) {
|
|
8812
9222
|
const text = formatRuntimeError(error, {
|
|
@@ -8827,7 +9237,7 @@ function startedOutput(outcome, format, spill) {
|
|
|
8827
9237
|
fields: void 0
|
|
8828
9238
|
}, spill);
|
|
8829
9239
|
}
|
|
8830
|
-
/** A serve Command stopped after it started:
|
|
9240
|
+
/** A serve Command stopped, or ended itself, after it started: that is how it is meant to end. */
|
|
8831
9241
|
const STOPPED_RESULT = {
|
|
8832
9242
|
stdout: "",
|
|
8833
9243
|
stderr: "",
|
|
@@ -9053,6 +9463,10 @@ function dryRunOutcome(command, plan) {
|
|
|
9053
9463
|
].join("\n")
|
|
9054
9464
|
};
|
|
9055
9465
|
}
|
|
9466
|
+
function serveContextOf(ctx, end) {
|
|
9467
|
+
const serveCtx = Object.create(ctx);
|
|
9468
|
+
return Object.assign(serveCtx, { end });
|
|
9469
|
+
}
|
|
9056
9470
|
async function execute(command, input, ctx, { dryRun, confirm, yes, page, invocation, commandLine, started, files, guardOutputs }) {
|
|
9057
9471
|
await checkTools(command.tools ?? [], ctx.env, ctx.commandLine());
|
|
9058
9472
|
if (command.rows !== void 0) return outcomeOf(await command.run({
|
|
@@ -9061,8 +9475,14 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, page, invoca
|
|
|
9061
9475
|
}, ctx), (data, style) => command.render(data, style));
|
|
9062
9476
|
if (command.effect === "read") return outcomeOf(await command.run(input, ctx), (data, style) => command.render(data, style));
|
|
9063
9477
|
if (command.effect === "serve") {
|
|
9064
|
-
|
|
9065
|
-
|
|
9478
|
+
let end = () => {};
|
|
9479
|
+
const ended = new Promise((resolve) => {
|
|
9480
|
+
end = resolve;
|
|
9481
|
+
});
|
|
9482
|
+
const outcome = outcomeOf(await command.start(input, serveContextOf(ctx, end)), (value, style) => command.render(value, style));
|
|
9483
|
+
await started(outcome);
|
|
9484
|
+
await Promise.race([ended, untilAborted(ctx.signal)]);
|
|
9485
|
+
return outcome;
|
|
9066
9486
|
}
|
|
9067
9487
|
const plan = await command.plan(input, ctx);
|
|
9068
9488
|
if (dryRun) return dryRunOutcome(command, plan);
|
|
@@ -9404,6 +9824,13 @@ function selfOf(name, bin) {
|
|
|
9404
9824
|
bin
|
|
9405
9825
|
];
|
|
9406
9826
|
}
|
|
9827
|
+
const unfakedSpawnDetached = async () => {
|
|
9828
|
+
throw new Error("runCli was given a fake spawn but no spawnDetached: pass spawnDetached to runCli, so the test starts no real child");
|
|
9829
|
+
};
|
|
9830
|
+
function spawnDetachedOf({ spawn, spawnDetached }) {
|
|
9831
|
+
if (spawnDetached !== void 0) return spawnDetached;
|
|
9832
|
+
return spawn === void 0 ? realSpawnDetached : unfakedSpawnDetached;
|
|
9833
|
+
}
|
|
9407
9834
|
function portsOf(cli, replacement) {
|
|
9408
9835
|
if (replacement !== void 0) return () => replacement;
|
|
9409
9836
|
return cli.ports ?? (() => ({}));
|
|
@@ -9437,11 +9864,19 @@ function portsBaseOf(scope, signal) {
|
|
|
9437
9864
|
}, {
|
|
9438
9865
|
fileError,
|
|
9439
9866
|
callerLine
|
|
9867
|
+
}),
|
|
9868
|
+
spawnDetached: contextSpawnDetached(spawnDetachedOf(options), {
|
|
9869
|
+
cwd,
|
|
9870
|
+
env,
|
|
9871
|
+
signal
|
|
9872
|
+
}, {
|
|
9873
|
+
fileError,
|
|
9874
|
+
callerLine
|
|
9440
9875
|
})
|
|
9441
9876
|
};
|
|
9442
9877
|
}
|
|
9443
9878
|
function contextOf(portsBase, control, parts) {
|
|
9444
|
-
const { ports, project, force, commandLine, deliver } = parts;
|
|
9879
|
+
const { ports, project, force, commandLine, deliver, notice } = parts;
|
|
9445
9880
|
return {
|
|
9446
9881
|
...portsBase,
|
|
9447
9882
|
deadline: (ms) => control.deadline(ms),
|
|
@@ -9454,7 +9889,8 @@ function contextOf(portsBase, control, parts) {
|
|
|
9454
9889
|
force,
|
|
9455
9890
|
commandLine,
|
|
9456
9891
|
poll: (check, { intervalMs, signal: stop }) => poll(portsBase.clock, check, intervalMs, stop === void 0 ? control.signal : AbortSignal.any([control.signal, stop])),
|
|
9457
|
-
deliver
|
|
9892
|
+
deliver,
|
|
9893
|
+
notice
|
|
9458
9894
|
};
|
|
9459
9895
|
}
|
|
9460
9896
|
/** Leaves the Side task running, so the caller writes the result before it waits for it. */
|
|
@@ -9571,6 +10007,7 @@ async function runCommand(cli, routed, options, write, baseFormat) {
|
|
|
9571
10007
|
project,
|
|
9572
10008
|
force: flags.force,
|
|
9573
10009
|
commandLine: typedLine,
|
|
10010
|
+
notice: (text) => write.stderr(noticeLine(text, routed.json)),
|
|
9574
10011
|
deliver: (data) => {
|
|
9575
10012
|
delivered = deliveredOutcome(command, data);
|
|
9576
10013
|
}
|
|
@@ -9586,22 +10023,30 @@ async function runCommand(cli, routed, options, write, baseFormat) {
|
|
|
9586
10023
|
let served = false;
|
|
9587
10024
|
let settled;
|
|
9588
10025
|
try {
|
|
10026
|
+
const outcome = await control.settle(noticeSkillCopy(cli, {
|
|
10027
|
+
builtIn,
|
|
10028
|
+
bin: options.bin
|
|
10029
|
+
}, ctx).then(() => execute(command, input, ctx, {
|
|
10030
|
+
dryRun: flags.dryRun,
|
|
10031
|
+
confirm: flags.confirm,
|
|
10032
|
+
yes: flags.yes,
|
|
10033
|
+
page: flags.page,
|
|
10034
|
+
invocation,
|
|
10035
|
+
commandLine,
|
|
10036
|
+
started: async (outcome) => {
|
|
10037
|
+
write.stdout(await startedOutput(outcome, format, spill));
|
|
10038
|
+
served = true;
|
|
10039
|
+
},
|
|
10040
|
+
files,
|
|
10041
|
+
guardOutputs: outputs.guard
|
|
10042
|
+
})));
|
|
10043
|
+
if (served) return {
|
|
10044
|
+
result: STOPPED_RESULT,
|
|
10045
|
+
finishSideTask
|
|
10046
|
+
};
|
|
9589
10047
|
settled = {
|
|
9590
10048
|
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
|
-
}))
|
|
10049
|
+
outcome
|
|
9605
10050
|
};
|
|
9606
10051
|
} catch (error) {
|
|
9607
10052
|
if (served && control.signal.aborted) return {
|
|
@@ -9693,12 +10138,13 @@ async function runWithProgress(cli, argv, options, write, stopNotice) {
|
|
|
9693
10138
|
return result;
|
|
9694
10139
|
}
|
|
9695
10140
|
async function routeAndRun(cli, argv, options, write, stopNotice) {
|
|
10141
|
+
const runAs = runAsOf(cli);
|
|
9696
10142
|
const format = {
|
|
9697
10143
|
cliName: cli.name,
|
|
9698
10144
|
env: options.env,
|
|
9699
10145
|
style: styleFor(options.env, options.stdoutIsTTY === true),
|
|
9700
10146
|
invocation: {
|
|
9701
|
-
|
|
10147
|
+
runAs,
|
|
9702
10148
|
argv,
|
|
9703
10149
|
flags: []
|
|
9704
10150
|
},
|
|
@@ -9707,9 +10153,9 @@ async function routeAndRun(cli, argv, options, write, stopNotice) {
|
|
|
9707
10153
|
stop: options.signal
|
|
9708
10154
|
};
|
|
9709
10155
|
const root = cli.commands === void 0 ? {
|
|
9710
|
-
...commandSpec(
|
|
10156
|
+
...commandSpec(runAs, cli.command),
|
|
9711
10157
|
description: cli.description
|
|
9712
|
-
} : groupSpec(
|
|
10158
|
+
} : groupSpec(runAs, cli.commands, builtInCommands(cli, {
|
|
9713
10159
|
bin: options.bin,
|
|
9714
10160
|
node: options.node
|
|
9715
10161
|
}));
|
|
@@ -9776,16 +10222,131 @@ async function runProcess(cli) {
|
|
|
9776
10222
|
}
|
|
9777
10223
|
//#endregion
|
|
9778
10224
|
//#region package.json
|
|
9779
|
-
var version$1 = "0.
|
|
10225
|
+
var version$1 = "0.15.0";
|
|
9780
10226
|
var description = "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.";
|
|
9781
10227
|
//#endregion
|
|
9782
10228
|
//#region src/build.ts
|
|
9783
10229
|
const build = {
|
|
9784
|
-
"sha": "
|
|
10230
|
+
"sha": "cd3df50",
|
|
9785
10231
|
"dirty": false,
|
|
9786
|
-
"builtAt": "2026-10-
|
|
10232
|
+
"builtAt": "2026-10-27T04:23:15.000Z"
|
|
9787
10233
|
};
|
|
9788
10234
|
//#endregion
|
|
10235
|
+
//#region src/fill.ts
|
|
10236
|
+
/** `text` with each `{{key}}` replaced by its value; `quoted` escapes values that land inside a double-quoted string. */
|
|
10237
|
+
function fill(text, values, quoted) {
|
|
10238
|
+
return text.replaceAll(/\{\{(\w+)\}\}/g, (placeholder, key) => {
|
|
10239
|
+
const value = Object.hasOwn(values, key) ? values[key] : void 0;
|
|
10240
|
+
if (value === void 0) throw new Error(`template placeholder ${placeholder} has no value`);
|
|
10241
|
+
return quoted ? JSON.stringify(value).slice(1, -1) : value;
|
|
10242
|
+
});
|
|
10243
|
+
}
|
|
10244
|
+
//#endregion
|
|
10245
|
+
//#region src/install-mode.ts
|
|
10246
|
+
const PARTIALS_DIR = fileURLToPath(new URL("../partials/", import.meta.url));
|
|
10247
|
+
const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
10248
|
+
const COMMANDS_ANCHOR = " commands: {";
|
|
10249
|
+
const GLOBAL_INSTALL = ` install: {
|
|
10250
|
+
mode: "global",
|
|
10251
|
+
package: manifest.name,
|
|
10252
|
+
registry: manifest.publishConfig.registry,
|
|
10253
|
+
},
|
|
10254
|
+
`;
|
|
10255
|
+
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. -->";
|
|
10256
|
+
function installModeOf(skillHub, dependency, binName) {
|
|
10257
|
+
if (skillHub !== void 0 && dependency) throw new UsageError({
|
|
10258
|
+
code: "SKILL_HOMES_CONFLICT",
|
|
10259
|
+
message: "--skill-hub and --dependency each name where the skill comes from: a Dependency install ships it inside the package",
|
|
10260
|
+
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"
|
|
10261
|
+
});
|
|
10262
|
+
if ((skillHub !== void 0 || dependency) && !SKILL_NAME.test(binName)) throw new UsageError({
|
|
10263
|
+
code: "SKILL_NAME_INVALID",
|
|
10264
|
+
message: `${binName} cannot name a skill, whose names are lowercase letters, digits and single hyphens`,
|
|
10265
|
+
hint: `choose a name such as my-cli, or leave out ${dependency ? "--dependency" : "--skill-hub"}`
|
|
10266
|
+
});
|
|
10267
|
+
return dependency ? { mode: "dependency" } : {
|
|
10268
|
+
mode: "global",
|
|
10269
|
+
skillHub
|
|
10270
|
+
};
|
|
10271
|
+
}
|
|
10272
|
+
async function partialsOf(mode, values) {
|
|
10273
|
+
const partial = async (name) => fill(await readFile(join(PARTIALS_DIR, mode, `${name}.md`), "utf8"), values, false).trimEnd();
|
|
10274
|
+
const [installLines, skillCheck, installSteps, updateSteps, sandbox] = await Promise.all([
|
|
10275
|
+
partial("installLines"),
|
|
10276
|
+
partial("skillCheck"),
|
|
10277
|
+
partial("installSteps"),
|
|
10278
|
+
partial("updateSteps"),
|
|
10279
|
+
partial("sandbox")
|
|
10280
|
+
]);
|
|
10281
|
+
return {
|
|
10282
|
+
installLines,
|
|
10283
|
+
skillCheck,
|
|
10284
|
+
installSteps,
|
|
10285
|
+
updateSteps,
|
|
10286
|
+
sandbox
|
|
10287
|
+
};
|
|
10288
|
+
}
|
|
10289
|
+
async function globalSetup(skillHub, packageName, binName) {
|
|
10290
|
+
const skillLine = skillHub === void 0 ? NO_SKILL_HUB : `\`! npx -y skills add ${skillHub} --skill ${binName} -g\``;
|
|
10291
|
+
const runAs = runAsOf({ name: binName });
|
|
10292
|
+
const url = JSON.stringify(skillHub);
|
|
10293
|
+
return {
|
|
10294
|
+
values: {
|
|
10295
|
+
runAs,
|
|
10296
|
+
doctorBin: "",
|
|
10297
|
+
...await partialsOf("global", {
|
|
10298
|
+
packageName,
|
|
10299
|
+
binName,
|
|
10300
|
+
runAs,
|
|
10301
|
+
skillLine
|
|
10302
|
+
})
|
|
10303
|
+
},
|
|
10304
|
+
edits: skillHub === void 0 ? [] : [{
|
|
10305
|
+
file: "src/cli.ts",
|
|
10306
|
+
find: COMMANDS_ANCHOR,
|
|
10307
|
+
replace: ` skill: { source: ${url} },\n${COMMANDS_ANCHOR}`
|
|
10308
|
+
}, {
|
|
10309
|
+
file: "scripts/release.ts",
|
|
10310
|
+
find: " smoke: {",
|
|
10311
|
+
replace: ` skillHub: ${url},\n smoke: {`
|
|
10312
|
+
}],
|
|
10313
|
+
files: []
|
|
10314
|
+
};
|
|
10315
|
+
}
|
|
10316
|
+
async function dependencySetup(packageName, binName) {
|
|
10317
|
+
const runAs = runAsOf({
|
|
10318
|
+
name: binName,
|
|
10319
|
+
install: {
|
|
10320
|
+
mode: "dependency",
|
|
10321
|
+
package: packageName
|
|
10322
|
+
}
|
|
10323
|
+
});
|
|
10324
|
+
return {
|
|
10325
|
+
values: {
|
|
10326
|
+
runAs,
|
|
10327
|
+
doctorBin: "src/bin.ts",
|
|
10328
|
+
...await partialsOf("dependency", {
|
|
10329
|
+
packageName,
|
|
10330
|
+
binName,
|
|
10331
|
+
runAs
|
|
10332
|
+
})
|
|
10333
|
+
},
|
|
10334
|
+
edits: [{
|
|
10335
|
+
file: "src/cli.ts",
|
|
10336
|
+
find: GLOBAL_INSTALL,
|
|
10337
|
+
replace: ` install: { mode: "dependency", package: manifest.name },\n`
|
|
10338
|
+
}, {
|
|
10339
|
+
file: "src/cli.ts",
|
|
10340
|
+
find: COMMANDS_ANCHOR,
|
|
10341
|
+
replace: ` skill: { packaged: "skills/${binName}" },\n${COMMANDS_ANCHOR}`
|
|
10342
|
+
}],
|
|
10343
|
+
files: ["skills"]
|
|
10344
|
+
};
|
|
10345
|
+
}
|
|
10346
|
+
async function installSetupOf(install, packageName, binName) {
|
|
10347
|
+
return install.mode === "global" ? globalSetup(install.skillHub, packageName, binName) : dependencySetup(packageName, binName);
|
|
10348
|
+
}
|
|
10349
|
+
//#endregion
|
|
9789
10350
|
//#region src/steps.ts
|
|
9790
10351
|
const stepErrors = defineErrors({ STEP_FAILED: { class: "operator" } });
|
|
9791
10352
|
const INSTALL = [{
|
|
@@ -9895,13 +10456,6 @@ async function templateFiles(values) {
|
|
|
9895
10456
|
}));
|
|
9896
10457
|
}
|
|
9897
10458
|
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
10459
|
async function writeTemplate(root, files, values) {
|
|
9906
10460
|
for (const file of files) {
|
|
9907
10461
|
const text = await readFile(join(TEMPLATE_DIR, file.source), "utf8");
|
|
@@ -9917,17 +10471,6 @@ const version = version$1;
|
|
|
9917
10471
|
//#region src/create.ts
|
|
9918
10472
|
const { defineCommand } = agentCli();
|
|
9919
10473
|
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
10474
|
function errorCode(error) {
|
|
9932
10475
|
return error instanceof Error && "code" in error ? error.code : void 0;
|
|
9933
10476
|
}
|
|
@@ -9940,30 +10483,24 @@ async function isFree(path) {
|
|
|
9940
10483
|
throw error;
|
|
9941
10484
|
}
|
|
9942
10485
|
}
|
|
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
|
-
}
|
|
10486
|
+
async function applyEdit(root, { file, find, replace }) {
|
|
10487
|
+
const path = join(root, file);
|
|
10488
|
+
const text = await readFile(path, "utf8");
|
|
10489
|
+
if (!text.includes(find)) throw new Error(`the template's ${file} has no \`${find.trim()}\` to replace`);
|
|
10490
|
+
await writeFile(path, text.replace(find, replace));
|
|
9953
10491
|
}
|
|
9954
10492
|
const MANIFEST = record(string(), unknown());
|
|
9955
|
-
async function
|
|
10493
|
+
async function editManifest(root, edit) {
|
|
9956
10494
|
const path = join(root, "package.json");
|
|
9957
10495
|
const manifest = MANIFEST.parse(JSON.parse(await readFile(path, "utf8")));
|
|
9958
|
-
const entries = Object.entries(manifest)
|
|
10496
|
+
const entries = edit(Object.entries(manifest));
|
|
9959
10497
|
await writeFile(path, `${JSON.stringify(Object.fromEntries(entries), null, 2)}\n`);
|
|
9960
10498
|
}
|
|
9961
|
-
function
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
});
|
|
10499
|
+
async function setRepository(root, repository) {
|
|
10500
|
+
await editManifest(root, (entries) => entries.flatMap((entry) => entry[0] === "description" ? [entry, ["repository", repository]] : [entry]));
|
|
10501
|
+
}
|
|
10502
|
+
async function addFiles(root, files) {
|
|
10503
|
+
await editManifest(root, (entries) => entries.map(([key, value]) => key === "files" && Array.isArray(value) ? [key, [...value, ...files]] : [key, value]));
|
|
9967
10504
|
}
|
|
9968
10505
|
async function ensureFree(path) {
|
|
9969
10506
|
if (!await isFree(path)) throw new UsageError({
|
|
@@ -9986,20 +10523,22 @@ const create = defineCommand({
|
|
|
9986
10523
|
install: boolean().default(true).describe("run pnpm install"),
|
|
9987
10524
|
git: boolean().default(true).describe("create a git repository with an initial commit"),
|
|
9988
10525
|
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")
|
|
10526
|
+
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"),
|
|
10527
|
+
dependency: boolean().default(false).describe("install the CLI per project, as a devDependency, with its skill shipped inside the package")
|
|
9990
10528
|
}),
|
|
9991
|
-
plan: async ({ name, description, install, git, repository, skillHub }, ctx) => {
|
|
10529
|
+
plan: async ({ name, description, install, git, repository, skillHub, dependency }, ctx) => {
|
|
9992
10530
|
const binName = PACKAGE_NAME.exec(name)?.[1] ?? name;
|
|
9993
|
-
|
|
10531
|
+
const mode = installModeOf(skillHub, dependency, binName);
|
|
9994
10532
|
const path = resolve(ctx.cwd, binName);
|
|
9995
10533
|
await ensureFree(path);
|
|
10534
|
+
const setup = await installSetupOf(mode, name, binName);
|
|
9996
10535
|
const values = {
|
|
9997
10536
|
packageName: name,
|
|
9998
10537
|
binName,
|
|
9999
10538
|
description,
|
|
10000
10539
|
debugVariable: debugVariable(binName),
|
|
10001
10540
|
runtimeVersion: version,
|
|
10002
|
-
|
|
10541
|
+
...setup.values
|
|
10003
10542
|
};
|
|
10004
10543
|
return {
|
|
10005
10544
|
path,
|
|
@@ -10011,14 +10550,16 @@ const create = defineCommand({
|
|
|
10011
10550
|
}),
|
|
10012
10551
|
install,
|
|
10013
10552
|
repository,
|
|
10014
|
-
|
|
10553
|
+
edits: setup.edits,
|
|
10554
|
+
manifestFiles: setup.files
|
|
10015
10555
|
};
|
|
10016
10556
|
},
|
|
10017
10557
|
apply: async (plan, ctx) => {
|
|
10018
10558
|
await mkdir(plan.path, { recursive: true });
|
|
10019
10559
|
await writeTemplate(plan.path, plan.files, plan.values);
|
|
10020
10560
|
if (plan.repository !== void 0) await setRepository(plan.path, plan.repository);
|
|
10021
|
-
|
|
10561
|
+
for (const edit of plan.edits) await applyEdit(plan.path, edit);
|
|
10562
|
+
if (plan.manifestFiles.length > 0) await addFiles(plan.path, plan.manifestFiles);
|
|
10022
10563
|
await runSteps(plan.steps, plan.path, ctx);
|
|
10023
10564
|
return {
|
|
10024
10565
|
path: plan.path,
|