@duyquangnvx/create-agent-cli 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duyquangnvx/create-agent-cli",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "files": [
13
13
  "dist",
14
+ "partials",
14
15
  "template",
15
16
  "CHANGELOG.md"
16
17
  ],
@@ -19,7 +20,7 @@
19
20
  "access": "public"
20
21
  },
21
22
  "devDependencies": {
22
- "@duyquangnvx/agent-cli": "0.13.1",
23
+ "@duyquangnvx/agent-cli": "0.14.0",
23
24
  "tsdown": "0.23.0",
24
25
  "zod": "4.6.7"
25
26
  },
@@ -0,0 +1,7 @@
1
+ Add the CLI to each project that uses it, then copy its skill into that project. In Claude Code, type each line in the prompt, in the project's folder:
2
+
3
+ `! pnpm add -D {{packageName}}`
4
+
5
+ `! {{runAs}} skill install`
6
+
7
+ With npm, the first line is `! npm install -D {{packageName}}`.
@@ -0,0 +1,12 @@
1
+ This installs the project's copy of {{binName}}. Always run it as `{{runAs}}`: a bare `npx {{binName}}` runs whatever npm package holds the name.
2
+
3
+ 1. When `{{runAs}}` stops with npx's `missing packages` error, no copy is installed at or above the working directory. Read the `package.json` files from the working directory up, and hand the user one line to type in the project's folder, as [Sandbox](#sandbox) says:
4
+ - When one lists {{packageName}} in `dependencies` or `devDependencies`, the project's install line, `! pnpm install` or `! npm install`, which installs the version the project pins.
5
+ - Otherwise the line that adds it as a devDependency, at the version the project then pins:
6
+
7
+ `! pnpm add -D {{packageName}}`
8
+
9
+ and with npm `! npm install -D {{packageName}}`.
10
+
11
+ With yarn or bun, hand the user that package manager's own line.
12
+ 2. Finish with [Check](#check): its `skill` row brings this skill to the installed version. {{binName}} is ready when `doctor` exits 0 with nothing in `next`.
@@ -0,0 +1,3 @@
1
+ Adding or moving {{binName}} fetches it from the registry, which Claude Code's sandbox may refuse. Hand the user the line to type with `!` in this session, which runs it outside the sandbox.
2
+
3
+ `doctor`'s `registry` row asks the registry {{binName}} is published to for its latest release. If the sandbox refuses that host, `doctor` still passes with only that row failing. If the user wants the row to work inside the sandbox, give them this block for their Claude Code settings, naming the registry's host, and let them decide:
@@ -0,0 +1 @@
1
+ The `skill` row compares this skill's copy at the project root, `.claude/skills/{{binName}}`, with the one the project's {{binName}} ships. When the copy is missing or differs, as after an update, run its `{{runAs}} skill install` fix, then load this skill again. A copy under `~/.claude/skills/{{binName}}` shadows the project's: ask the user to remove it.
@@ -0,0 +1,4 @@
1
+ 1. Run `{{runAs}} doctor --json` and read its `registry` row. When it reports no newer release, {{binName}} is current: stop here. When a newer release exists, the row's fix is the line that moves {{packageName}} to it; with yarn or bun the row names the version without a line, so use that package manager's own line for it. If the row fails to reach the registry, the sandbox refused the host, as [Sandbox](#sandbox) says.
2
+ 2. Hand the user that line to type with `!`.
3
+ 3. Read the `CHANGELOG.md` that `{{runAs}} --version` names, from the top down to the version the project left, and do each upgrade step it lists, oldest first.
4
+ 4. Finish with [Check](#check): its `skill` row brings this skill to the new version.
@@ -0,0 +1,5 @@
1
+ Install the skill first, so that the coding agent loading it can install and check the CLI. In Claude Code, type each line in the prompt:
2
+
3
+ {{skillLine}}
4
+
5
+ `! npm install --global {{packageName}}@latest`
@@ -0,0 +1,10 @@
1
+ This installs the CLI. The user has installed this skill already.
2
+
3
+ 1. Run `node --version` and compare it with the range `npm view {{packageName}} engines.node` prints, which `doctor`'s `node` row also checks once the CLI is installed. Under nvm every Node version has its own global packages, so {{binName}} lands under the Node this shell runs. If that Node is too old, or is not the one the user's terminal runs, tell the user and let them choose before going on.
4
+ 2. Hand the user the install line to type, as [Sandbox](#sandbox) says:
5
+
6
+ `! npm install --global {{packageName}}@latest`
7
+
8
+ Never run `npx {{binName}}`: that runs whatever npm package holds the name.
9
+ 3. If `{{binName}} --version` then shows an older version than this skill describes, npm answered from a stale cache. Hand the user the same line with `--cache "$(mktemp -d)"` appended.
10
+ 4. Finish with [Check](#check). {{binName}} is ready when `doctor` exits 0 with nothing in `next`: a row that only advises, such as `install`, leaves the exit code at 0.
@@ -0,0 +1,3 @@
1
+ Claude Code's sandbox never lets a command write npm's global folder, `~/.claude/skills` or `~/.agents/skills`, so the install line, `{{binName}} update` and `npx skills` fail inside it. Hand the user the line to type with `!` in this session, which runs it outside the sandbox under the Node this shell runs.
2
+
3
+ `doctor`'s `registry` row, `update` and `npm view` ask the registry {{binName}} is published to for its latest release. If the sandbox refuses that host, `doctor` still passes with only that row failing, and `update --dry-run` fails. If the user wants these to work inside the sandbox, give them this block for their Claude Code settings, naming the registry's host, and let them decide:
@@ -0,0 +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 with its `npx -y skills add` fix, since `npx skills update` skips a copy it did not install. After a fix that runs the `skills` installer, load this skill again.
@@ -0,0 +1,4 @@
1
+ 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.
2
+ 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.
3
+ 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.
4
+ 4. Confirm the new version with `{{binName}} --version`.
@@ -4,11 +4,7 @@
4
4
 
5
5
  ## Install
6
6
 
7
- Install the skill first, so that the coding agent loading it can install and check the CLI. In Claude Code, type each line in the prompt:
8
-
9
- {{skillInstall}}
10
-
11
- `! npm install --global {{packageName}}@latest`
7
+ {{installLines}}
12
8
 
13
9
  Built on [`@duyquangnvx/agent-cli`](https://www.npmjs.com/package/@duyquangnvx/agent-cli): declare Commands in `src/cli.ts` and test them through `runCli`, as `test/cli.test.ts` does.
14
10
 
@@ -2,7 +2,7 @@
2
2
  name: {{binName}}
3
3
  description: >-
4
4
  {{description}} Use when a task needs what `{{binName}}` does, or when a `{{binName}}` command fails.
5
- allowed-tools: Bash({{binName}} --version)
5
+ allowed-tools: Bash({{runAs}} --version)
6
6
  metadata:
7
7
  version: "0.0.0"
8
8
  ---
@@ -11,11 +11,11 @@ metadata:
11
11
 
12
12
  # {{binName}}
13
13
 
14
- `{{binName}} --help` lists the Commands, and `{{binName}} <command> --help` shows a Command's examples, args, options and the flags the Runtime adds. This file covers the work around them.
14
+ `{{runAs}} --help` lists the Commands, and `{{runAs}} <command> --help` shows a Command's examples, args, options and the flags the Runtime adds. This file covers the work around them.
15
15
 
16
16
  ## Installed CLI
17
17
 
18
- !`{{binName}} --version 2>&1 || echo "{{binName}}: not installed"`
18
+ !`{{runAs}} --version 2>&1 || echo "{{binName}}: not installed"`
19
19
 
20
20
  The line above should show 0.0.0, the version this skill describes. If it says {{binName}} is not installed or shows another version, read [setup](references/setup.md) before anything else.
21
21
 
@@ -32,12 +32,12 @@ The line above should show 0.0.0, the version this skill describes. If it says {
32
32
 
33
33
  ### Greet someone
34
34
 
35
- 1. Run `{{binName}} greet <name> --json`.
35
+ 1. Run `{{runAs}} greet <name> --json`.
36
36
  2. Read `greeting` from the JSON on stdout.
37
37
 
38
38
  ### Write a card
39
39
 
40
- 1. Run `{{binName}} card <name> --language <code> --dry-run` to see the file it writes and what it costs.
40
+ 1. Run `{{runAs}} card <name> --language <code> --dry-run` to see the file it writes and what it costs.
41
41
  2. If it states a cost, ask the user before going on.
42
42
  3. Run it again without `--dry-run`, adding `--yes` once the user approved the cost.
43
43
 
@@ -47,7 +47,7 @@ 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 naming the next step. Under `--json`, every stderr line is one JSON object: `{"progress":"..."}` while it runs, then `{ errors: [{ code, message, hint }] }` as the last line. Match the first row that fits:
50
+ Errors go to stderr and end with a `hint:` line naming the next step. Under `--json`, every stderr line is one JSON object: `{"progress":"..."}` or `{"notice":"..."}` while it runs, then `{ errors: [{ code, message, hint }] }` as the last line. Match the first row that fits:
51
51
 
52
52
  | Code or exit | What to do |
53
53
  |---|---|
@@ -8,39 +8,25 @@ Work through a section in order and stop at a step that fails. Ask the user to d
8
8
 
9
9
  ## Check
10
10
 
11
- Run `{{binName}} doctor --json`. It checks the setup without changing anything and prints `{ rows, next }`.
11
+ Run `{{runAs}} doctor --json`. It checks the setup without changing anything and prints `{ rows, next }`.
12
12
 
13
13
  - Each row has a `check`, a `state` (`ok`, `problem` or `skipped`) and a `message` saying what it found. A row that has a `fix` names the command that fixes it.
14
14
  - `next` lists every fix in order. Hand the user each one to type with `!`, then run `doctor` again.
15
- - 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 its `npx -y skills add` fix, since `npx skills update` skips a copy it did not install. After a fix that runs the `skills` installer, load this skill again. If the session does not show it, ask the user to type `/reload-skills`, and failing that to restart Claude Code.
15
+ - {{skillCheck}} If the session does not show it, ask the user to type `/reload-skills`, and failing that to restart Claude Code.
16
16
 
17
- `{{binName}} --version` names on its second line the `CHANGELOG.md` installed with the CLI. When the user asks what changed, read it from the top, newest first, down to the version in question.
17
+ `{{runAs}} --version` names on its second line the `CHANGELOG.md` installed with the CLI. When the user asks what changed, read it from the top, newest first, down to the version in question.
18
18
 
19
19
  ## Install
20
20
 
21
- This installs the CLI. The user has installed this skill already.
22
-
23
- 1. Run `node --version` and compare it with the range `npm view {{packageName}} engines.node` prints, which `doctor`'s `node` row also checks once the CLI is installed. Under nvm every Node version has its own global packages, so {{binName}} lands under the Node this shell runs. If that Node is too old, or is not the one the user's terminal runs, tell the user and let them choose before going on.
24
- 2. Hand the user the install line to type, as [Sandbox](#sandbox) says:
25
-
26
- `! npm install --global {{packageName}}@latest`
27
-
28
- Never run `npx {{binName}}`: that runs whatever npm package holds the name.
29
- 3. If `{{binName}} --version` then shows an older version than this skill describes, npm answered from a stale cache. Hand the user the same line with `--cache "$(mktemp -d)"` appended.
30
- 4. Finish with [Check](#check). {{binName}} is ready when `doctor` exits 0 with nothing in `next`: a row that only advises, such as `install`, leaves the exit code at 0.
21
+ {{installSteps}}
31
22
 
32
23
  ## Update
33
24
 
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, 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
- 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
- 4. Confirm the new version with `{{binName}} --version`.
25
+ {{updateSteps}}
38
26
 
39
27
  ## Sandbox
40
28
 
41
- Claude Code's sandbox never lets a command write npm's global folder, `~/.claude/skills` or `~/.agents/skills`, so the install line, `{{binName}} update` and `npx skills` fail inside it. Hand the user the line to type with `!` in this session, which runs it outside the sandbox under the Node this shell runs.
42
-
43
- `doctor`'s `registry` row, `update` and `npm view` ask the registry {{binName}} is published to for its latest release. If the sandbox refuses that host, `doctor` still passes with only that row failing, and `update --dry-run` fails. If the user wants it to work inside the sandbox, give them this block for their Claude Code settings, naming the registry's host, and let them decide:
29
+ {{sandbox}}
44
30
 
45
31
  ```json
46
32
  { "sandbox": { "network": { "allowedDomains": ["registry.npmjs.org"] } } }
@@ -14,8 +14,14 @@ const greet = defineCommand({
14
14
  name: "greet",
15
15
  description: "Greet someone by name",
16
16
  examples: [
17
- { command: "{{binName}} greet Ana", note: "say hello to Ana" },
18
- { command: "{{binName}} greet Ana --json", note: "get the greeting as JSON" },
17
+ {
18
+ command: "{{runAs}} greet Ana",
19
+ note: "say hello to Ana",
20
+ },
21
+ {
22
+ command: "{{runAs}} greet Ana --json",
23
+ note: "get the greeting as JSON",
24
+ },
19
25
  ],
20
26
  effect: "read",
21
27
  args: z.object({
@@ -30,14 +36,20 @@ const card = defineCommand({
30
36
  description: "Write a greeting card to <name>.txt",
31
37
  help:
32
38
  `A card in English is free; one in another language costs ${PRICE_PER_CARD} and needs --yes. ` +
33
- "Give - as the message to read it from stdin: `{{binName}} card Ana - < note.txt`.",
39
+ "Give - as the message to read it from stdin: `{{runAs}} card Ana - < note.txt`.",
34
40
  examples: [
35
- { command: "{{binName}} card Ana", note: "write Ana.txt with an English greeting" },
36
41
  {
37
- command: "{{binName}} card Ana --language es --dry-run",
42
+ command: "{{runAs}} card Ana",
43
+ note: "write Ana.txt with an English greeting",
44
+ },
45
+ {
46
+ command: "{{runAs}} card Ana --language es --dry-run",
38
47
  note: "show the file it would write and what it costs",
39
48
  },
40
- { command: "{{binName}} card Ana --from Ben --from Cy", note: "sign the card from Ben and Cy" },
49
+ {
50
+ command: "{{runAs}} card Ana --from Ben --from Cy",
51
+ note: "sign the card from Ben and Cy",
52
+ },
41
53
  ],
42
54
  effect: "write",
43
55
  args: z.object({
@@ -73,7 +85,7 @@ const card = defineCommand({
73
85
  if (greeting === undefined) {
74
86
  throw cliError(errors, "LANGUAGE_UNSUPPORTED", {
75
87
  message: `the greetings service has no ${plan.language} greeting`,
76
- hint: `run \`{{binName}} card ${plan.name} --language ${FREE_LANGUAGE}\` for an English card`,
88
+ hint: `run \`{{runAs}} card ${plan.name} --language ${FREE_LANGUAGE}\` for an English card`,
77
89
  });
78
90
  }
79
91
  const parts = [
@@ -1,4 +1,4 @@
1
- import { mkdtemp, readFile } from "node:fs/promises";
1
+ import { mkdtemp, readFile, writeFile } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
@@ -9,6 +9,9 @@ import { cli } from "../src/cli.ts";
9
9
  import type { Greetings } from "../src/ports.ts";
10
10
 
11
11
  const CWD = "/work";
12
+ const RUN_AS = "{{runAs}}";
13
+ // doctor finds a Dependency install's copy from the script it runs as, so it gets one; a Global install gets none, as running from source counts as not installed.
14
+ const DOCTOR_BIN: string = "{{doctorBin}}";
12
15
 
13
16
  /** A file system holding `existing` (paths under CWD) that records each write. */
14
17
  function fakeFiles(existing: readonly string[] = []) {
@@ -50,6 +53,10 @@ function fakeGreetings() {
50
53
  return { greetings, calls };
51
54
  }
52
55
 
56
+ function hintOf(stderr: string): string | undefined {
57
+ return stderr.split("\n").find((line) => line.startsWith("hint: "));
58
+ }
59
+
53
60
  function run(
54
61
  argv: string[],
55
62
  files = fakeFiles().files,
@@ -126,7 +133,7 @@ describe("card", () => {
126
133
  const refused = await run(["card", "Ana"], files);
127
134
 
128
135
  expect(refused.exitCode).toBe(1);
129
- expect(refused.stderr).toMatch(/hint: .*`{{binName}} card Ana --force`/);
136
+ expect(hintOf(refused.stderr)).toContain(`\`${RUN_AS} card Ana --force\``);
130
137
  expect(written.size).toBe(0);
131
138
 
132
139
  const forced = await run(["card", "Ana", "--force"], files);
@@ -141,7 +148,7 @@ describe("card", () => {
141
148
  const refused = await run(["card", "Ana", "--language", "es"], undefined, greetings);
142
149
 
143
150
  expect(refused.exitCode).toBe(2);
144
- expect(refused.stderr).toMatch(/hint: .*`{{binName}} card Ana --language es --yes`/);
151
+ expect(hintOf(refused.stderr)).toContain(`\`${RUN_AS} card Ana --language es --yes\``);
145
152
  expect(calls).toEqual([]);
146
153
 
147
154
  const approved = await run(["card", "Ana", "--language", "es", "--yes"], undefined, greetings);
@@ -163,12 +170,16 @@ describe("card", () => {
163
170
  describe("install lines", () => {
164
171
  const root = fileURLToPath(new URL("..", import.meta.url));
165
172
 
166
- /** Each doctor row's fix, run from this checkout with no npm on PATH and no skill installed. */
173
+ /** Each doctor row's fix, run from a pnpm project lacking the CLI, with no npm on PATH and no skill installed. */
167
174
  async function doctorFixes(): Promise<Record<string, string | undefined>> {
168
175
  const home = await mkdtemp(join(tmpdir(), "{{binName}}-home-"));
176
+ const project = await mkdtemp(join(tmpdir(), "{{binName}}-project-"));
177
+ await writeFile(join(project, "package.json"), "{}\n");
178
+ await writeFile(join(project, "pnpm-lock.yaml"), "");
169
179
  const result = await runCli(cli, ["doctor", "--json"], {
170
180
  env: { HOME: home, PATH: "" },
171
- cwd: root,
181
+ cwd: project,
182
+ bin: DOCTOR_BIN === "" ? undefined : join(root, DOCTOR_BIN),
172
183
  });
173
184
  const { rows } = JSON.parse(result.stdout) as { rows: { check: string; fix?: string }[] };
174
185
  return Object.fromEntries(rows.map(({ check, fix }) => [check, fix]));