@duyquangnvx/create-agent-cli 0.6.0 → 0.8.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 CHANGED
@@ -4,6 +4,25 @@ What changed in each release of `@duyquangnvx/agent-cli` (the Runtime) and `@duy
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## v0.8.0 (2026-10-20)
8
+
9
+ ### Changed
10
+
11
+ - **Breaking:** `update` shows only the Upgrade steps of a skipped release whose CHANGELOG section lists them under `### Upgrade from v<version>`, under `upgrade steps for <version>:`. A release whose only step is `Nothing to do.` is left out, and one without the heading still shows its whole section under `changes in <version>:`.
12
+ - **Breaking:** each entry of `upgrades` in `update --json` is `{ version, steps }` or `{ version, notes }`.
13
+ - The scaffolded template's CHANGELOG asks each release from the second on to hold `### Upgrade from v<the release below>`, and its setup reference tells the agent to do the steps `update` prints. The agent-cli skill's shipping reference explains the convention.
14
+
15
+ ## v0.7.0 (2026-10-20)
16
+
17
+ ### Added
18
+
19
+ - Every Runtime code is exported under its name: `OUTPUT_EXISTS`, `PROJECT_INVALID`, `INVALID_INPUT`, `UNKNOWN_OPTION`, `UNKNOWN_COMMAND`, `UNKNOWN_FIELD`, `CONFIRMATION_REQUIRED`, `CONFIRMATION_MISMATCH`, `NOT_CONFIRMED`, `TOOL_FAILED`, `TOOL_MISSING`, `DOCTOR_PROBLEMS`, `INTERNAL`, `NOT_GLOBAL_INSTALL`, `REGISTRY_UNREACHABLE`, `REGISTRY_AUTH`, `INSTALL_FAILED`, `NPM_FAILED` and `NO_LATEST`. The agent-cli skill says which of them a Command may throw, and asserts codes through them.
20
+ - A CLI's `doctor` rows read the Project file as `ctx.project`, which was always `undefined` there. `doctor` reads the file once for all its rows; a row that reads `ctx.project` while the file is unusable is skipped, since the `project` row reports it. See `DoctorCheck.run`. The agent-cli skill's example adds a `tag` row that reads it.
21
+
22
+ ### Changed
23
+
24
+ - **Breaking:** `doctor`'s `project` row carries the code of the failed read, so a Project file its schema rejects fails `doctor` with `PROJECT_INVALID` instead of `DOCTOR_PROBLEMS` (still exit 2), the code a Command fails with on the same file. The row's message adds the error's hint.
25
+
7
26
  ## v0.6.0 (2026-10-20)
8
27
 
9
28
  ### Changed
package/dist/bin.js CHANGED
@@ -5795,7 +5795,7 @@ var UsageError = class extends CliError {
5795
5795
  this.name = "UsageError";
5796
5796
  }
5797
5797
  };
5798
- const INTERNAL_CODE = "INTERNAL";
5798
+ const INTERNAL = "INTERNAL";
5799
5799
  function errnoOf(error) {
5800
5800
  return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
5801
5801
  }
@@ -5835,7 +5835,7 @@ function fieldsOf$1(error, format) {
5835
5835
  };
5836
5836
  }
5837
5837
  return {
5838
- code: INTERNAL_CODE,
5838
+ code: INTERNAL,
5839
5839
  message: messageOf(error),
5840
5840
  hint: bugHint(format)
5841
5841
  };
@@ -7190,22 +7190,41 @@ function nodeRow(cli) {
7190
7190
  }
7191
7191
  };
7192
7192
  }
7193
- function projectRow(projectFile) {
7194
- const { file } = projectFile;
7193
+ /** A row's thrown error as its problem: a `CliError` with its hint and code, anything else a bug. */
7194
+ function problemOfError(name, error) {
7195
+ if (error instanceof CliError) return {
7196
+ state: "problem",
7197
+ message: `${error.message}; ${error.hint}`,
7198
+ code: error.code
7199
+ };
7200
+ return {
7201
+ state: "problem",
7202
+ message: `the ${name} row hit a bug: ${messageOf(error)}`
7203
+ };
7204
+ }
7205
+ function readProject(projectFile, ctx) {
7206
+ if (projectFile === void 0) return {
7207
+ ok: true,
7208
+ project: void 0
7209
+ };
7210
+ try {
7211
+ return {
7212
+ ok: true,
7213
+ project: projectOf(projectFile, ctx.cwd, ctx.fileError)
7214
+ };
7215
+ } catch (error) {
7216
+ return {
7217
+ ok: false,
7218
+ error
7219
+ };
7220
+ }
7221
+ }
7222
+ function projectRow({ file }, read) {
7195
7223
  return {
7196
7224
  name: "project",
7197
7225
  run: (ctx) => {
7198
- let project;
7199
- try {
7200
- project = projectOf(projectFile, ctx.cwd, ctx.fileError);
7201
- } catch (error) {
7202
- if (error instanceof CliError && error.code === "PROJECT_INVALID") return {
7203
- state: "problem",
7204
- message: error.message
7205
- };
7206
- throw error;
7207
- }
7208
- return ok(project === void 0 ? `none found: no ${file} at or above ${ctx.cwd}` : join(project.root, file));
7226
+ if (!read.ok) return problemOfError("project", read.error);
7227
+ return ok(read.project === void 0 ? `none found: no ${file} at or above ${ctx.cwd}` : join(read.project.root, file));
7209
7228
  }
7210
7229
  };
7211
7230
  }
@@ -7273,11 +7292,11 @@ function skillRow(cli) {
7273
7292
  };
7274
7293
  }
7275
7294
  /** Every row `doctor` reports, in order: the fixed rows around the CLI's own and its tools'. */
7276
- function doctorChecks(cli, launch) {
7295
+ function doctorChecks(cli, launch, read) {
7277
7296
  return [
7278
7297
  buildRow(cli),
7279
7298
  nodeRow(cli),
7280
- ...cli.project === void 0 ? [] : [projectRow(cli.project)],
7299
+ ...cli.project === void 0 ? [] : [projectRow(cli.project, read)],
7281
7300
  ...cli.doctor ?? [],
7282
7301
  ...toolRows(cli.tools ?? [], cli.commands),
7283
7302
  installRow(cli, launch),
@@ -7310,28 +7329,29 @@ function problemOf(check, error, { signal, deadline, ms }) {
7310
7329
  state: "problem",
7311
7330
  message: `${check.name} did not answer within ${ms / MS_PER_SECOND} s`
7312
7331
  };
7313
- if (error instanceof CliError) return {
7314
- state: "problem",
7315
- message: `${error.message}; ${error.hint}`,
7316
- code: error.code
7317
- };
7318
- return {
7319
- state: "problem",
7320
- message: `the ${check.name} row hit a bug: ${messageOf(error)}`
7321
- };
7332
+ return problemOfError(check.name, error);
7322
7333
  }
7323
- function withSignal(ctx, signal) {
7324
- return Object.create(ctx, { signal: { value: signal } });
7334
+ function rowContext(ctx, signal, read) {
7335
+ return Object.create(ctx, {
7336
+ signal: { value: signal },
7337
+ project: { get: () => {
7338
+ if (!read.ok) throw read.error;
7339
+ return read.project;
7340
+ } }
7341
+ });
7325
7342
  }
7326
- async function rowOf(check, ctx) {
7343
+ async function rowOf(check, ctx, read) {
7327
7344
  const ms = check.deadlineMs ?? DEFAULT_DEADLINE_MS;
7328
7345
  const signal = ctx.deadline(ms);
7329
7346
  let finding;
7330
7347
  try {
7331
- finding = await Promise.race([Promise.resolve().then(() => check.run(withSignal(ctx, signal))), untilAborted(signal)]);
7348
+ finding = await Promise.race([Promise.resolve().then(() => check.run(rowContext(ctx, signal, read))), untilAborted(signal)]);
7332
7349
  } catch (error) {
7333
7350
  if (ctx.signal.aborted) return void 0;
7334
- finding = problemOf(check, error, {
7351
+ finding = !read.ok && error === read.error ? {
7352
+ state: "skipped",
7353
+ message: "the Project file is unusable; the project row says why"
7354
+ } : problemOf(check, error, {
7335
7355
  signal: ctx.signal,
7336
7356
  deadline: signal,
7337
7357
  ms
@@ -7379,7 +7399,8 @@ function doctorCommand(cli, launch) {
7379
7399
  effect: "read",
7380
7400
  output: reportSchema,
7381
7401
  run: async (_input, ctx) => {
7382
- const checked = (await Promise.all(doctorChecks(cli, launch).map((check) => rowOf(check, ctx)))).filter((row) => row !== void 0);
7402
+ const read = readProject(cli.project, ctx);
7403
+ const checked = (await Promise.all(doctorChecks(cli, launch, read).map((check) => rowOf(check, ctx, read)))).filter((row) => row !== void 0);
7383
7404
  const rows = checked.map(({ row }) => row);
7384
7405
  const report = {
7385
7406
  rows,
@@ -7533,8 +7554,20 @@ var RunControl = class {
7533
7554
  }
7534
7555
  };
7535
7556
  //#endregion
7536
- //#region ../agent-cli/dist/built-ins/upgrade-notes.js
7557
+ //#region ../agent-cli/dist/built-ins/upgrades.js
7558
+ /** A skipped release's Upgrade steps, or its whole section when it lists none. */
7559
+ const upgradeSchema = union([object({
7560
+ version: string(),
7561
+ steps: array(string())
7562
+ }), object({
7563
+ version: string(),
7564
+ notes: string()
7565
+ })]);
7537
7566
  const RELEASE_HEADING = /^## v?(\S+)/;
7567
+ const UPGRADE_HEADING = /^### Upgrade from v\S+$/;
7568
+ const HEADING = /^#{1,6} /;
7569
+ const ITEM = /^[-*] /;
7570
+ const NOTHING_TO_DO = /^nothing to do\.?$/i;
7538
7571
  function releasesIn(changelog) {
7539
7572
  const releases = [];
7540
7573
  let current;
@@ -7549,16 +7582,33 @@ function releasesIn(changelog) {
7549
7582
  if (current !== void 0) releases.push(current);
7550
7583
  } else current?.lines.push(line);
7551
7584
  }
7552
- return releases.map(({ version, lines }) => ({
7585
+ return releases;
7586
+ }
7587
+ function listedSteps(lines) {
7588
+ const start = lines.findIndex((line) => UPGRADE_HEADING.test(line.trimEnd()));
7589
+ if (start === -1) return [];
7590
+ const end = lines.findIndex((line, i) => i > start && HEADING.test(line));
7591
+ const body = lines.slice(start + 1, end === -1 ? void 0 : end).filter((line) => line.trim() !== "");
7592
+ const starts = body.flatMap((line, i) => ITEM.test(line) ? [i] : []);
7593
+ return starts.map((first, i) => body.slice(first, starts[i + 1]).map((line) => line.trim()).join(" ").replace(ITEM, ""));
7594
+ }
7595
+ function upgradeOf({ version, lines }) {
7596
+ const listed = listedSteps(lines);
7597
+ if (listed.length === 0) return [{
7553
7598
  version,
7554
7599
  notes: lines.join("\n").trim()
7555
- }));
7600
+ }];
7601
+ const todo = listed.filter((step) => !NOTHING_TO_DO.test(step));
7602
+ return todo.length === 0 ? [] : [{
7603
+ version,
7604
+ steps: todo
7605
+ }];
7556
7606
  }
7557
7607
  /**
7558
- * What changed in each release after `from` up to `to`, oldest first, from the CHANGELOG.md in
7559
- * `packageDir`; none when the package ships no CHANGELOG.
7608
+ * What each release after `from` up to `to` asks of the operator, oldest first, from the
7609
+ * CHANGELOG.md in `packageDir`; none when the package ships no CHANGELOG.
7560
7610
  */
7561
- async function upgradeNotes(packageDir, from, to) {
7611
+ async function upgradesOf(packageDir, from, to) {
7562
7612
  let changelog;
7563
7613
  try {
7564
7614
  changelog = await readFile(join(packageDir, "CHANGELOG.md"), "utf8");
@@ -7566,7 +7616,7 @@ async function upgradeNotes(packageDir, from, to) {
7566
7616
  if (isMissingFile(error)) return [];
7567
7617
  throw error;
7568
7618
  }
7569
- return releasesIn(changelog).filter(({ version }) => compareVersions(version, from) > 0 && compareVersions(version, to) <= 0).sort((a, b) => compareVersions(a.version, b.version));
7619
+ return releasesIn(changelog).filter(({ version }) => compareVersions(version, from) > 0 && compareVersions(version, to) <= 0).sort((a, b) => compareVersions(a.version, b.version)).flatMap(upgradeOf);
7570
7620
  }
7571
7621
  //#endregion
7572
7622
  //#region ../agent-cli/dist/built-ins/update.js
@@ -7577,18 +7627,18 @@ const updateSchema = object({
7577
7627
  version: string(),
7578
7628
  prefix: string()
7579
7629
  }).nullable(),
7580
- upgrades: array(object({
7581
- version: string(),
7582
- notes: string()
7583
- })),
7630
+ upgrades: array(upgradeSchema),
7584
7631
  next: array(string())
7585
7632
  });
7586
7633
  function nothingToInstall({ from, latest }) {
7587
7634
  return standingOf(from, latest) === "current" ? `already on ${from}, the latest release: nothing to install` : `${from} is newer than the latest release ${latest}: nothing to install`;
7588
7635
  }
7636
+ function renderUpgrade(upgrade) {
7637
+ return "steps" in upgrade ? [`\nupgrade steps for ${upgrade.version}:`, ...upgrade.steps.map((step) => `- ${step}`)].join("\n") : `\nchanges in ${upgrade.version}:\n${upgrade.notes}`;
7638
+ }
7589
7639
  function renderUpdate(cli, install, update) {
7590
7640
  const { installed } = update;
7591
- const head = installed === null ? [nothingToInstall(update)] : [`installed ${install.package}@${installed.version} with the npm under ${installed.prefix}, replacing ${update.from}`, ...update.upgrades.map(({ version, notes }) => `\nchanges in ${version}:\n${notes}`)];
7641
+ const head = installed === null ? [nothingToInstall(update)] : [`installed ${install.package}@${installed.version} with the npm under ${installed.prefix}, replacing ${update.from}`, ...update.upgrades.map(renderUpgrade)];
7592
7642
  const next = update.next.map((command) => `next: ${command}, so the ${cli.name} skill describes ${update.latest}`);
7593
7643
  return (next.length === 0 ? head : [
7594
7644
  ...head,
@@ -7651,7 +7701,7 @@ function updateCommand(cli, install, { bin, node }) {
7651
7701
  version: latest,
7652
7702
  prefix
7653
7703
  },
7654
- upgrades: await upgradeNotes(plan.packageDir, from, latest),
7704
+ upgrades: await upgradesOf(plan.packageDir, from, latest),
7655
7705
  next: await skillNext(cli, latest, ctx)
7656
7706
  };
7657
7707
  if (ctx.signal.aborted) {
@@ -8075,7 +8125,7 @@ const RUNTIME_EXIT_CLASSES = {
8075
8125
  [STDIN_STALLED]: "caller",
8076
8126
  [INTERRUPTED]: "interrupted",
8077
8127
  [DEADLINE_EXCEEDED]: "wait",
8078
- [INTERNAL_CODE]: "bug",
8128
+ [INTERNAL]: "bug",
8079
8129
  [PERMISSION_DENIED]: "operator",
8080
8130
  [DISK_FULL]: "operator",
8081
8131
  [NOT_FOUND]: "caller",
@@ -9387,14 +9437,14 @@ async function runProcess(cli) {
9387
9437
  }
9388
9438
  //#endregion
9389
9439
  //#region package.json
9390
- var version$1 = "0.6.0";
9440
+ var version$1 = "0.8.0";
9391
9441
  var description = "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.";
9392
9442
  //#endregion
9393
9443
  //#region src/build.ts
9394
9444
  const build = {
9395
- "sha": "6821473",
9445
+ "sha": "d2cc610",
9396
9446
  "dirty": false,
9397
- "builtAt": "2026-10-20T02:08:46.888Z"
9447
+ "builtAt": "2026-10-20T09:33:37.544Z"
9398
9448
  };
9399
9449
  //#endregion
9400
9450
  //#region src/steps.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duyquangnvx/create-agent-cli",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -19,7 +19,7 @@
19
19
  "access": "public"
20
20
  },
21
21
  "devDependencies": {
22
- "@duyquangnvx/agent-cli": "0.6.0",
22
+ "@duyquangnvx/agent-cli": "0.8.0",
23
23
  "tsdown": "0.23.0",
24
24
  "zod": "4.6.7"
25
25
  },
@@ -1,5 +1,5 @@
1
1
  # Changelog
2
2
 
3
- What changed in each release of `{{packageName}}`, newest first, in the format of [Keep a Changelog 2.0](https://keepachangelog.com/en/2.0.0/). Each change lands under `## Unreleased` as it merges, grouped under `### Added`, `### Changed`, `### Fixed` or `### Removed`. A breaking change starts with `**Breaking:**` and names what breaks: a Command, a flag, a `--json` field, an exit code or the skill. A release turns `## Unreleased` into `## vX.Y.Z (YYYY-MM-DD)`.
3
+ What changed in each release of `{{packageName}}`, newest first, in the format of [Keep a Changelog 2.0](https://keepachangelog.com/en/2.0.0/). Each change lands under `## Unreleased` as it merges, grouped under `### Added`, `### Changed`, `### Fixed` or `### Removed`. A breaking change starts with `**Breaking:**` and names what breaks: a Command, a flag, a `--json` field, an exit code or the skill. A release turns `## Unreleased` into `## vX.Y.Z (YYYY-MM-DD)`. From the second release on, a release holds `### Upgrade from v<the release below>` listing what someone upgrading must do, or `- Nothing to do.`.
4
4
 
5
5
  ## Unreleased
@@ -32,7 +32,7 @@ This installs the CLI. The user has installed this skill already.
32
32
  ## Update
33
33
 
34
34
  1. Run `{{binName}} update --dry-run` to see the version it would install. If it fails to reach the registry, the sandbox refused the host, as [Sandbox](#sandbox) says: go on to the next step.
35
- 2. Ask the user to type `! {{binName}} update`; never run it yourself. It installs the latest release and prints what changed since the version it replaced: under `--json`, `upgrades` lists each release's notes, oldest first. Do each upgrade step those notes give, oldest first.
35
+ 2. Ask the user to type `! {{binName}} update`; never run it yourself. It installs the latest release and prints, for each release since the version it replaced, oldest first, the upgrade steps it asks of the user, or its whole notes when it lists none: under `--json`, each entry of `upgrades` holds `steps` or `notes`. Do each step, oldest first, and any the notes give.
36
36
  3. Hand the user each line in `next` to type with `!`: they bring this skill to the version `update` installed, or to the latest release when it installed nothing, as `doctor`'s `skill` row would. Load this skill again after, as [Check](#check) says.
37
37
  4. Confirm the new version with `{{binName}} --version`.
38
38