@duyquangnvx/create-agent-cli 0.28.0 → 0.29.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.29.0 (2026-11-09)
8
+
9
+ ### Added
10
+
11
+ - `agentCli` returns `defineDoctorCheck`, which types a `doctor` row's `ctx` by the CLI's ports and Project file and its `errors` by the Error table, so a row needs no `DoctorCheck<Ports, Settings>` annotation and a code the table lacks is a type error.
12
+
13
+ ### Changed
14
+
15
+ - **Breaking:** `defineCommand`, as `agentCli` returns it, is one method per Effect: `defineCommand.read`, `.act`, `.write`, `.destructive`, `.serve`, and `.list` for a List Command. A Command no longer declares `effect`, which the method sets, and `defineCommand.read` no longer takes `rows`. Each method has one signature, so a type error names the property at fault, such as a `run` returning a string where `output` has a number, rather than an Effect. The scaffolded template uses the methods.
16
+ - **Breaking:** `checkExamples` from `@duyquangnvx/agent-cli/testing` is `checkCli`, since it checks more than examples; the scaffolded template's test calls it.
17
+ - **Breaking:** `checkCli` also fails on a Command help shows that has no example, and on an arg or option without `.describe()`, so a CLI's test that passed `checkExamples` may fail until it adds them.
18
+ - `ctx.secret` with a name the Command does not declare is a type error that names the declared Secrets, or reads `"the Command declares no secrets"` when it declares none, rather than one about `never`.
19
+ - The scaffolded template pins `@biomejs/biome` 2.6.2 and `@duyquangnvx/harness` 0.5.2, the versions the Runtime itself is checked with.
20
+
21
+ ### Upgrade from v0.28.0
22
+
23
+ - Call `checkCli` where the CLI's tests called `checkExamples`, then give each Command and field it names an example or a `.describe()`.
24
+ - Declare each Command with the method of its Effect and delete its `effect`: `defineCommand({ effect: "write", … })` becomes `defineCommand.write({ … })`, and a read Command that declares `rows` becomes `defineCommand.list({ … })`.
25
+
7
26
  ## v0.28.0 (2026-11-09)
8
27
 
9
28
  ### Added
package/dist/bin.js CHANGED
@@ -7700,10 +7700,20 @@ function checkErrors(errors, label, table) {
7700
7700
  if (unknown.length > 0) throw new Error(`${label} declares ${unknown.join(", ")} in errors, a code the Error table lacks`);
7701
7701
  }
7702
7702
  function defineCommandFor() {
7703
- return declared;
7703
+ return {
7704
+ list: withEffect("read"),
7705
+ read: withEffect("read"),
7706
+ act: withEffect("act"),
7707
+ serve: withEffect("serve"),
7708
+ write: withEffect("write"),
7709
+ destructive: withEffect("destructive")
7710
+ };
7704
7711
  }
7705
- function declared(command) {
7706
- return command;
7712
+ function withEffect(effect) {
7713
+ return (command) => ({
7714
+ ...command,
7715
+ effect
7716
+ });
7707
7717
  }
7708
7718
  const defineCommand$1 = defineCommandFor();
7709
7719
  //#endregion
@@ -7724,9 +7734,9 @@ function cliErrorOf(code, init, options) {
7724
7734
  /** The bundler's stamp, absent when the CLI runs unbundled. Read per call, so a test can define it. */
7725
7735
  function buildStamp() {
7726
7736
  return {
7727
- "sha": "b509cf2",
7737
+ "sha": "0478338",
7728
7738
  "dirty": false,
7729
- "builtAt": "2026-11-09T01:38:28.000Z"
7739
+ "builtAt": "2026-11-09T05:16:34.000Z"
7730
7740
  };
7731
7741
  }
7732
7742
  /** The first line of `--version`: the version, and the commit and time of a stamped build. */
@@ -8604,7 +8614,7 @@ function renderSetup(cli, install, setup) {
8604
8614
  }
8605
8615
  /** The built-in `setup`, which runs from any copy, such as npx's, so it never asks to be the global one. */
8606
8616
  function setupCommand(cli, install, launch) {
8607
- return defineCommand$1({
8617
+ return defineCommand$1.write({
8608
8618
  description: "install this CLI and its skill on this machine",
8609
8619
  examples: [{
8610
8620
  command: `${cli.name} setup`,
@@ -8613,7 +8623,6 @@ function setupCommand(cli, install, launch) {
8613
8623
  command: `${cli.name} setup --dry-run`,
8614
8624
  note: "show what it would change"
8615
8625
  }],
8616
- effect: "write",
8617
8626
  errors: [
8618
8627
  NOT_GLOBAL_INSTALL,
8619
8628
  NOT_ON_PATH,
@@ -8705,7 +8714,7 @@ function interruptedAfterInstall(plan) {
8705
8714
  }
8706
8715
  /** The built-in `update`, for a CLI installed globally with npm. */
8707
8716
  function updateCommand(cli, install, launch) {
8708
- return defineCommand$1({
8717
+ return defineCommand$1.write({
8709
8718
  description: "install the latest version of this CLI from its registry",
8710
8719
  examples: [{
8711
8720
  command: `${cli.name} update`,
@@ -8714,7 +8723,6 @@ function updateCommand(cli, install, launch) {
8714
8723
  command: `${cli.name} update --dry-run`,
8715
8724
  note: "show the version it would install"
8716
8725
  }],
8717
- effect: "write",
8718
8726
  errors: [NOT_GLOBAL_INSTALL, ...NPM_ERRORS],
8719
8727
  output: updateSchema,
8720
8728
  plan: async (_input, ctx) => {
@@ -9077,7 +9085,7 @@ function renderInstalled(cli, data) {
9077
9085
  }
9078
9086
  /** The built-in `skill install`, for a CLI whose skill ships in its package. */
9079
9087
  function skillInstallCommand(cli, folder, agents) {
9080
- return defineCommand$1({
9088
+ return defineCommand$1.write({
9081
9089
  description: agents === void 0 ? "copy this CLI's skill into the project" : "copy this CLI's skill into the project and write its Agents section",
9082
9090
  examples: [{
9083
9091
  command: `${cli.name} skill install`,
@@ -9086,7 +9094,6 @@ function skillInstallCommand(cli, folder, agents) {
9086
9094
  command: `${cli.name} skill install --dry-run`,
9087
9095
  note: "show what it would change"
9088
9096
  }],
9089
- effect: "write",
9090
9097
  output: installSchema,
9091
9098
  plan: (_input, ctx) => planOf(cli, folder, agents, ctx),
9092
9099
  apply: async (plan, ctx) => {
@@ -9428,7 +9435,7 @@ function checkedRows(error, problems, table) {
9428
9435
  }
9429
9436
  /** The built-in `doctor`: the fixed rows around the CLI's own, each under its own deadline. */
9430
9437
  function doctorCommand(cli, launch) {
9431
- const doctor = defineCommand$1({
9438
+ const doctor = defineCommand$1.read({
9432
9439
  description: "check the setup and say what fixes each problem",
9433
9440
  examples: [{
9434
9441
  command: `${cli.name} doctor`,
@@ -9437,7 +9444,6 @@ function doctorCommand(cli, launch) {
9437
9444
  command: `${cli.name} doctor --json`,
9438
9445
  note: "get the rows and fixes as data"
9439
9446
  }],
9440
- effect: "read",
9441
9447
  output: reportSchema,
9442
9448
  run: async (_input, ctx) => {
9443
9449
  const rowsScope = {
@@ -9634,7 +9640,7 @@ function checkDocs({ name, docs }) {
9634
9640
  if (docs === void 0) return;
9635
9641
  if (!(docs !== "" && !isAbsolute(docs) && !normalize(docs).startsWith(".."))) throw new Error(`${name} declares docs ${JSON.stringify(docs)}: name a folder inside its package, relative to the package root`);
9636
9642
  }
9637
- function checkCli(cli) {
9643
+ function checkDeclared(cli) {
9638
9644
  const table = errorTableOf(cli);
9639
9645
  checkDocs(cli);
9640
9646
  if (cli.commands === void 0) {
@@ -9707,16 +9713,17 @@ function resolved(cli) {
9707
9713
  }
9708
9714
  /**
9709
9715
  * Takes the CLI's ports factory, Project file and Error table once; the `defineCommand`,
9710
- * `defineCli` and `cliError` it returns are typed by them.
9716
+ * `defineDoctorCheck`, `defineCli` and `cliError` it returns are typed by them.
9711
9717
  */
9712
9718
  function agentCli(values = {}) {
9713
9719
  checkOwnCodes(errorTableOf(values));
9714
9720
  return {
9715
9721
  defineCommand: defineCommandFor(),
9716
- defineCli: (cli) => checkCli({
9722
+ defineCli: (cli) => checkDeclared({
9717
9723
  ...resolved(cli),
9718
9724
  ...values
9719
9725
  }),
9726
+ defineDoctorCheck: (row) => row,
9720
9727
  cliError: cliErrorOf
9721
9728
  };
9722
9729
  }
@@ -11744,7 +11751,7 @@ async function runProcess(cli) {
11744
11751
  //#region package.json
11745
11752
  var package_default = {
11746
11753
  name: "@duyquangnvx/create-agent-cli",
11747
- version: "0.28.0",
11754
+ version: "0.29.0",
11748
11755
  description: "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
11749
11756
  type: "module",
11750
11757
  engines: { "node": ">=22.12.0" },
@@ -12031,13 +12038,12 @@ async function ensureFree(path) {
12031
12038
  //#region src/bin.ts
12032
12039
  await runProcess(defineCli({
12033
12040
  manifest: package_default,
12034
- command: defineCommand({
12041
+ command: defineCommand.write({
12035
12042
  description: "Create a new Agent CLI project in ./<name>",
12036
12043
  examples: [{
12037
12044
  command: "create-agent-cli weather --description \"Weather forecasts\"",
12038
12045
  note: "create ./weather, install dependencies and make the first commit"
12039
12046
  }],
12040
- effect: "write",
12041
12047
  errors: ["STEP_FAILED"],
12042
12048
  args: object({ name: string().regex(PACKAGE_NAME, "must be a lowercase npm package name, such as weather or @me/weather").describe("package name; its unscoped part names the bin and the directory") }),
12043
12049
  options: object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duyquangnvx/create-agent-cli",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -21,7 +21,7 @@
21
21
  "access": "public"
22
22
  },
23
23
  "devDependencies": {
24
- "@duyquangnvx/agent-cli": "0.28.0",
24
+ "@duyquangnvx/agent-cli": "0.29.0",
25
25
  "tsdown": "0.23.0",
26
26
  "zod": "4.6.7"
27
27
  },
@@ -1,4 +1,4 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/2.5.15/schema.json",
2
+ "$schema": "https://biomejs.dev/schemas/2.6.2/schema.json",
3
3
  "extends": ["@duyquangnvx/harness/biome"]
4
4
  }
@@ -28,9 +28,9 @@
28
28
  "release": "node scripts/release.ts"
29
29
  },
30
30
  "devDependencies": {
31
- "@biomejs/biome": "2.5.15",
31
+ "@biomejs/biome": "2.6.2",
32
32
  "@duyquangnvx/agent-cli": "{{runtimeVersion}}",
33
- "@duyquangnvx/harness": "0.3.1",
33
+ "@duyquangnvx/harness": "0.5.2",
34
34
  "@duyquangnvx/release": "0.9.0",
35
35
  "@types/node": "^22.19.0",
36
36
  "tsdown": "0.23.0",
@@ -9,7 +9,7 @@ const { defineCli, defineCommand } = agentCli({ errors, ports });
9
9
  const FREE_LANGUAGE = "en";
10
10
  const PRICE_PER_CARD = "$0.01";
11
11
 
12
- const greet = defineCommand({
12
+ const greet = defineCommand.read({
13
13
  description: "Greet someone by name",
14
14
  examples: [
15
15
  {
@@ -21,14 +21,13 @@ const greet = defineCommand({
21
21
  note: "get the greeting as JSON",
22
22
  },
23
23
  ],
24
- effect: "read",
25
24
  args: z.object({
26
25
  name: z.string().min(1).describe("who to greet"),
27
26
  }),
28
27
  run: ({ name }) => ({ greeting: `Hello, ${name}!` }),
29
28
  });
30
29
 
31
- const card = defineCommand({
30
+ const card = defineCommand.write({
32
31
  description: "Write a greeting card to <name>.txt",
33
32
  help:
34
33
  `A card in English is free; one in another language costs ${PRICE_PER_CARD} and needs --yes. ` +
@@ -47,7 +46,6 @@ const card = defineCommand({
47
46
  note: "sign the card from Ben and Cy",
48
47
  },
49
48
  ],
50
- effect: "write",
51
49
  errors: ["LANGUAGE_UNSUPPORTED"],
52
50
  args: z.object({
53
51
  name: z
@@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
3
3
  import { join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { type Files, runCli } from "@duyquangnvx/agent-cli";
6
- import { checkExamples } from "@duyquangnvx/agent-cli/testing";
6
+ import { checkCli } from "@duyquangnvx/agent-cli/testing";
7
7
  import { describe, expect, test } from "vitest";
8
8
  import manifest from "../package.json" with { type: "json" };
9
9
  import { cli } from "../src/cli.ts";
@@ -74,8 +74,8 @@ function run(
74
74
  return runCli(cli, argv, { cwd: CWD, files, ports: { greetings }, stdin });
75
75
  }
76
76
 
77
- test("every example parses, names a Command and passes its schemas", async () => {
78
- await checkExamples(cli);
77
+ test("every example runs, every Command help shows has one, and every arg and option is described", async () => {
78
+ await checkCli(cli);
79
79
  });
80
80
 
81
81
  describe("greet", () => {