@duyquangnvx/create-agent-cli 0.14.0 → 0.15.1

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/bin.js +789 -679
  3. package/package.json +2 -2
package/dist/bin.js CHANGED
@@ -5869,13 +5869,22 @@ function debugTrace(error, format) {
5869
5869
  function errorsOf(error) {
5870
5870
  return error instanceof AggregateError && error.errors.length > 0 ? error.errors.flatMap(errorsOf) : [error];
5871
5871
  }
5872
+ /** The `details` field that `error`'s code declares for text mode to print, when it holds a string. */
5873
+ function printedDetail(error, format) {
5874
+ if (!(error instanceof CliError)) return void 0;
5875
+ const key = format.errors[error.code]?.print;
5876
+ const { details } = error;
5877
+ if (key === void 0 || typeof details !== "object" || details === null) return void 0;
5878
+ const value = Reflect.get(details, key);
5879
+ return typeof value === "string" ? value : void 0;
5880
+ }
5872
5881
  function errorLines(error, format) {
5873
5882
  const fields = fieldsOf$1(error, format);
5874
- const stack = debugTrace(error, format);
5883
+ const blocks = [printedDetail(error, format), debugTrace(error, format)].filter((block) => block !== void 0);
5875
5884
  const { style } = format;
5876
5885
  const label = style.red(format.isBug(error) ? "internal error:" : "error:");
5877
- const trace = stack === void 0 ? "" : `${stack}\n`;
5878
- return `${label} ${fields.message}\n${trace}${style.cyan("hint:")} ${fields.hint}\n`;
5886
+ const between = blocks.map((block) => `${block}\n`).join("");
5887
+ return `${label} ${fields.message}\n${between}${style.cyan("hint:")} ${fields.hint}\n`;
5879
5888
  }
5880
5889
  function formatRuntimeError(error, format) {
5881
5890
  const errors = errorsOf(error);
@@ -6487,15 +6496,6 @@ function isCommand(node) {
6487
6496
  return typeof node.effect === "string";
6488
6497
  }
6489
6498
  //#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
6499
6499
  //#region ../agent-cli/dist/project.js
6500
6500
  const PROJECT_INVALID = "PROJECT_INVALID";
6501
6501
  function invalid(path, problem, callerLine, options) {
@@ -6610,6 +6610,69 @@ function versionLine({ version, build }) {
6610
6610
  return build === void 0 ? version : `${version} (${build.sha}${build.dirty ? "+dirty" : ""}) built ${build.builtAt}`;
6611
6611
  }
6612
6612
  //#endregion
6613
+ //#region ../agent-cli/dist/project-root.js
6614
+ const MANAGERS = [
6615
+ "pnpm",
6616
+ "npm",
6617
+ "yarn",
6618
+ "bun"
6619
+ ];
6620
+ const LOCKFILES = [
6621
+ ["pnpm-lock.yaml", "pnpm"],
6622
+ ["package-lock.json", "npm"],
6623
+ ["yarn.lock", "yarn"],
6624
+ ["bun.lock", "bun"],
6625
+ ["bun.lockb", "bun"]
6626
+ ];
6627
+ const manifestSchema$1 = object({
6628
+ packageManager: string().optional(),
6629
+ dependencies: record(string(), unknown()).optional(),
6630
+ devDependencies: record(string(), unknown()).optional()
6631
+ });
6632
+ /** `dir` and every folder above it, nearest first. */
6633
+ function ancestorsOf(dir) {
6634
+ const parent = dirname(dir);
6635
+ return parent === dir ? [dir] : [dir, ...ancestorsOf(parent)];
6636
+ }
6637
+ async function exists(path) {
6638
+ return await orMissing(stat(path)) !== void 0;
6639
+ }
6640
+ /** Whether `path` lies strictly inside `dir`. */
6641
+ function isInside(dir, path) {
6642
+ const inner = relative(dir, path);
6643
+ return inner !== "" && !inner.startsWith("..") && !isAbsolute(inner);
6644
+ }
6645
+ async function manifestIn(dir) {
6646
+ let text;
6647
+ try {
6648
+ text = await readFile(join(dir, "package.json"), "utf8");
6649
+ } catch (error) {
6650
+ if (isMissingFile(error)) return void 0;
6651
+ throw error;
6652
+ }
6653
+ let manifest;
6654
+ try {
6655
+ manifest = JSON.parse(text);
6656
+ } catch {
6657
+ return;
6658
+ }
6659
+ return manifestSchema$1.safeParse(manifest).data;
6660
+ }
6661
+ async function declaredManager(dir) {
6662
+ const name = (await manifestIn(dir))?.packageManager?.split("@")[0];
6663
+ return MANAGERS.find((manager) => manager === name);
6664
+ }
6665
+ async function lockfileRootOf(cwd) {
6666
+ for (const dir of ancestorsOf(cwd)) for (const [file, kind] of LOCKFILES) if (await exists(join(dir, file))) return {
6667
+ dir,
6668
+ manager: await declaredManager(dir) ?? kind
6669
+ };
6670
+ }
6671
+ /** The package manager's project root: the folder of the nearest lockfile, or `cwd` without one. */
6672
+ async function projectRootOf(cwd) {
6673
+ return (await lockfileRootOf(cwd))?.dir ?? cwd;
6674
+ }
6675
+ //#endregion
6613
6676
  //#region ../agent-cli/dist/tail.js
6614
6677
  /** The last `count` lines of a child's output, trimmed; empty when it wrote nothing. */
6615
6678
  function lastLines(output, count) {
@@ -6994,7 +7057,6 @@ function npmContext(cli, registry, ctx, node, cwd) {
6994
7057
  node
6995
7058
  };
6996
7059
  }
6997
- const NPMJS = "https://registry.npmjs.org/";
6998
7060
  const STDERR_TAIL_LINES = 10;
6999
7061
  const RM_RETRIES = 3;
7000
7062
  const NETWORK_CODES = /* @__PURE__ */ new Set([
@@ -7013,28 +7075,6 @@ const NPM_CODE = /^npm (?:error|ERR!) code (\S+)$/m;
7013
7075
  function scopeOf(name) {
7014
7076
  return /^(@[^/]+)\//.exec(name)?.[1];
7015
7077
  }
7016
- /** The flag that points npm at the CLI's registry; none for npmjs, npm's default. */
7017
- function registryFlags({ package: name, registry }) {
7018
- if (registry === NPMJS) return [];
7019
- const scope = scopeOf(name);
7020
- return [scope === void 0 ? `--registry=${registry}` : `--${scope}:registry=${registry}`];
7021
- }
7022
- /** The registry a Global install declares. */
7023
- function globalRegistry(install) {
7024
- const { package: name, registry } = install;
7025
- return {
7026
- package: name,
7027
- label: registry,
7028
- flags: registryFlags(install),
7029
- unreachableHint: "check the network or VPN",
7030
- loginHint: `log in to ${registry} (\`npm login --registry=${registry}\`) or set its token in ~/.npmrc`,
7031
- missingHint: `check that ${name} is published on ${registry}`
7032
- };
7033
- }
7034
- /** npm for a Global install, run where the CLI was started. */
7035
- function globalNpm(cli, install, ctx, node) {
7036
- return npmContext(cli, globalRegistry(install), ctx, node, process.cwd());
7037
- }
7038
7078
  /** The command a person runs to install the CLI globally from its registry. */
7039
7079
  function installCommandLine(registry, version = "latest") {
7040
7080
  return [
@@ -7177,62 +7217,13 @@ async function latestVersion(ctx) {
7177
7217
  return version;
7178
7218
  }
7179
7219
  //#endregion
7180
- //#region ../agent-cli/dist/built-ins/global-install.js
7181
- const NOT_GLOBAL_INSTALL = "NOT_GLOBAL_INSTALL";
7182
- /** Where npm puts global packages under its global prefix. */
7183
- function globalModulesDir(prefix) {
7184
- return process.platform === "win32" ? join(prefix, "node_modules") : join(prefix, "lib", "node_modules");
7185
- }
7186
- function notGlobal(ctx, message) {
7187
- return new CliError({
7188
- code: NOT_GLOBAL_INSTALL,
7189
- message,
7190
- hint: `install it with npm, so \`${ctx.cliName} update\` can replace it: ${installCommandLine(ctx.registry)}`
7191
- });
7192
- }
7193
- async function realpathOrUndefined(path) {
7194
- return realpath(path).catch((error) => {
7195
- if (isMissingFile(error)) return void 0;
7196
- throw error;
7197
- });
7198
- }
7199
- /** Whether `path` lies strictly inside `dir`. */
7200
- function isInside(dir, path) {
7201
- const inner = relative(dir, path);
7202
- return inner !== "" && !inner.startsWith("..") && !isAbsolute(inner);
7203
- }
7204
- /**
7205
- * The global npm install this copy runs from, found by asking npm for its global prefix.
7206
- * `bin` is the script that started the CLI; throws `NOT_GLOBAL_INSTALL` when it is not inside that install.
7207
- */
7208
- async function globalInstallOf(bin, ctx) {
7209
- if (bin === void 0) throw notGlobal(ctx, `${ctx.cliName} runs from source, not from an npm install`);
7210
- const prefix = await globalPrefix(ctx);
7211
- const packageDir = join(globalModulesDir(prefix), ctx.registry.package);
7212
- const installed = await realpathOrUndefined(packageDir);
7213
- const here = await realpathOrUndefined(bin) ?? bin;
7214
- if (installed === void 0 || !isInside(installed, here)) throw notGlobal(ctx, `${ctx.cliName} runs from ${here}, not from npm's global install at ${packageDir}`);
7215
- if ((await lstat(packageDir)).isSymbolicLink()) throw notGlobal(ctx, `${ctx.cliName} runs from a link to ${installed} (npm link), not from an npm install`);
7216
- return {
7217
- prefix,
7218
- packageDir
7219
- };
7220
+ //#region ../agent-cli/dist/built-ins/install/run-as.js
7221
+ /** The Run-as: the bare name, or `npx -q --no-install <bin>` for a Dependency install (ADR 0011). */
7222
+ function runAsOf({ name, install }) {
7223
+ return install?.mode === "dependency" ? `npx -q --no-install ${name}` : name;
7220
7224
  }
7221
7225
  //#endregion
7222
- //#region ../agent-cli/dist/built-ins/dependency-install.js
7223
- const MANAGERS = [
7224
- "pnpm",
7225
- "npm",
7226
- "yarn",
7227
- "bun"
7228
- ];
7229
- const LOCKFILES = [
7230
- ["pnpm-lock.yaml", "pnpm"],
7231
- ["package-lock.json", "npm"],
7232
- ["yarn.lock", "yarn"],
7233
- ["bun.lock", "bun"],
7234
- ["bun.lockb", "bun"]
7235
- ];
7226
+ //#region ../agent-cli/dist/built-ins/install/dependency.js
7236
7227
  const LINES = {
7237
7228
  pnpm: {
7238
7229
  upgrade: (packages) => `pnpm up --latest ${packages.join(" ")}`,
@@ -7245,46 +7236,7 @@ const LINES = {
7245
7236
  install: "npm install"
7246
7237
  }
7247
7238
  };
7248
- const manifestSchema$1 = object({
7249
- packageManager: string().optional(),
7250
- dependencies: record(string(), unknown()).optional(),
7251
- devDependencies: record(string(), unknown()).optional()
7252
- });
7253
7239
  const packageSchema = object({ version: string() });
7254
- /** `dir` and every folder above it, nearest first. */
7255
- function ancestorsOf(dir) {
7256
- const parent = dirname(dir);
7257
- return parent === dir ? [dir] : [dir, ...ancestorsOf(parent)];
7258
- }
7259
- async function exists(path) {
7260
- try {
7261
- await stat(path);
7262
- return true;
7263
- } catch (error) {
7264
- if (isMissingFile(error)) return false;
7265
- throw error;
7266
- }
7267
- }
7268
- async function manifestIn(dir) {
7269
- let text;
7270
- try {
7271
- text = await readFile(join(dir, "package.json"), "utf8");
7272
- } catch (error) {
7273
- if (isMissingFile(error)) return void 0;
7274
- throw error;
7275
- }
7276
- let manifest;
7277
- try {
7278
- manifest = JSON.parse(text);
7279
- } catch {
7280
- return;
7281
- }
7282
- return manifestSchema$1.safeParse(manifest).data;
7283
- }
7284
- async function declaredManager(dir) {
7285
- const name = (await manifestIn(dir))?.packageManager?.split("@")[0];
7286
- return MANAGERS.find((manager) => manager === name);
7287
- }
7288
7240
  async function manifestListing(name, cwd, root) {
7289
7241
  const dirs = ancestorsOf(cwd);
7290
7242
  const top = root === void 0 ? dirs.length : dirs.indexOf(root.dir) + 1;
@@ -7293,16 +7245,6 @@ async function manifestListing(name, cwd, root) {
7293
7245
  if (manifest?.dependencies?.[name] !== void 0 || manifest?.devDependencies?.[name] !== void 0) return join(dir, "package.json");
7294
7246
  }
7295
7247
  }
7296
- async function lockfileRootOf(cwd) {
7297
- for (const dir of ancestorsOf(cwd)) for (const [file, kind] of LOCKFILES) if (await exists(join(dir, file))) return {
7298
- dir,
7299
- manager: await declaredManager(dir) ?? kind
7300
- };
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
- }
7306
7248
  async function versionIn(manifest) {
7307
7249
  try {
7308
7250
  return packageSchema.parse(JSON.parse(await readFile(manifest, "utf8"))).version;
@@ -7332,10 +7274,7 @@ async function standingOf(install, bin, cwd, root) {
7332
7274
  kind: "linked",
7333
7275
  copy
7334
7276
  };
7335
- const runningFrom = await realpath(bin).catch((error) => {
7336
- if (isMissingFile(error)) return bin;
7337
- throw error;
7338
- });
7277
+ const runningFrom = await orMissing(realpath(bin)) ?? bin;
7339
7278
  return isInside(copy.realDir, runningFrom) ? {
7340
7279
  kind: "installed",
7341
7280
  copy
@@ -7386,7 +7325,7 @@ function problem(message, fix) {
7386
7325
  };
7387
7326
  }
7388
7327
  /** The `install` row of a Dependency install. */
7389
- function dependencyInstallFinding(cli, install, { cwd, root, standing }) {
7328
+ function installFinding(cli, runAs, install, { cwd, root, standing }) {
7390
7329
  const lines = root === void 0 ? void 0 : LINES[root.manager];
7391
7330
  switch (standing.kind) {
7392
7331
  case "source":
@@ -7396,7 +7335,7 @@ function dependencyInstallFinding(cli, install, { cwd, root, standing }) {
7396
7335
  case "unreadable": return problem(`${standing.manifest} is not a package.json with a version: ${standing.reason}`, lines?.install);
7397
7336
  case "other": {
7398
7337
  const { copy, runningFrom } = standing;
7399
- return problem(`${cli.name} runs from ${runningFrom}, not from this project's ${install.package} ${copy.version} at ${copy.realDir}`, `${runAsOf(cli)} doctor`);
7338
+ return problem(`${cli.name} runs from ${runningFrom}, not from this project's ${install.package} ${copy.version} at ${copy.realDir}`, `${runAs} doctor`);
7400
7339
  }
7401
7340
  case "installed": {
7402
7341
  const { copy } = standing;
@@ -7426,16 +7365,37 @@ function adviceFor(install, root, latest) {
7426
7365
  if (lines !== void 0) return { fix: lines.upgrade(packages) };
7427
7366
  return { note: `move ${packages.length === 1 ? packages[0] : `${packages.slice(0, -1).join(", ")} and ${packages.at(-1)}`} to ${latest} with ${root?.manager ?? "the project's package manager"}` };
7428
7367
  }
7429
- function dependencyRelease(cli, install, { cwd, root, standing }) {
7430
- if (isUnchecked(standing)) return { skipped: skippedFor(cli, install, standing) };
7368
+ function checkOf$1(cli, install, runAs, { bin, node }) {
7369
+ let survey;
7370
+ const surveyed = (cwd) => {
7371
+ survey ??= surveyDependency(install, bin, cwd);
7372
+ return survey;
7373
+ };
7374
+ return {
7375
+ install: async (ctx) => installFinding(cli, runAs, install, await surveyed(ctx.cwd)),
7376
+ release: async (ctx) => {
7377
+ const { cwd, root, standing } = await surveyed(ctx.cwd);
7378
+ if (isUnchecked(standing)) return { skipped: skippedFor(cli, install, standing) };
7379
+ return {
7380
+ npm: npmContext(cli, registryOf(install, root?.dir), ctx, node, root?.dir ?? cwd),
7381
+ advise: (latest) => adviceFor(install, root, latest)
7382
+ };
7383
+ }
7384
+ };
7385
+ }
7386
+ /** The Install of a CLI each project pins as a dependency, run through npx (ADR 0011). */
7387
+ function dependencyInstall(cli, install) {
7388
+ const runAs = runAsOf(cli);
7431
7389
  return {
7432
- registry: registryOf(install, root?.dir),
7433
- cwd: root?.dir ?? cwd,
7434
- advise: (latest) => adviceFor(install, root, latest)
7390
+ runAs,
7391
+ builtIns: () => ({}),
7392
+ exitClasses: [],
7393
+ agentsCaveat: `and only from a package that has \`${install.package}\` in its dependencies; from anywhere else npx reports the package missing`,
7394
+ check: (launch) => checkOf$1(cli, install, runAs, launch)
7435
7395
  };
7436
7396
  }
7437
7397
  //#endregion
7438
- //#region ../agent-cli/dist/built-ins/skills.js
7398
+ //#region ../agent-cli/dist/built-ins/skill/hub.js
7439
7399
  const SKILL_DIRS = [[".claude", "skills"], [".agents", "skills"]];
7440
7400
  const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
7441
7401
  const METADATA_VERSION = /^metadata:[ \t]*\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+version:[ \t]*["']?([^"'\s]+)["']?[ \t]*$/m;
@@ -7493,411 +7453,123 @@ function fixFor(cliName, copies, source) {
7493
7453
  if (has("unversioned") && add !== void 0) return has("behind") ? `${update} && ${add}` : add;
7494
7454
  return has("behind") || has("unversioned") ? update : void 0;
7495
7455
  }
7496
- async function skillReport(cli, version, ctx) {
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;
7456
+ const SKILL_STATE_NOTES = {
7457
+ current: "the same as this build",
7458
+ behind: "older than this build",
7459
+ ahead: "newer than this build",
7460
+ unversioned: "states no metadata.version",
7461
+ unreadable: "could not be read"
7462
+ };
7463
+ function describeCopy({ path, version, state }) {
7464
+ return `${path}${version === void 0 ? "" : ` ${version}`} (${SKILL_STATE_NOTES[state]})`;
7465
+ }
7466
+ /** A skill that `npx skills add` installs from `source`, the Skill Hub; with no `source`, no line adds it. */
7467
+ function hubSkill(cli, source) {
7468
+ const findingAt = async (version, ctx) => {
7469
+ const copies = await skillCopies(cli.name, version, ctx);
7470
+ const fix = fixFor(cli.name, copies, source);
7471
+ const message = copies.length === 0 ? `no copy of the ${cli.name} skill is installed` : copies.map(describeCopy).join("; ");
7472
+ return fix === void 0 ? {
7473
+ state: "ok",
7474
+ message
7475
+ } : {
7476
+ state: "ok",
7477
+ message,
7478
+ fix
7479
+ };
7480
+ };
7499
7481
  return {
7500
- copies,
7501
- fix: fixFor(cli.name, copies, source)
7482
+ kind: "hub",
7483
+ builtIns: {},
7484
+ rows: [{
7485
+ name: "skill",
7486
+ advisory: true,
7487
+ run: (ctx) => findingAt(cli.version, ctx)
7488
+ }],
7489
+ notice: async () => {},
7490
+ findingAt
7502
7491
  };
7503
7492
  }
7504
7493
  //#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;
7494
+ //#region ../agent-cli/dist/built-ins/agents-md.js
7495
+ /** One paragraph telling every session that the CLI exists, how to run it and which skill to load. */
7496
+ function agentsBlock(cli, { runAs, agentsCaveat }) {
7497
+ const what = cli.description.replace(/\.$/, "");
7498
+ const run = `Run it as \`${runAs}\`${agentsCaveat === void 0 ? "" : `, ${agentsCaveat}`}`;
7499
+ return `\`${cli.name}\`: ${what}. ${run}. Load the \`${cli.name}\` skill before using it.`;
7509
7500
  }
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);
7501
+ function markersOf(name) {
7513
7502
  return {
7514
- root,
7515
- copy: join(root, ".claude", "skills", cli.name)
7503
+ begin: `<!-- BEGIN:${name} -->`,
7504
+ end: `<!-- END:${name} -->`
7516
7505
  };
7517
7506
  }
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();
7507
+ /** `text` with the CLI's block between its markers, appended when they are missing; nothing else changes. */
7508
+ function withBlock(cli, install, text) {
7509
+ const { begin, end } = markersOf(cli.name);
7510
+ const whole = `${begin}\n${agentsBlock(cli, install)}\n${end}`;
7511
+ const start = text.indexOf(begin);
7512
+ const stop = start === -1 ? -1 : text.indexOf(end, start);
7513
+ if (stop !== -1) return `${text.slice(0, start)}${whole}${text.slice(stop + end.length)}`;
7514
+ if (text === "") return `${whole}\n`;
7515
+ return `${text}${text.endsWith("\n") ? "" : "\n"}\n${whole}\n`;
7524
7516
  }
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;
7517
+ async function agentsMdAt(cli, install, root) {
7518
+ const path = join(root, "AGENTS.md");
7519
+ const existing = await orMissing(readFile(path, "utf8"));
7520
+ const block = agentsBlock(cli, install);
7521
+ if (existing === void 0) return {
7522
+ path,
7523
+ block,
7524
+ change: "create"
7525
+ };
7526
+ if (withBlock(cli, install, existing) === existing) return {
7527
+ path,
7528
+ block,
7529
+ change: null
7530
+ };
7530
7531
  return {
7531
- packageVersion,
7532
- copyVersion,
7533
- same: copyVersion === packageVersion
7532
+ path,
7533
+ block,
7534
+ change: existing.includes(markersOf(cli.name).begin) ? "rewrite" : "add"
7534
7535
  };
7535
7536
  }
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;
7537
+ async function handEditsAt(root) {
7538
+ const claudeMd = join(root, "CLAUDE.md");
7539
+ const text = await orMissing(readFile(claudeMd, "utf8"));
7540
+ if (text === void 0 || text.split(/\r?\n/).some((line) => line.trim() === "@AGENTS.md")) return [];
7541
+ return [`add a line \`@AGENTS.md\` to ${claudeMd}, so Claude Code reads AGENTS.md`];
7540
7542
  }
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
7585
- };
7586
- }
7587
- //#endregion
7588
- //#region ../agent-cli/dist/built-ins/doctor-rows.js
7589
- function ok(message) {
7590
- return {
7591
- state: "ok",
7592
- message
7593
- };
7594
- }
7595
- function noInstall(cli) {
7596
- return {
7597
- state: "skipped",
7598
- message: `not checked: ${cli.name} declares no install`
7599
- };
7600
- }
7601
- function buildRow(cli) {
7543
+ function agentsRow(cli, install) {
7602
7544
  return {
7603
- name: "build",
7604
- run: () => ok(versionLine(cli))
7605
- };
7606
- }
7607
- function nodeRow(cli) {
7608
- return {
7609
- name: "node",
7610
- run: () => {
7611
- const found = process.versions.node;
7612
- const range = cli.engines?.node;
7613
- if (range === void 0) return ok(`Node ${found}; ${cli.name} declares no engines.node to check it against`);
7614
- const minimum = engineMinimum(range);
7615
- if (minimum === void 0) return {
7545
+ name: "agents",
7546
+ advisory: true,
7547
+ run: async (ctx) => {
7548
+ const root = await projectRootOf(ctx.cwd);
7549
+ const { path, change } = await agentsMdAt(cli, install, root);
7550
+ const fix = `${install.runAs} skill install`;
7551
+ if (change === "rewrite") return {
7616
7552
  state: "problem",
7617
- message: `${cli.name} declares engines.node "${range}", which doctor cannot read: only >=X.Y.Z is supported`
7553
+ message: `${path} holds a ${cli.name} block other than the one ${fix} writes`,
7554
+ fix
7618
7555
  };
7619
- if (meetsMinimum(found, minimum)) return ok(`Node ${found} meets ${range}`);
7620
- const major = minimum[0];
7621
- return {
7556
+ if (change !== null) return {
7622
7557
  state: "problem",
7623
- message: `${cli.name} runs on Node ${found}, older than the ${range} it needs; install Node ${minimum.join(".")} or newer and make it the default`,
7624
- fix: `nvm install ${major} && nvm alias default ${major}`
7558
+ message: `${path} holds no ${cli.name} block, so sessions in this repo do not know ${cli.name}`,
7559
+ fix
7625
7560
  };
7626
- }
7627
- };
7628
- }
7629
- /** A row's thrown error as its problem: a `CliError` with its hint and code, anything else a bug. */
7630
- function problemOfError(name, error) {
7631
- if (error instanceof CliError) return {
7632
- state: "problem",
7633
- message: `${error.message}; ${error.hint}`,
7634
- code: error.code
7635
- };
7636
- return {
7637
- state: "problem",
7638
- message: `the ${name} row hit a bug: ${messageOf(error)}`
7639
- };
7640
- }
7641
- function readProject(projectFile, ctx) {
7642
- if (projectFile === void 0) return {
7643
- ok: true,
7644
- project: void 0
7645
- };
7646
- try {
7647
- return {
7648
- ok: true,
7649
- project: projectOf(projectFile, ctx.cwd, {
7650
- fileError: ctx.fileError,
7651
- callerLine: ctx.commandLine()
7652
- })
7653
- };
7654
- } catch (error) {
7655
- return {
7656
- ok: false,
7657
- error
7658
- };
7659
- }
7660
- }
7661
- function projectRow({ file }, read) {
7662
- return {
7663
- name: "project",
7664
- run: (ctx) => {
7665
- if (!read.ok) return problemOfError("project", read.error);
7666
- return ok(read.project === void 0 ? `none found: no ${file} at or above ${ctx.cwd}` : join(read.project.root, file));
7667
- }
7668
- };
7669
- }
7670
- function surveyOnce(bin) {
7671
- let survey;
7672
- return (install, cwd) => {
7673
- survey ??= surveyDependency(install, bin, cwd);
7674
- return survey;
7675
- };
7676
- }
7677
- function installRow(cli, { bin, node }, survey) {
7678
- return {
7679
- name: "install",
7680
- advisory: true,
7681
- run: async (ctx) => {
7682
- const { install } = cli;
7683
- if (install === void 0) return noInstall(cli);
7684
- if (install.mode === "dependency") return dependencyInstallFinding(cli, install, await survey(install, ctx.cwd));
7685
- const npm = globalNpm(cli, install, ctx, node);
7686
- try {
7687
- return ok(`a global npm install under ${(await globalInstallOf(bin, npm)).prefix}`);
7688
- } catch (error) {
7689
- if (error instanceof CliError && error.code === "NOT_GLOBAL_INSTALL") return {
7690
- state: "problem",
7691
- message: error.message,
7692
- fix: installCommandLine(npm.registry)
7693
- };
7694
- throw error;
7695
- }
7696
- }
7697
- };
7698
- }
7699
- async function releaseLookupOf(cli, install, ctx, node, survey) {
7700
- if (install.mode === "global") {
7701
- const fix = `${cli.name} update`;
7702
- return {
7703
- npm: globalNpm(cli, install, ctx, node),
7704
- advise: () => ({ fix })
7705
- };
7706
- }
7707
- const release = dependencyRelease(cli, install, await survey(install, ctx.cwd));
7708
- if ("skipped" in release) return release;
7709
- return {
7710
- npm: npmContext(cli, release.registry, ctx, node, release.cwd),
7711
- advise: release.advise
7712
- };
7713
- }
7714
- function registryRow(cli, { node }, survey) {
7715
- return {
7716
- name: "registry",
7717
- advisory: true,
7718
- run: async (ctx) => {
7719
- const { install } = cli;
7720
- if (install === void 0) return noInstall(cli);
7721
- const lookup = await releaseLookupOf(cli, install, ctx, node, survey);
7722
- if ("skipped" in lookup) return lookup.skipped;
7723
- const { npm, advise } = lookup;
7724
- const latest = await latestVersion(npm);
7725
- const standing = standingOf$1(cli.version, latest);
7726
- const head = `${npm.registry.label} has ${latest} as latest: this build`;
7727
- if (standing === "behind") {
7728
- const message = `${head} (${cli.version}) is older`;
7729
- const advice = advise(latest);
7730
- return "fix" in advice ? {
7731
- state: "problem",
7732
- message,
7733
- fix: advice.fix
7734
- } : {
7735
- state: "problem",
7736
- message: `${message}; ${advice.note}`
7737
- };
7738
- }
7739
- return ok(`${head} is ${standing === "current" ? "current" : `newer (${cli.version})`}`);
7740
- }
7741
- };
7742
- }
7743
- const SKILL_STATE_NOTES = {
7744
- current: "the same as this build",
7745
- behind: "older than this build",
7746
- ahead: "newer than this build",
7747
- unversioned: "states no metadata.version",
7748
- unreadable: "could not be read"
7749
- };
7750
- function describeCopy({ path, version, state }) {
7751
- return `${path}${version === void 0 ? "" : ` ${version}`} (${SKILL_STATE_NOTES[state]})`;
7752
- }
7753
- function skillRow(cli) {
7754
- const packaged = packagedFolderOf(cli);
7755
- return {
7756
- name: "skill",
7757
- advisory: true,
7758
- run: async (ctx) => {
7759
- if (packaged !== void 0) return packagedSkillFinding(cli, packaged, ctx);
7760
- const { copies, fix } = await skillReport(cli, cli.version, ctx);
7761
- const message = copies.length === 0 ? `no copy of the ${cli.name} skill is installed` : copies.map(describeCopy).join("; ");
7762
- return fix === void 0 ? ok(message) : {
7561
+ const holds = `${path} holds the ${cli.name} block`;
7562
+ const [edit] = await handEditsAt(root);
7563
+ return edit === void 0 ? {
7763
7564
  state: "ok",
7764
- message,
7765
- fix
7565
+ message: holds
7566
+ } : {
7567
+ state: "problem",
7568
+ message: `${holds}; ${edit}`
7766
7569
  };
7767
7570
  }
7768
7571
  };
7769
7572
  }
7770
- /** Every row `doctor` reports, in order: the fixed rows around the CLI's own and its tools'. */
7771
- function doctorChecks(cli, launch, read) {
7772
- const survey = surveyOnce(launch.bin);
7773
- return [
7774
- buildRow(cli),
7775
- nodeRow(cli),
7776
- ...cli.project === void 0 ? [] : [projectRow(cli.project, read)],
7777
- ...cli.doctor ?? [],
7778
- ...toolRows(cli.tools ?? [], cli.commands),
7779
- installRow(cli, launch, survey),
7780
- registryRow(cli, launch, survey),
7781
- skillRow(cli)
7782
- ];
7783
- }
7784
- //#endregion
7785
- //#region ../agent-cli/dist/built-ins/doctor.js
7786
- const DOCTOR_PROBLEMS = "DOCTOR_PROBLEMS";
7787
- const DEFAULT_DEADLINE_MS = 1e4;
7788
- const MS_PER_SECOND = 1e3;
7789
- const MIN_LABEL_WIDTH = 8;
7790
- const reportSchema = object({
7791
- rows: array(object({
7792
- check: string(),
7793
- state: _enum([
7794
- "ok",
7795
- "problem",
7796
- "skipped"
7797
- ]),
7798
- message: string(),
7799
- fix: string().optional(),
7800
- code: string().optional(),
7801
- skill: packagedSkillDetailsSchema.optional()
7802
- })),
7803
- next: array(string())
7804
- });
7805
- function problemOf(check, error, { signal, deadline, ms }) {
7806
- if (!signal.aborted && deadline.aborted && error === deadline.reason) return {
7807
- state: "problem",
7808
- message: `${check.name} did not answer within ${ms / MS_PER_SECOND} s`
7809
- };
7810
- return problemOfError(check.name, error);
7811
- }
7812
- function rowContext(ctx, signal, read) {
7813
- return Object.create(ctx, {
7814
- signal: { value: signal },
7815
- project: { get: () => {
7816
- if (!read.ok) throw read.error;
7817
- return read.project;
7818
- } }
7819
- });
7820
- }
7821
- async function rowOf(check, ctx, read) {
7822
- const ms = check.deadlineMs ?? DEFAULT_DEADLINE_MS;
7823
- const signal = ctx.deadline(ms);
7824
- let finding;
7825
- try {
7826
- finding = await Promise.race([Promise.resolve().then(() => check.run(rowContext(ctx, signal, read))), untilAborted(signal)]);
7827
- } catch (error) {
7828
- if (ctx.signal.aborted) return void 0;
7829
- finding = !read.ok && error === read.error ? {
7830
- state: "skipped",
7831
- message: "the Project file is unusable; the project row says why"
7832
- } : problemOf(check, error, {
7833
- signal: ctx.signal,
7834
- deadline: signal,
7835
- ms
7836
- });
7837
- }
7838
- return {
7839
- row: {
7840
- check: check.name,
7841
- ...finding
7842
- },
7843
- advisory: check.advisory === true
7844
- };
7845
- }
7846
- function nextOf(rows) {
7847
- return [...new Set(rows.flatMap(({ fix }) => fix === void 0 ? [] : [fix]))];
7848
- }
7849
- function render({ rows, next }, style) {
7850
- const width = Math.max(MIN_LABEL_WIDTH, ...rows.map(({ check }) => check.length)) + 2;
7851
- const stateWidth = Math.max(...rows.map(({ state }) => state.length)) + 2;
7852
- return [...rows.map(({ check, state, message }) => {
7853
- const label = state === "problem" ? style.red(state) : state;
7854
- return `${check.padEnd(width)}${label}${" ".repeat(stateWidth - state.length)}${message}`;
7855
- }), ...next.map((fix) => `${"next".padEnd(width)}${fix}`)].join("\n");
7856
- }
7857
- function failure(cliName, callerLine, problems) {
7858
- const errors = problems.map(({ check, message, fix, code }) => new CliError({
7859
- code: code ?? "DOCTOR_PROBLEMS",
7860
- message: `${cliName} doctor found a problem in ${check}: ${message}`,
7861
- hint: fix === void 0 ? "the row says what is wrong" : `run \`${fix}\`, then run \`${callerLine}\` again`
7862
- }));
7863
- return new AggregateError(errors);
7864
- }
7865
- /** The built-in `doctor`: the fixed rows around the CLI's own, each under its own deadline. */
7866
- function doctorCommand(cli, launch) {
7867
- return defineCommand$1({
7868
- name: "doctor",
7869
- description: "check the setup and say what fixes each problem",
7870
- examples: [{
7871
- command: `${runAsOf(cli)} doctor`,
7872
- note: "check the setup and list the commands that fix it"
7873
- }, {
7874
- command: `${runAsOf(cli)} doctor --json`,
7875
- note: "get the rows and fixes as data"
7876
- }],
7877
- effect: "read",
7878
- output: reportSchema,
7879
- run: async (_input, ctx) => {
7880
- const read = readProject(cli.project, ctx);
7881
- const checked = (await Promise.all(doctorChecks(cli, launch, read).map((check) => rowOf(check, ctx, read)))).filter((row) => row !== void 0);
7882
- const rows = checked.map(({ row }) => row);
7883
- const report = {
7884
- rows,
7885
- next: nextOf(rows)
7886
- };
7887
- if (ctx.signal.aborted) {
7888
- ctx.deliver(report);
7889
- ctx.signal.throwIfAborted();
7890
- }
7891
- const problems = checked.filter(({ row, advisory }) => row.state === "problem" && !advisory).map(({ row }) => row);
7892
- if (problems.length > 0) {
7893
- ctx.deliver(report);
7894
- throw failure(cli.name, ctx.commandLine(), problems);
7895
- }
7896
- return report;
7897
- },
7898
- render
7899
- });
7900
- }
7901
7573
  //#endregion
7902
7574
  //#region ../agent-cli/dist/agent.js
7903
7575
  const AGENT_NAME = /^[a-zA-Z0-9_-]+$/u;
@@ -8230,7 +7902,33 @@ async function probeOutputs({ files, cwd, fileError }, outputs) {
8230
7902
  }));
8231
7903
  }
8232
7904
  //#endregion
8233
- //#region ../agent-cli/dist/built-ins/skill-install.js
7905
+ //#region ../agent-cli/dist/built-ins/skill/install-command.js
7906
+ /** The project root and the Skill copy under it, as `skill install` writes it. */
7907
+ async function skillCopyOf(cli, cwd) {
7908
+ const root = await projectRootOf(cwd);
7909
+ return {
7910
+ root,
7911
+ copy: join(root, ".claude", "skills", cli.name)
7912
+ };
7913
+ }
7914
+ /** Every file under `dir`, relative to it and sorted; none when `dir` is missing. */
7915
+ async function filesIn(dir) {
7916
+ return (await orMissing(readdir(dir, {
7917
+ recursive: true,
7918
+ withFileTypes: true
7919
+ })) ?? []).filter((entry) => entry.isFile()).map((entry) => relative(dir, join(entry.parentPath, entry.name))).sort();
7920
+ }
7921
+ /** The versions of the package's skill in `source` and of its copy in `copy`. */
7922
+ async function skillVersionsOf(source, copy) {
7923
+ const [shipped, installed] = await Promise.all([readFile(join(source, "SKILL.md"), "utf8"), orMissing(readFile(join(copy, "SKILL.md"), "utf8"))]);
7924
+ const packageVersion = skillVersionIn(shipped) ?? null;
7925
+ const copyVersion = installed === void 0 ? void 0 : skillVersionIn(installed) ?? null;
7926
+ return {
7927
+ packageVersion,
7928
+ copyVersion,
7929
+ same: copyVersion === packageVersion
7930
+ };
7931
+ }
8234
7932
  const installSchema = object({
8235
7933
  copy: string(),
8236
7934
  written: array(string()),
@@ -8245,46 +7943,11 @@ async function sameContent(left, right) {
8245
7943
  const [a, b] = await Promise.all([readFile(left), readFile(right)]);
8246
7944
  return a.equals(b);
8247
7945
  }
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) {
8256
- return {
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) {
7946
+ async function planOf(cli, folder, install, cwd, packageRoot) {
8282
7947
  const source = join(packageRoot, folder);
8283
7948
  const { root, copy } = await skillCopyOf(cli, cwd);
8284
7949
  const [shipped, present] = await Promise.all([filesIn(source), filesIn(copy)]);
8285
7950
  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
7951
  return {
8289
7952
  source,
8290
7953
  copy,
@@ -8292,11 +7955,7 @@ async function planOf(cli, folder, cwd, packageRoot) {
8292
7955
  replace: shipped.filter((_file, index) => differs[index]),
8293
7956
  remove: present.filter((file) => !shipped.includes(file)),
8294
7957
  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
- },
7958
+ agentsMd: await agentsMdAt(cli, install, root),
8300
7959
  handEdits: await handEditsAt(root)
8301
7960
  };
8302
7961
  }
@@ -8336,8 +7995,8 @@ function editedRemovals(plan, ctx) {
8336
7995
  });
8337
7996
  }
8338
7997
  /** The built-in `skill install`, for a CLI whose skill ships in its package. */
8339
- function skillInstallCommand(cli, folder) {
8340
- const runAs = runAsOf(cli);
7998
+ function skillInstallCommand(cli, folder, install) {
7999
+ const { runAs } = install;
8341
8000
  return defineCommand$1({
8342
8001
  name: "install",
8343
8002
  description: "copy this CLI's skill into the project and write its AGENTS.md block",
@@ -8351,16 +8010,16 @@ function skillInstallCommand(cli, folder) {
8351
8010
  effect: "write",
8352
8011
  output: installSchema,
8353
8012
  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),
8013
+ plan: (_input, ctx) => planOf(cli, folder, install, ctx.cwd, ctx.packageRoot),
8355
8014
  outputs: (plan) => plan.sameVersion ? [...plan.write, ...plan.replace].map((file) => join(plan.copy, file)) : [],
8356
8015
  apply: async (plan, ctx) => {
8357
8016
  if (plan.sameVersion && plan.remove.length > 0 && !ctx.force) throw editedRemovals(plan, ctx);
8358
8017
  for (const file of [...plan.write, ...plan.replace]) await ctx.files.write(join(plan.copy, file), await readFile(join(plan.source, file)));
8359
8018
  for (const file of plan.remove) await ctx.files.remove(join(plan.copy, file));
8360
- const { path, block, change } = plan.agentsMd;
8019
+ const { path, change } = plan.agentsMd;
8361
8020
  if (change !== null) {
8362
8021
  const existing = await orMissing(readFile(path, "utf8")) ?? "";
8363
- await ctx.files.write(path, withBlock(existing, block, markersOf(cli.name)));
8022
+ await ctx.files.write(path, withBlock(cli, install, existing));
8364
8023
  }
8365
8024
  return {
8366
8025
  copy: plan.copy,
@@ -8378,6 +8037,93 @@ function skillInstallCommand(cli, folder) {
8378
8037
  });
8379
8038
  }
8380
8039
  //#endregion
8040
+ //#region ../agent-cli/dist/built-ins/skill/packaged.js
8041
+ async function personalCopyOf(name, copy, ctx) {
8042
+ const home = join(homeOf(ctx.env), ".claude", "skills", name);
8043
+ const real = await orMissing(realpath(home));
8044
+ return real === void 0 || real === await orMissing(realpath(copy)) ? void 0 : home;
8045
+ }
8046
+ /** What the `skill` row of a Packaged skill carries under `--json` beside its message. */
8047
+ const packagedSkillDetailsSchema = object({
8048
+ copy: string(),
8049
+ copyVersion: string().nullable(),
8050
+ packageVersion: string().nullable(),
8051
+ /** A personal copy, which Claude Code prefers over the project's. */
8052
+ home: string().optional()
8053
+ });
8054
+ function versionLabel(version) {
8055
+ return version ?? "(no metadata.version)";
8056
+ }
8057
+ function compared(name, { copy, copyVersion, packageVersion }, same) {
8058
+ const stated = `${copy} ${versionLabel(copyVersion)}`;
8059
+ return same ? `${stated}, the version ${name} ships` : `${stated} differs from the ${versionLabel(packageVersion)} that ${name} ships`;
8060
+ }
8061
+ /** The project root's copy against the package's, and any under home. */
8062
+ async function findingOf(cli, folder, install, ctx) {
8063
+ const { copy } = await skillCopyOf(cli, ctx.cwd);
8064
+ const versions = await skillVersionsOf(join(ctx.packageRoot, folder), copy);
8065
+ const { packageVersion } = versions;
8066
+ const copyVersion = versions.copyVersion ?? null;
8067
+ const home = await personalCopyOf(cli.name, copy, ctx);
8068
+ const skill = {
8069
+ copy,
8070
+ copyVersion,
8071
+ packageVersion,
8072
+ ...home === void 0 ? {} : { home }
8073
+ };
8074
+ const head = versions.copyVersion === void 0 ? `no copy of the ${cli.name} skill at ${copy}` : compared(cli.name, skill, versions.same);
8075
+ const message = home === void 0 ? head : `${head}; remove ${home}: Claude Code prefers a personal skill over the project's`;
8076
+ if (!versions.same) return {
8077
+ state: "problem",
8078
+ message,
8079
+ fix: `${install.runAs} skill install`,
8080
+ skill
8081
+ };
8082
+ return home === void 0 ? {
8083
+ state: "ok",
8084
+ message,
8085
+ skill
8086
+ } : {
8087
+ state: "problem",
8088
+ message,
8089
+ skill
8090
+ };
8091
+ }
8092
+ async function notice(cli, folder, install, { builtIn, bin }, ctx) {
8093
+ if (builtIn || bin === void 0 || cli.commands === void 0) return;
8094
+ let finding;
8095
+ try {
8096
+ finding = await findingOf(cli, folder, install, ctx);
8097
+ } catch (error) {
8098
+ if (errnoOf(error) === void 0) throw error;
8099
+ ctx.notice(`could not check the ${cli.name} skill: ${messageOf(error)}`);
8100
+ return;
8101
+ }
8102
+ if (finding.state !== "problem") return;
8103
+ ctx.notice(finding.fix === void 0 ? finding.message : `${finding.message}; run \`${finding.fix}\``);
8104
+ }
8105
+ /** A skill shipped in the package at `folder`, which `skill install` copies to the project root. */
8106
+ function packagedSkill(cli, folder, install) {
8107
+ return {
8108
+ kind: "packaged",
8109
+ builtIns: { skill: { install: skillInstallCommand(cli, folder, install) } },
8110
+ rows: [{
8111
+ name: "skill",
8112
+ advisory: true,
8113
+ run: (ctx) => findingOf(cli, folder, install, ctx)
8114
+ }, agentsRow(cli, install)],
8115
+ notice: (launch, ctx) => notice(cli, folder, install, launch, ctx),
8116
+ findingAt: (_version, ctx) => findingOf(cli, folder, install, ctx)
8117
+ };
8118
+ }
8119
+ //#endregion
8120
+ //#region ../agent-cli/dist/built-ins/skill/skill.js
8121
+ /** A Packaged skill, or else a Skill Hub's, also for a CLI that declares no `skill`. */
8122
+ function skillOf(cli, install) {
8123
+ const { skill } = cli;
8124
+ return skill !== void 0 && "packaged" in skill ? packagedSkill(cli, skill.packaged, install) : hubSkill(cli, skill?.source);
8125
+ }
8126
+ //#endregion
8381
8127
  //#region ../agent-cli/dist/interrupts.js
8382
8128
  const INTERRUPTED = "INTERRUPTED";
8383
8129
  const DEADLINE_EXCEEDED = "DEADLINE_EXCEEDED";
@@ -8515,6 +8261,59 @@ var RunControl = class {
8515
8261
  }
8516
8262
  };
8517
8263
  //#endregion
8264
+ //#region ../agent-cli/dist/built-ins/install/global-npm.js
8265
+ const NOT_GLOBAL_INSTALL = "NOT_GLOBAL_INSTALL";
8266
+ /** Where npm puts global packages under its global prefix. */
8267
+ function globalModulesDir(prefix) {
8268
+ return process.platform === "win32" ? join(prefix, "node_modules") : join(prefix, "lib", "node_modules");
8269
+ }
8270
+ function notGlobal(ctx, message) {
8271
+ return new CliError({
8272
+ code: NOT_GLOBAL_INSTALL,
8273
+ message,
8274
+ hint: `install it with npm, so \`${ctx.cliName} update\` can replace it: ${installCommandLine(ctx.registry)}`
8275
+ });
8276
+ }
8277
+ /**
8278
+ * The global npm install this copy runs from, found by asking npm for its global prefix.
8279
+ * `bin` is the script that started the CLI; throws `NOT_GLOBAL_INSTALL` when it is not inside that install.
8280
+ */
8281
+ async function globalInstallOf(bin, ctx) {
8282
+ if (bin === void 0) throw notGlobal(ctx, `${ctx.cliName} runs from source, not from an npm install`);
8283
+ const prefix = await globalPrefix(ctx);
8284
+ const packageDir = join(globalModulesDir(prefix), ctx.registry.package);
8285
+ const installed = await orMissing(realpath(packageDir));
8286
+ const here = await orMissing(realpath(bin)) ?? bin;
8287
+ if (installed === void 0 || !isInside(installed, here)) throw notGlobal(ctx, `${ctx.cliName} runs from ${here}, not from npm's global install at ${packageDir}`);
8288
+ if ((await lstat(packageDir)).isSymbolicLink()) throw notGlobal(ctx, `${ctx.cliName} runs from a link to ${installed} (npm link), not from an npm install`);
8289
+ return {
8290
+ prefix,
8291
+ packageDir
8292
+ };
8293
+ }
8294
+ const NPMJS = "https://registry.npmjs.org/";
8295
+ /** The flag that points npm at the CLI's registry; none for npmjs, npm's default. */
8296
+ function registryFlags({ package: name, registry }) {
8297
+ if (registry === NPMJS) return [];
8298
+ const scope = scopeOf(name);
8299
+ return [scope === void 0 ? `--registry=${registry}` : `--${scope}:registry=${registry}`];
8300
+ }
8301
+ function globalRegistry(install) {
8302
+ const { package: name, registry } = install;
8303
+ return {
8304
+ package: name,
8305
+ label: registry,
8306
+ flags: registryFlags(install),
8307
+ unreachableHint: "check the network or VPN",
8308
+ loginHint: `log in to ${registry} (\`npm login --registry=${registry}\`) or set its token in ~/.npmrc`,
8309
+ missingHint: `check that ${name} is published on ${registry}`
8310
+ };
8311
+ }
8312
+ /** npm for a Global install, run in the working directory. */
8313
+ function globalNpm(cli, install, ctx, node) {
8314
+ return npmContext(cli, globalRegistry(install), ctx, node, ctx.cwd);
8315
+ }
8316
+ //#endregion
8518
8317
  //#region ../agent-cli/dist/built-ins/upgrades.js
8519
8318
  /** A skipped release's Upgrade steps, or its whole section when it lists none. */
8520
8319
  const upgradeSchema = union([object({
@@ -8565,115 +8364,420 @@ function upgradeOf({ version, lines }) {
8565
8364
  steps: todo
8566
8365
  }];
8567
8366
  }
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;
8367
+ /**
8368
+ * What each release after `from` up to `to` asks of the operator, oldest first, from the
8369
+ * CHANGELOG.md in `packageDir`; none when the package ships no CHANGELOG.
8370
+ */
8371
+ async function upgradesOf(packageDir, from, to) {
8372
+ let changelog;
8373
+ try {
8374
+ changelog = await readFile(join(packageDir, "CHANGELOG.md"), "utf8");
8375
+ } catch (error) {
8376
+ if (isMissingFile(error)) return [];
8377
+ throw error;
8378
+ }
8379
+ return releasesIn(changelog).filter(({ version }) => compareVersions(version, from) > 0 && compareVersions(version, to) <= 0).sort((a, b) => compareVersions(a.version, b.version)).flatMap(upgradeOf);
8380
+ }
8381
+ //#endregion
8382
+ //#region ../agent-cli/dist/built-ins/update.js
8383
+ const updateSchema = object({
8384
+ from: string(),
8385
+ latest: string(),
8386
+ installed: object({
8387
+ version: string(),
8388
+ prefix: string()
8389
+ }).nullable(),
8390
+ upgrades: array(upgradeSchema),
8391
+ next: array(string())
8392
+ });
8393
+ function nothingToInstall({ from, latest }) {
8394
+ 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`;
8395
+ }
8396
+ function renderUpgrade(upgrade) {
8397
+ return "steps" in upgrade ? [`\nupgrade steps for ${upgrade.version}:`, ...upgrade.steps.map((step) => `- ${step}`)].join("\n") : `\nchanges in ${upgrade.version}:\n${upgrade.notes}`;
8398
+ }
8399
+ function renderUpdate(cli, install, update) {
8400
+ const { installed } = update;
8401
+ const head = installed === null ? [nothingToInstall(update)] : [`installed ${install.package}@${installed.version} with the npm under ${installed.prefix}, replacing ${update.from}`, ...update.upgrades.map(renderUpgrade)];
8402
+ const next = update.next.map((command) => `next: ${command}, so the ${cli.name} skill describes ${update.latest}`);
8403
+ return (next.length === 0 ? head : [
8404
+ ...head,
8405
+ "",
8406
+ ...next
8407
+ ]).join("\n");
8408
+ }
8409
+ async function skillNext(skill, latest, ctx) {
8410
+ const { fix } = await skill.findingAt(latest, ctx);
8411
+ return fix === void 0 ? [] : [fix];
8412
+ }
8413
+ function interruptedAfterInstall(plan) {
8414
+ return new CliError({
8415
+ code: INTERRUPTED,
8416
+ message: `interrupted, but ${plan.package}@${plan.latest} had already been installed with the npm under ${plan.prefix}, replacing ${plan.from}`,
8417
+ hint: "nothing to run again: run the commands under next"
8418
+ });
8419
+ }
8420
+ /** The built-in `update`, for a CLI installed globally with npm. */
8421
+ function updateCommand(cli, install, skill, { bin, node }) {
8422
+ return defineCommand$1({
8423
+ name: "update",
8424
+ description: "install the latest version of this CLI from its registry",
8425
+ examples: [{
8426
+ command: `${cli.name} update`,
8427
+ note: "install the latest version and show what changed"
8428
+ }, {
8429
+ command: `${cli.name} update --dry-run`,
8430
+ note: "show the version it would install"
8431
+ }],
8432
+ effect: "write",
8433
+ output: updateSchema,
8434
+ plan: async (_input, ctx) => {
8435
+ const npm = globalNpm(cli, install, ctx, node);
8436
+ const copy = await globalInstallOf(bin, npm);
8437
+ return {
8438
+ package: install.package,
8439
+ from: cli.version,
8440
+ latest: await latestVersion(npm),
8441
+ prefix: copy.prefix,
8442
+ packageDir: copy.packageDir
8443
+ };
8444
+ },
8445
+ apply: async (plan, ctx) => {
8446
+ const { from, latest, prefix } = plan;
8447
+ if (standingOf$1(from, latest) !== "behind") return {
8448
+ from,
8449
+ latest,
8450
+ installed: null,
8451
+ upgrades: [],
8452
+ next: await skillNext(skill, latest, ctx)
8453
+ };
8454
+ ctx.signal.throwIfAborted();
8455
+ ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
8456
+ await ctx.runToEnd(`installing ${plan.package}@${latest}`, () => installGlobally(latest, globalNpm(cli, install, ctx, node)));
8457
+ const update = {
8458
+ from,
8459
+ latest,
8460
+ installed: {
8461
+ version: latest,
8462
+ prefix
8463
+ },
8464
+ upgrades: await upgradesOf(plan.packageDir, from, latest),
8465
+ next: await skillNext(skill, latest, ctx)
8466
+ };
8467
+ if (ctx.signal.aborted) {
8468
+ ctx.deliver(update);
8469
+ throw interruptedAfterInstall(plan);
8470
+ }
8471
+ return update;
8472
+ },
8473
+ render: (update) => renderUpdate(cli, install, update),
8474
+ 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)
8475
+ });
8476
+ }
8477
+ //#endregion
8478
+ //#region ../agent-cli/dist/built-ins/install/global.js
8479
+ function checkOf(cli, install, { bin, node }) {
8480
+ return {
8481
+ install: async (ctx) => {
8482
+ const npm = globalNpm(cli, install, ctx, node);
8483
+ try {
8484
+ return {
8485
+ state: "ok",
8486
+ message: `a global npm install under ${(await globalInstallOf(bin, npm)).prefix}`
8487
+ };
8488
+ } catch (error) {
8489
+ if (error instanceof CliError && error.code === "NOT_GLOBAL_INSTALL") return {
8490
+ state: "problem",
8491
+ message: error.message,
8492
+ fix: installCommandLine(npm.registry)
8493
+ };
8494
+ throw error;
8495
+ }
8496
+ },
8497
+ release: async (ctx) => {
8498
+ const fix = `${cli.name} update`;
8499
+ return {
8500
+ npm: globalNpm(cli, install, ctx, node),
8501
+ advise: () => ({ fix })
8502
+ };
8503
+ }
8504
+ };
8505
+ }
8506
+ /** The Install of a CLI installed once per machine with npm, which `update` replaces. */
8507
+ function globalInstall(cli, install) {
8508
+ const adapter = {
8509
+ runAs: runAsOf(cli),
8510
+ builtIns: (launch) => ({ update: updateCommand(cli, install, skillOf(cli, adapter), launch) }),
8511
+ exitClasses: ["source"],
8512
+ agentsCaveat: void 0,
8513
+ check: (launch) => checkOf(cli, install, launch)
8514
+ };
8515
+ return adapter;
8516
+ }
8517
+ //#endregion
8518
+ //#region ../agent-cli/dist/built-ins/install/install.js
8519
+ function noInstall(cli) {
8520
+ const skipped = async () => ({
8521
+ state: "skipped",
8522
+ message: `not checked: ${cli.name} declares no install`
8523
+ });
8524
+ return {
8525
+ runAs: runAsOf(cli),
8526
+ builtIns: () => ({}),
8527
+ exitClasses: [],
8528
+ agentsCaveat: void 0,
8529
+ check: () => ({
8530
+ install: skipped,
8531
+ release: async () => ({ skipped: await skipped() })
8532
+ })
8533
+ };
8534
+ }
8535
+ function installOf(cli) {
8536
+ const { install } = cli;
8537
+ if (install === void 0) return noInstall(cli);
8538
+ return install.mode === "global" ? globalInstall(cli, install) : dependencyInstall(cli, install);
8539
+ }
8540
+ //#endregion
8541
+ //#region ../agent-cli/dist/built-ins/doctor-rows.js
8542
+ function ok(message) {
8543
+ return {
8544
+ state: "ok",
8545
+ message
8546
+ };
8547
+ }
8548
+ function buildRow(cli) {
8549
+ return {
8550
+ name: "build",
8551
+ run: () => ok(versionLine(cli))
8552
+ };
8553
+ }
8554
+ function nodeRow(cli) {
8555
+ return {
8556
+ name: "node",
8557
+ run: () => {
8558
+ const found = process.versions.node;
8559
+ const range = cli.engines?.node;
8560
+ if (range === void 0) return ok(`Node ${found}; ${cli.name} declares no engines.node to check it against`);
8561
+ const minimum = engineMinimum(range);
8562
+ if (minimum === void 0) return {
8563
+ state: "problem",
8564
+ message: `${cli.name} declares engines.node "${range}", which doctor cannot read: only >=X.Y.Z is supported`
8565
+ };
8566
+ if (meetsMinimum(found, minimum)) return ok(`Node ${found} meets ${range}`);
8567
+ const major = minimum[0];
8568
+ return {
8569
+ state: "problem",
8570
+ message: `${cli.name} runs on Node ${found}, older than the ${range} it needs; install Node ${minimum.join(".")} or newer and make it the default`,
8571
+ fix: `nvm install ${major} && nvm alias default ${major}`
8572
+ };
8573
+ }
8574
+ };
8575
+ }
8576
+ /** A row's thrown error as its problem: a `CliError` with its hint and code, anything else a bug. */
8577
+ function problemOfError(name, error) {
8578
+ if (error instanceof CliError) return {
8579
+ state: "problem",
8580
+ message: `${error.message}; ${error.hint}`,
8581
+ code: error.code
8582
+ };
8583
+ return {
8584
+ state: "problem",
8585
+ message: `the ${name} row hit a bug: ${messageOf(error)}`
8586
+ };
8587
+ }
8588
+ function readProject(projectFile, ctx) {
8589
+ if (projectFile === void 0) return {
8590
+ ok: true,
8591
+ project: void 0
8592
+ };
8574
8593
  try {
8575
- changelog = await readFile(join(packageDir, "CHANGELOG.md"), "utf8");
8594
+ return {
8595
+ ok: true,
8596
+ project: projectOf(projectFile, ctx.cwd, {
8597
+ fileError: ctx.fileError,
8598
+ callerLine: ctx.commandLine()
8599
+ })
8600
+ };
8576
8601
  } catch (error) {
8577
- if (isMissingFile(error)) return [];
8578
- throw error;
8602
+ return {
8603
+ ok: false,
8604
+ error
8605
+ };
8579
8606
  }
8580
- return releasesIn(changelog).filter(({ version }) => compareVersions(version, from) > 0 && compareVersions(version, to) <= 0).sort((a, b) => compareVersions(a.version, b.version)).flatMap(upgradeOf);
8607
+ }
8608
+ function projectRow({ file }, read) {
8609
+ return {
8610
+ name: "project",
8611
+ run: (ctx) => {
8612
+ if (!read.ok) return problemOfError("project", read.error);
8613
+ return ok(read.project === void 0 ? `none found: no ${file} at or above ${ctx.cwd}` : join(read.project.root, file));
8614
+ }
8615
+ };
8616
+ }
8617
+ function installRow(check) {
8618
+ return {
8619
+ name: "install",
8620
+ advisory: true,
8621
+ run: check.install
8622
+ };
8623
+ }
8624
+ function registryRow(cli, check) {
8625
+ return {
8626
+ name: "registry",
8627
+ advisory: true,
8628
+ run: async (ctx) => {
8629
+ const lookup = await check.release(ctx);
8630
+ if ("skipped" in lookup) return lookup.skipped;
8631
+ const { npm, advise } = lookup;
8632
+ const latest = await latestVersion(npm);
8633
+ const standing = standingOf$1(cli.version, latest);
8634
+ const head = `${npm.registry.label} has ${latest} as latest: this build`;
8635
+ if (standing === "behind") {
8636
+ const message = `${head} (${cli.version}) is older`;
8637
+ const advice = advise(latest);
8638
+ return "fix" in advice ? {
8639
+ state: "problem",
8640
+ message,
8641
+ fix: advice.fix
8642
+ } : {
8643
+ state: "problem",
8644
+ message: `${message}; ${advice.note}`
8645
+ };
8646
+ }
8647
+ return ok(`${head} is ${standing === "current" ? "current" : `newer (${cli.version})`}`);
8648
+ }
8649
+ };
8650
+ }
8651
+ /** Every row `doctor` reports, in order: the fixed rows around the CLI's own and its tools'. */
8652
+ function doctorChecks(cli, launch, read) {
8653
+ const install = installOf(cli);
8654
+ const check = install.check(launch);
8655
+ return [
8656
+ buildRow(cli),
8657
+ nodeRow(cli),
8658
+ ...cli.project === void 0 ? [] : [projectRow(cli.project, read)],
8659
+ ...cli.doctor ?? [],
8660
+ ...toolRows(cli.tools ?? [], cli.commands),
8661
+ installRow(check),
8662
+ registryRow(cli, check),
8663
+ ...skillOf(cli, install).rows
8664
+ ];
8581
8665
  }
8582
8666
  //#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),
8667
+ //#region ../agent-cli/dist/built-ins/doctor.js
8668
+ const DOCTOR_PROBLEMS = "DOCTOR_PROBLEMS";
8669
+ const DEFAULT_DEADLINE_MS = 1e4;
8670
+ const MS_PER_SECOND = 1e3;
8671
+ const MIN_LABEL_WIDTH = 8;
8672
+ const reportSchema = object({
8673
+ rows: array(object({
8674
+ check: string(),
8675
+ state: _enum([
8676
+ "ok",
8677
+ "problem",
8678
+ "skipped"
8679
+ ]),
8680
+ message: string(),
8681
+ fix: string().optional(),
8682
+ code: string().optional(),
8683
+ skill: packagedSkillDetailsSchema.optional()
8684
+ })),
8592
8685
  next: array(string())
8593
8686
  });
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`;
8687
+ function problemOf(check, error, { signal, deadline, ms }) {
8688
+ if (!signal.aborted && deadline.aborted && error === deadline.reason) return {
8689
+ state: "problem",
8690
+ message: `${check.name} did not answer within ${ms / MS_PER_SECOND} s`
8691
+ };
8692
+ return problemOfError(check.name, error);
8596
8693
  }
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}`;
8694
+ function rowContext(ctx, signal, read) {
8695
+ return Object.create(ctx, {
8696
+ signal: { value: signal },
8697
+ project: { get: () => {
8698
+ if (!read.ok) throw read.error;
8699
+ return read.project;
8700
+ } }
8701
+ });
8599
8702
  }
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");
8703
+ async function rowOf(check, ctx, read) {
8704
+ const ms = check.deadlineMs ?? DEFAULT_DEADLINE_MS;
8705
+ const signal = ctx.deadline(ms);
8706
+ let finding;
8707
+ try {
8708
+ finding = await Promise.race([Promise.resolve().then(() => check.run(rowContext(ctx, signal, read))), untilAborted(signal)]);
8709
+ } catch (error) {
8710
+ if (ctx.signal.aborted) return void 0;
8711
+ finding = !read.ok && error === read.error ? {
8712
+ state: "skipped",
8713
+ message: "the Project file is unusable; the project row says why"
8714
+ } : problemOf(check, error, {
8715
+ signal: ctx.signal,
8716
+ deadline: signal,
8717
+ ms
8718
+ });
8719
+ }
8720
+ return {
8721
+ row: {
8722
+ check: check.name,
8723
+ ...finding
8724
+ },
8725
+ advisory: check.advisory === true
8726
+ };
8609
8727
  }
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] : [];
8728
+ function nextOf(rows) {
8729
+ return [...new Set(rows.flatMap(({ fix }) => fix === void 0 ? [] : [fix]))];
8614
8730
  }
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
- });
8731
+ function render({ rows, next }, style) {
8732
+ const width = Math.max(MIN_LABEL_WIDTH, ...rows.map(({ check }) => check.length)) + 2;
8733
+ const stateWidth = Math.max(...rows.map(({ state }) => state.length)) + 2;
8734
+ return [...rows.map(({ check, state, message }) => {
8735
+ const label = state === "problem" ? style.red(state) : state;
8736
+ return `${check.padEnd(width)}${label}${" ".repeat(stateWidth - state.length)}${message}`;
8737
+ }), ...next.map((fix) => `${"next".padEnd(width)}${fix}`)].join("\n");
8621
8738
  }
8622
- /** The built-in `update`, for a CLI installed globally with npm. */
8623
- function updateCommand(cli, install, { bin, node }) {
8739
+ function failure(cliName, callerLine, problems) {
8740
+ const errors = problems.map(({ check, message, fix, code }) => new CliError({
8741
+ code: code ?? "DOCTOR_PROBLEMS",
8742
+ message: `${cliName} doctor found a problem in ${check}: ${message}`,
8743
+ hint: fix === void 0 ? "the row says what is wrong" : `run \`${fix}\`, then run \`${callerLine}\` again`
8744
+ }));
8745
+ return new AggregateError(errors);
8746
+ }
8747
+ /** The built-in `doctor`: the fixed rows around the CLI's own, each under its own deadline. */
8748
+ function doctorCommand(cli, launch) {
8624
8749
  return defineCommand$1({
8625
- name: "update",
8626
- description: "install the latest version of this CLI from its registry",
8750
+ name: "doctor",
8751
+ description: "check the setup and say what fixes each problem",
8627
8752
  examples: [{
8628
- command: `${cli.name} update`,
8629
- note: "install the latest version and show what changed"
8753
+ command: `${runAsOf(cli)} doctor`,
8754
+ note: "check the setup and list the commands that fix it"
8630
8755
  }, {
8631
- command: `${cli.name} update --dry-run`,
8632
- note: "show the version it would install"
8756
+ command: `${runAsOf(cli)} doctor --json`,
8757
+ note: "get the rows and fixes as data"
8633
8758
  }],
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
- };
8646
- },
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)
8759
+ effect: "read",
8760
+ output: reportSchema,
8761
+ run: async (_input, ctx) => {
8762
+ const read = readProject(cli.project, ctx);
8763
+ const checked = (await Promise.all(doctorChecks(cli, launch, read).map((check) => rowOf(check, ctx, read)))).filter((row) => row !== void 0);
8764
+ const rows = checked.map(({ row }) => row);
8765
+ const report = {
8766
+ rows,
8767
+ next: nextOf(rows)
8668
8768
  };
8669
8769
  if (ctx.signal.aborted) {
8670
- ctx.deliver(update);
8671
- throw interruptedAfterInstall(plan);
8770
+ ctx.deliver(report);
8771
+ ctx.signal.throwIfAborted();
8672
8772
  }
8673
- return update;
8773
+ const problems = checked.filter(({ row, advisory }) => row.state === "problem" && !advisory).map(({ row }) => row);
8774
+ if (problems.length > 0) {
8775
+ ctx.deliver(report);
8776
+ throw failure(cli.name, ctx.commandLine(), problems);
8777
+ }
8778
+ return report;
8674
8779
  },
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)
8780
+ render
8677
8781
  });
8678
8782
  }
8679
8783
  //#endregion
@@ -8683,11 +8787,11 @@ function updateCommand(cli, install, { bin, node }) {
8683
8787
  * `skill install` only for a Packaged skill.
8684
8788
  */
8685
8789
  function builtInCommands(cli, launch) {
8686
- const packaged = packagedFolderOf(cli);
8790
+ const install = installOf(cli);
8687
8791
  return {
8688
8792
  doctor: doctorCommand(cli, launch),
8689
- ...cli.install?.mode === "global" ? { update: updateCommand(cli, cli.install, launch) } : {},
8690
- ...packaged === void 0 ? {} : { skill: { install: skillInstallCommand(cli, packaged) } }
8793
+ ...install.builtIns(launch),
8794
+ ...skillOf(cli, install).builtIns
8691
8795
  };
8692
8796
  }
8693
8797
  //#endregion
@@ -8811,7 +8915,7 @@ function reachableExitClasses(cli) {
8811
8915
  "caller",
8812
8916
  "operator",
8813
8917
  "wait",
8814
- ...cli.commands !== void 0 && cli.install !== void 0 ? ["source"] : []
8918
+ ...cli.commands === void 0 ? [] : installOf(cli).exitClasses
8815
8919
  ];
8816
8920
  const own = Object.values(cli.errors ?? {}).map((declaration) => declaration.class);
8817
8921
  return /* @__PURE__ */ new Set([...runtime, ...own]);
@@ -8837,7 +8941,7 @@ function checkOwnCodes(cli) {
8837
8941
  function defineCli$1(cli) {
8838
8942
  checkOwnCodes(cli);
8839
8943
  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`);
8944
+ if (skillOf(cli, installOf(cli)).kind === "packaged") throw new Error(`${cli.name} declares a Packaged skill, which only a CLI with several Commands can install`);
8841
8945
  return cli;
8842
8946
  }
8843
8947
  const { commands } = cli;
@@ -8860,7 +8964,10 @@ function agentCli() {
8860
8964
  }
8861
8965
  //#endregion
8862
8966
  //#region ../agent-cli/dist/error-table.js
8863
- /** Keeps each code's literal class and `details` schema, so `cliError` can type its throw sites. */
8967
+ /**
8968
+ * Keeps each code's literal class and `details` schema, so `cliError` can type its throw sites;
8969
+ * `print` must name a string key of the code's `details`.
8970
+ */
8864
8971
  function defineErrors(table) {
8865
8972
  return table;
8866
8973
  }
@@ -9952,7 +10059,10 @@ async function runCommand(cli, routed, options, write, baseFormat) {
9952
10059
  let served = false;
9953
10060
  let settled;
9954
10061
  try {
9955
- const outcome = await control.settle(execute(command, input, ctx, {
10062
+ const outcome = await control.settle(skillOf(cli, installOf(cli)).notice({
10063
+ builtIn,
10064
+ bin: options.bin
10065
+ }, ctx).then(() => execute(command, input, ctx, {
9956
10066
  dryRun: flags.dryRun,
9957
10067
  confirm: flags.confirm,
9958
10068
  yes: flags.yes,
@@ -9965,7 +10075,7 @@ async function runCommand(cli, routed, options, write, baseFormat) {
9965
10075
  },
9966
10076
  files,
9967
10077
  guardOutputs: outputs.guard
9968
- }));
10078
+ })));
9969
10079
  if (served) return {
9970
10080
  result: STOPPED_RESULT,
9971
10081
  finishSideTask
@@ -10148,14 +10258,14 @@ async function runProcess(cli) {
10148
10258
  }
10149
10259
  //#endregion
10150
10260
  //#region package.json
10151
- var version$1 = "0.14.0";
10261
+ var version$1 = "0.15.1";
10152
10262
  var description = "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.";
10153
10263
  //#endregion
10154
10264
  //#region src/build.ts
10155
10265
  const build = {
10156
- "sha": "f114cf0",
10266
+ "sha": "da48f36",
10157
10267
  "dirty": false,
10158
- "builtAt": "2026-10-27T02:06:04.000Z"
10268
+ "builtAt": "2026-10-27T08:31:19.000Z"
10159
10269
  };
10160
10270
  //#endregion
10161
10271
  //#region src/fill.ts