@duyquangnvx/create-agent-cli 0.18.0 → 0.20.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,29 @@ What changed in each release of `@duyquangnvx/agent-cli` (the Runtime) and `@duy
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## v0.20.0 (2026-10-28)
8
+
9
+ ### Changed
10
+
11
+ - **Breaking:** the hints of `OUTPUT_EXISTS` and `ALREADY_EXISTS` read `once the user agrees to replace it, run the Next step` (`them` for several, and `remove` for `skill install` removing an edited file), `NOT_CONFIRMED`'s reads `once the user approves the cost, run the Next step`, and those of `CONFIRMATION_REQUIRED` and `CONFIRMATION_MISMATCH` read `once the user confirms the target, run the Next step`, so an agent asks before adding `--force`, `--yes` or `--confirm` without a skill telling it to. The docs' example `bundle` gives `BUNDLE_EXISTS` the same kind of hint.
12
+ - **Breaking:** the scaffolded skill's Recovering from errors section tells the agent to do what the hint says, and keeps a row only for `LANGUAGE_UNSUPPORTED`, which now asks the user whether an English card will do before running the Next step. The rows for `OUTPUT_EXISTS`, `NOT_CONFIRMED` and every exit code are gone, since the hints and `--help` say the same.
13
+
14
+ ### Fixed
15
+
16
+ - `--force` in help reads `replace a file the run writes that already exists; --dry-run lists every file it writes`, since `--dry-run` lists the files a run writes, not which of them exist.
17
+
18
+ ## v0.19.0 (2026-10-28)
19
+
20
+ ### Added
21
+
22
+ - `defineCli({ skill })` takes `{ add: "<command line>" }`, the whole line that installs the skill from a Skill Hub whose line the Runtime cannot build. `doctor`'s `skill` fix and `update`'s `next` give it verbatim for a missing copy or one that states no version, as `npx -y skills update <name> && <line>` when another copy is older than the CLI, and `npx -y skills update <name>` for an older copy alone. `defineCli` throws on an empty line or one holding a line break.
23
+ - The Scaffolder's `--skill-hub-api` takes `--skill-add "<command line>"`, which it writes as `skill: { add }` and, after `! `, as the README's skill line. Either flag without the other is a usage error, `SKILL_ADD_MISSING` or `SKILL_HUB_API_MISSING`, and an empty or multi-line `--skill-add` is `SKILL_ADD_INVALID`.
24
+
25
+ ### Removed
26
+
27
+ - **Breaking:** `defineCli({ skill })` no longer takes `{ page }`, and `doctor` and `update` no longer name a page; declare the hub's install line as `{ add }`.
28
+ - **Breaking:** the Scaffolder's `--skill-page` and its usage error `SKILL_PAGE_MISSING` are gone; pass `--skill-add`.
29
+
7
30
  ## v0.18.0 (2026-10-28)
8
31
 
9
32
  ### Added
package/README.md CHANGED
@@ -6,6 +6,6 @@ Creates a new Agent CLI repository already wired to [`@duyquangnvx/agent-cli`](h
6
6
  pnpm create @duyquangnvx/agent-cli <name> --description "One line saying what it does" --skill-hub-git https://github.com/<owner>/skills.git
7
7
  ```
8
8
 
9
- The new repository has an Error table, a port, two example Commands (one writing a file at a cost) and their test, a bundled build that stamps the commit and build time into `--version`, CI, a release script, a CHANGELOG, an `AGENTS.md` whose Agents section points Coding agents at the Runtime's docs in `node_modules`, and a skeleton skill for the new CLI whose setup reference installs, checks and updates it, so a user installs the skill first and lets their agent do the rest. `--skill-hub-git` names the git Skill Hub each release commits that skill to and agents install it from, and `--skill-hub-api` with `--skill-page` an API Skill Hub that gives each user their own install line from that page; `--dependency` instead starts a CLI each project pins as a devDependency, run as `npx -q --no-install <name>`, whose skill ships inside the package for its `skill install` to copy into the project. Pass `--dry-run` to see what it would write.
9
+ The new repository has an Error table, a port, two example Commands (one writing a file at a cost) and their test, a bundled build that stamps the commit and build time into `--version`, CI, a release script, a CHANGELOG, an `AGENTS.md` whose Agents section points Coding agents at the Runtime's docs in `node_modules`, and a skeleton skill for the new CLI whose setup reference installs, checks and updates it, so a user installs the skill first and lets their agent do the rest. `--skill-hub-git` names the git Skill Hub each release commits that skill to and agents install it from, and `--skill-hub-api` with `--skill-add` an API Skill Hub and the whole line that installs the skill from it; `--dependency` instead starts a CLI each project pins as a devDependency, run as `npx -q --no-install <name>`, whose skill ships inside the package for its `skill install` to copy into the project. Pass `--dry-run` to see what it would write.
10
10
 
11
11
  See the [repository](https://github.com/duyquangnvx/cli-for-agent) for an overview and the [Runtime's docs](https://github.com/duyquangnvx/cli-for-agent/blob/main/packages/agent-cli/docs/README.md) for how to build on it.
package/dist/bin.js CHANGED
@@ -6471,7 +6471,7 @@ const YES_FLAG = standardFlag("--yes", {
6471
6471
  schema: boolean()
6472
6472
  });
6473
6473
  const FORCE_FLAG = standardFlag("--force", {
6474
- description: "replace the files the run writes when they exist; --dry-run lists them",
6474
+ description: "replace a file the run writes that already exists; --dry-run lists every file it writes",
6475
6475
  takes: "nothing",
6476
6476
  schema: boolean()
6477
6477
  });
@@ -6764,9 +6764,9 @@ function toolRows(tools, commands) {
6764
6764
  /** The bundler's stamp, absent when the CLI runs unbundled. Read per call, so a test can define it. */
6765
6765
  function buildStamp() {
6766
6766
  return {
6767
- "sha": "e9645e3",
6767
+ "sha": "330940a",
6768
6768
  "dirty": false,
6769
- "builtAt": "2026-10-28T08:17:12.000Z"
6769
+ "builtAt": "2026-10-28T15:25:01.000Z"
6770
6770
  };
6771
6771
  }
6772
6772
  /** The first line of `--version`: the version, and the commit and time of a stamped build. */
@@ -7616,18 +7616,16 @@ async function skillCopies(cliName, version, { cwd, env }) {
7616
7616
  return copies;
7617
7617
  }
7618
7618
  const has = (copies, wanted) => copies.some(({ state }) => state === wanted);
7619
+ function addLineOf(cliName, from) {
7620
+ return "add" in from ? from.add : `npx -y skills add ${from.source} --skill ${cliName} -g`;
7621
+ }
7619
7622
  function fixFor(cliName, copies, from) {
7620
7623
  const update = `npx -y skills update ${cliName}`;
7621
- const add = from !== void 0 && "source" in from ? `npx -y skills add ${from.source} --skill ${cliName} -g` : void 0;
7624
+ const add = from === void 0 ? void 0 : addLineOf(cliName, from);
7622
7625
  if (copies.length === 0) return add;
7623
7626
  if (has(copies, "unversioned") && add !== void 0) return has(copies, "behind") ? `${update} && ${add}` : add;
7624
- if (from !== void 0 && "page" in from) return has(copies, "behind") ? update : void 0;
7625
7627
  return has(copies, "behind") || has(copies, "unversioned") ? update : void 0;
7626
7628
  }
7627
- function pageNote(copies, from) {
7628
- if (from === void 0 || !("page" in from)) return;
7629
- return copies.length === 0 || has(copies, "unversioned") ? `copy your install line from ${from.page}` : void 0;
7630
- }
7631
7629
  const SKILL_STATE_NOTES = {
7632
7630
  current: "the same as this build",
7633
7631
  behind: "older than this build",
@@ -7638,13 +7636,11 @@ const SKILL_STATE_NOTES = {
7638
7636
  function describeCopy({ path, version, state }) {
7639
7637
  return `${path}${version === void 0 ? "" : ` ${version}`} (${SKILL_STATE_NOTES[state]})`;
7640
7638
  }
7641
- /** A skill on a Skill Hub, which `from` says how to install; with neither, no line adds it. */
7639
+ /** A skill on a Skill Hub, which `from` says how to install; without it, no line adds it. */
7642
7640
  function hubSkill(cli, from) {
7643
7641
  const findingOf = (copies) => {
7644
7642
  const fix = fixFor(cli.name, copies, from);
7645
- const note = pageNote(copies, from);
7646
- const found = copies.length === 0 ? `no copy of the ${cli.name} skill is installed` : copies.map(describeCopy).join("; ");
7647
- const message = note === void 0 ? found : `${found}; ${note}`;
7643
+ const message = copies.length === 0 ? `no copy of the ${cli.name} skill is installed` : copies.map(describeCopy).join("; ");
7648
7644
  return fix === void 0 ? {
7649
7645
  state: "ok",
7650
7646
  message
@@ -7665,8 +7661,6 @@ function hubSkill(cli, from) {
7665
7661
  notice: async () => {},
7666
7662
  nextAt: async (version, ctx) => {
7667
7663
  const copies = await skillCopies(cli.name, version, ctx);
7668
- const note = pageNote(copies, from);
7669
- if (note !== void 0) ctx.notice(note);
7670
7664
  const fix = fixFor(cli.name, copies, from);
7671
7665
  return fix === void 0 ? [] : [fix];
7672
7666
  }
@@ -8028,7 +8022,7 @@ async function checkOutputs({ rerun, commandLine, force, files }, outputs) {
8028
8022
  throw new CliError({
8029
8023
  code: OUTPUT_EXISTS,
8030
8024
  message: `\`${commandLine}\` would overwrite ${existing.join(", ")}`,
8031
- hint: `to replace ${them}, run the Next step`,
8025
+ hint: `once the user agrees to replace ${them}, run the Next step`,
8032
8026
  next: [rerun({ add: [FORCE_FLAG.flag] })]
8033
8027
  });
8034
8028
  }
@@ -8050,7 +8044,7 @@ function outputsGuard(files, cwd) {
8050
8044
  throw new CliError({
8051
8045
  code: ALREADY_EXISTS,
8052
8046
  message: error.message,
8053
- hint: "to replace it, run the Next step",
8047
+ hint: "once the user agrees to replace it, run the Next step",
8054
8048
  next: [forceStep]
8055
8049
  }, { cause: error });
8056
8050
  }
@@ -8176,7 +8170,7 @@ function editedRemovals(plan, ctx) {
8176
8170
  return new CliError({
8177
8171
  code: OUTPUT_EXISTS,
8178
8172
  message: `skill install would remove ${plan.remove.map((file) => join(plan.copy, file)).join(", ")}`,
8179
- hint: `to remove ${them}, run the Next step`,
8173
+ hint: `once the user agrees to remove ${them}, run the Next step`,
8180
8174
  next: [ctx.rerun({ add: [FORCE_FLAG.flag] })]
8181
8175
  });
8182
8176
  }
@@ -9199,13 +9193,15 @@ function checkTree(commands, path, table) {
9199
9193
  else checkTree(node, at, table);
9200
9194
  }
9201
9195
  }
9202
- function checkSkillPage({ name, skill }) {
9203
- if (skill === void 0 || !("page" in skill)) return;
9204
- if (!URL.canParse(skill.page) || new URL(skill.page).protocol !== "https:") throw new Error(`${name} declares skill.page ${skill.page}, which is not an https URL`);
9196
+ function checkSkillAdd({ name, skill }) {
9197
+ if (skill === void 0 || !("add" in skill)) return;
9198
+ const declares = `${name} declares skill.add ${JSON.stringify(skill.add)}`;
9199
+ if (skill.add.trim() === "") throw new Error(`${declares}, which is empty`);
9200
+ if (/[\r\n]/.test(skill.add)) throw new Error(`${declares}, which holds a line break`);
9205
9201
  }
9206
9202
  function checkCli(cli) {
9207
9203
  const table = errorTableOf(cli);
9208
- checkSkillPage(cli);
9204
+ checkSkillAdd(cli);
9209
9205
  if (cli.commands === void 0) {
9210
9206
  checkCommand(cli.command, commandLabel([], cli.name).join(" "), table);
9211
9207
  if (skillOf$1(cli, installOf(cli)).kind === "packaged") throw new Error(`${cli.name} declares a Packaged skill, which only a CLI with several Commands can install`);
@@ -10710,7 +10706,7 @@ const CONFIRMATION_MISMATCH = "CONFIRMATION_MISMATCH";
10710
10706
  function checkConfirmation({ rerun, commandLine, given }, expected) {
10711
10707
  if (given === expected) return;
10712
10708
  const advice = {
10713
- hint: "to confirm, run the Next step",
10709
+ hint: "once the user confirms the target, run the Next step",
10714
10710
  next: [rerun({
10715
10711
  remove: [CONFIRM_FLAG.flag],
10716
10712
  add: [CONFIRM_FLAG.flag, expected]
@@ -10736,7 +10732,7 @@ function checkCost({ rerun, commandLine, yes }, cost) {
10736
10732
  throw new CliError({
10737
10733
  code: NOT_CONFIRMED,
10738
10734
  message: `\`${commandLine}\` needs ${YES_FLAG.flag}: ${cost}`,
10739
- hint: "to approve the cost, run the Next step",
10735
+ hint: "once the user approves the cost, run the Next step",
10740
10736
  next: [rerun({ add: [YES_FLAG.flag] })]
10741
10737
  });
10742
10738
  }
@@ -11294,7 +11290,7 @@ async function runProcess(cli) {
11294
11290
  //#region package.json
11295
11291
  var package_default = {
11296
11292
  name: "@duyquangnvx/create-agent-cli",
11297
- version: "0.18.0",
11293
+ version: "0.20.0",
11298
11294
  description: "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
11299
11295
  type: "module",
11300
11296
  engines: { "node": ">=22.12.0" },
@@ -11345,31 +11341,36 @@ const PARTIALS_DIR = fileURLToPath(new URL("../partials/", import.meta.url));
11345
11341
  const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
11346
11342
  const COMMANDS_ANCHOR = " commands: {";
11347
11343
  const GLOBAL_INSTALL = ` install: "global",\n`;
11348
- const NO_SKILL_HUB = "<!-- Set `skill.source` in `defineCli` and `skillHub: { git }` in `scripts/release.ts` to the git URL of the Skill Hub each release commits the skill to, or set `skill.page` to the Skill Hub's page where each user copies their own install line and `skillHub: { api }` to the API Skill Hub each release publishes the skill to. Then put here what `node dist/bin.js doctor` gives in its `skill` row: the `! npx -y skills add` line of its fix, or the sentence naming the page. -->";
11349
- function skillHubOf({ git, api, page }) {
11344
+ const NO_SKILL_HUB = "<!-- Set `skill.source` in `defineCli` and `skillHub: { git }` in `scripts/release.ts` to the git URL of the Skill Hub each release commits the skill to, or set `skill.add` to the whole line that installs the skill and `skillHub: { api }` to the API Skill Hub each release publishes the skill to. Then put here what `node dist/bin.js doctor` gives in its `skill` row: the line of its fix, after `! `. -->";
11345
+ function skillHubOf({ git, api, add }) {
11350
11346
  if (git !== void 0 && api !== void 0) throw new UsageError({
11351
11347
  code: "SKILL_HOMES_CONFLICT",
11352
11348
  message: "--skill-hub-git and --skill-hub-api each name the Skill Hub the skill goes to",
11353
11349
  hint: "pass one of them"
11354
11350
  });
11355
- if (api !== void 0 && page === void 0) throw new UsageError({
11356
- code: "SKILL_PAGE_MISSING",
11357
- message: "--skill-hub-api needs --skill-page, the hub's page where each user copies their own install line",
11358
- hint: "add --skill-page with the skill's page on the hub"
11351
+ if (api !== void 0 && add === void 0) throw new UsageError({
11352
+ code: "SKILL_ADD_MISSING",
11353
+ message: "--skill-hub-api needs --skill-add, the whole line that installs the skill from the hub",
11354
+ hint: "add --skill-add with the hub's install line for the skill"
11359
11355
  });
11360
- if (page !== void 0 && api === void 0) throw new UsageError({
11356
+ if (add !== void 0 && api === void 0) throw new UsageError({
11361
11357
  code: "SKILL_HUB_API_MISSING",
11362
- message: "--skill-page names a page of an API Skill Hub, which --skill-hub-api names",
11358
+ message: "--skill-add is the install line of an API Skill Hub, which --skill-hub-api names",
11363
11359
  hint: "add --skill-hub-api with the hub's URL"
11364
11360
  });
11361
+ if (add !== void 0 && (add.trim() === "" || /[\r\n]/.test(add))) throw new UsageError({
11362
+ code: "SKILL_ADD_INVALID",
11363
+ message: `--skill-add ${JSON.stringify(add)} is not one command line`,
11364
+ hint: "pass the hub's install line for the skill, on one line"
11365
+ });
11365
11366
  if (git !== void 0) return {
11366
11367
  kind: "git",
11367
11368
  url: git
11368
11369
  };
11369
- return api === void 0 || page === void 0 ? void 0 : {
11370
+ return api === void 0 || add === void 0 ? void 0 : {
11370
11371
  kind: "api",
11371
11372
  url: api,
11372
- page
11373
+ add
11373
11374
  };
11374
11375
  }
11375
11376
  function installModeOf(flags, dependency, binName) {
@@ -11391,7 +11392,7 @@ function installModeOf(flags, dependency, binName) {
11391
11392
  }
11392
11393
  function skillLineOf(skillHub, binName) {
11393
11394
  if (skillHub === void 0) return NO_SKILL_HUB;
11394
- return skillHub.kind === "git" ? `\`! npx -y skills add ${skillHub.url} --skill ${binName} -g\`` : `The skill's line is your own: copy your install line from ${skillHub.page}, then type it.`;
11395
+ return skillHub.kind === "git" ? `\`! npx -y skills add ${skillHub.url} --skill ${binName} -g\`` : `\`! ${skillHub.add}\``;
11395
11396
  }
11396
11397
  async function partialsOf(mode, values) {
11397
11398
  const partial = async (name) => fill(await readFile(join(PARTIALS_DIR, mode, `${name}.md`), "utf8"), values, false).trimEnd();
@@ -11437,7 +11438,7 @@ async function globalSetup(skillHub, packageName, binName) {
11437
11438
  };
11438
11439
  }
11439
11440
  function skillOf(skillHub) {
11440
- return skillHub.kind === "git" ? `{ source: ${JSON.stringify(skillHub.url)} }` : `{ page: ${JSON.stringify(skillHub.page)} }`;
11441
+ return skillHub.kind === "git" ? `{ source: ${JSON.stringify(skillHub.url)} }` : `{ add: ${JSON.stringify(skillHub.add)} }`;
11441
11442
  }
11442
11443
  async function dependencySetup(packageName, binName) {
11443
11444
  const runAs = runAsOf({
@@ -11653,15 +11654,15 @@ await runProcess(defineCli({
11653
11654
  repository: url({ protocol: /^https$/ }).optional().describe("the https URL of the CLI's git repository"),
11654
11655
  skillHubGit: url({ protocol: /^https$/ }).refine((url) => new URL(url).search === "" && new URL(url).hash === "", "must name the hub's repository with no query or fragment").refine((url) => new URL(url).pathname.endsWith(".git"), "must end in .git").optional().describe("the https git URL of a git Skill Hub each release commits the CLI's skill to"),
11655
11656
  skillHubApi: url({ protocol: /^https$/ }).optional().describe("the https URL of an API Skill Hub each release publishes the CLI's skill to"),
11656
- skillPage: url({ protocol: /^https$/ }).optional().describe("the https page of the skill on the API Skill Hub, where each user copies their own install line"),
11657
+ skillAdd: string().optional().describe("the whole line that installs the CLI's skill from the API Skill Hub"),
11657
11658
  dependency: boolean().default(false).describe("install the CLI per project, as a devDependency, with its skill shipped inside the package")
11658
11659
  }),
11659
- plan: async ({ name, description, install, git, repository, skillHubGit, skillHubApi, skillPage, dependency }, ctx) => {
11660
+ plan: async ({ name, description, install, git, repository, skillHubGit, skillHubApi, skillAdd, dependency }, ctx) => {
11660
11661
  const binName = PACKAGE_NAME.exec(name)?.[1] ?? name;
11661
11662
  const mode = installModeOf({
11662
11663
  git: skillHubGit,
11663
11664
  api: skillHubApi,
11664
- page: skillPage
11665
+ add: skillAdd
11665
11666
  }, dependency, binName);
11666
11667
  const path = resolve(ctx.cwd, binName);
11667
11668
  await ensureFree(path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duyquangnvx/create-agent-cli",
3
- "version": "0.18.0",
3
+ "version": "0.20.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.18.0",
24
+ "@duyquangnvx/agent-cli": "0.20.0",
25
25
  "tsdown": "0.23.0",
26
26
  "zod": "4.6.7"
27
27
  },
@@ -1 +1 @@
1
- The `skill` row names every installed copy of this skill against the installed CLI. A missing copy, or one that states no version, is installed afresh as the row says, with its `npx -y skills add` fix or from the page its message names, since `npx skills update` skips a copy it did not install. After a fix that runs the `skills` installer, load this skill again.
1
+ The `skill` row names every installed copy of this skill against the installed CLI. A missing copy, or one that states no version, is installed afresh with the row's fix, since `npx skills update` skips a copy it did not install. After a fix that runs the `skills` installer, load this skill again.
@@ -47,18 +47,10 @@ Pass `--json` whenever you parse the output: stdout then holds only the data. A
47
47
 
48
48
  ## Recovering from errors
49
49
 
50
- Errors go to stderr and end with a `hint:` line, then a `run:` line for each command to run next, in order. Under `--json`, every stderr line is one JSON object: `{"progress":"..."}` or `{"notice":"..."}` while it runs, then `{ errors: [{ code, message, hint, next }] }` as the last line, `next` holding those commands when there are any. Match the first row that fits:
50
+ Errors go to stderr and end with a `hint:` line, then a `run:` line for each command to run next, in order. Under `--json`, every stderr line is one JSON object: `{"progress":"..."}` or `{"notice":"..."}` while it runs, then `{ errors: [{ code, message, hint, next }] }` as the last line, `next` holding those commands when there are any. Do what the hint says, except for a code below:
51
51
 
52
- | Code or exit | What to do |
52
+ | Code | What to do |
53
53
  |---|---|
54
- | `OUTPUT_EXISTS` | The run would overwrite a file. Ask the user before adding `--force`. |
55
- | `NOT_CONFIRMED` | The run costs money. Tell the user the cost from the message; add `--yes` only once they approve it. |
56
- | `LANGUAGE_UNSUPPORTED` | The greetings service lacks that language. Run the Next step's English card, or ask the user for another language. |
57
- | exit 1 | You called it wrong. Fix what the message and hint point at, then run it again. |
58
- | exit 2 | Something on this machine needs a person. Tell the user what the hint says and ask them to fix it. |
59
- | exit 3 | The source it talks to is broken. Tell the user; running it again won't help. |
60
- | exit 75 | Nobody needs to act. Wait, then run it again, as the hint says. |
61
- | exit 70, `INTERNAL` | A bug in {{binName}}. Run it once more with `{{debugVariable}}=1` for the stack trace, then give the user stderr verbatim. |
62
- | exit 130 or 143 | It was interrupted. Run it again only if the user still wants it. |
63
-
64
- <!-- Add a row above the exit rows for each CliError code {{binName}} throws, saying what to do about it. -->
54
+ | `LANGUAGE_UNSUPPORTED` | The user asked for that language. Ask whether an English card will do before running the Next step. |
55
+
56
+ <!-- Add a row for each code {{binName}} throws whose right move goes beyond its hint. A guardrail on a flag goes in the workflow step that passes it instead. -->
@@ -207,15 +207,13 @@ describe("install lines", () => {
207
207
  expect(await text("README.md")).toContain(`\`! ${install}\``);
208
208
  });
209
209
 
210
- // A hub that gives each user their own install line has no fix: the row's message names its page.
211
210
  test.skipIf(cli.skill === undefined)(
212
- "the README installs the skill as doctor's skill row says",
211
+ "the README installs the skill with doctor's skill line",
213
212
  async () => {
214
- const { fix, message } = (await doctorRows()).skill ?? { message: "" };
215
- const line = fix === undefined ? message.split("; ").at(-1) : `\`! ${fix}\``;
213
+ const fix = (await doctorRows()).skill?.fix;
216
214
 
217
- expect(line).toMatch(/^`! |^copy your install line from /);
218
- expect(await text("README.md")).toContain(line);
215
+ expect(fix).toBeDefined();
216
+ expect(await text("README.md")).toContain(`\`! ${fix}\``);
219
217
  },
220
218
  );
221
219