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