@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/CHANGELOG.md +17 -0
- package/README.md +1 -1
- package/dist/bin.js +1188 -721
- package/package.json +3 -2
- package/partials/dependency/installLines.md +7 -0
- package/partials/dependency/installSteps.md +12 -0
- package/partials/dependency/sandbox.md +3 -0
- package/partials/dependency/skillCheck.md +1 -0
- package/partials/dependency/updateSteps.md +4 -0
- package/partials/global/installLines.md +5 -0
- package/partials/global/installSteps.md +10 -0
- package/partials/global/sandbox.md +3 -0
- package/partials/global/skillCheck.md +1 -0
- package/partials/global/updateSteps.md +4 -0
- package/template/README.md +1 -5
- package/template/skills/{{binName}}/SKILL.md +6 -6
- package/template/skills/{{binName}}/references/setup.md +6 -20
- package/template/src/cli.ts +19 -7
- package/template/test/cli.test.ts +16 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duyquangnvx/create-agent-cli",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
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,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`.
|
package/template/README.md
CHANGED
|
@@ -4,11 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
|
|
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({{
|
|
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
|
-
`{{
|
|
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
|
-
!`{{
|
|
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 `{{
|
|
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 `{{
|
|
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 `{{
|
|
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
|
-
-
|
|
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
|
-
`{{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"] } } }
|
package/template/src/cli.ts
CHANGED
|
@@ -14,8 +14,14 @@ const greet = defineCommand({
|
|
|
14
14
|
name: "greet",
|
|
15
15
|
description: "Greet someone by name",
|
|
16
16
|
examples: [
|
|
17
|
-
{
|
|
18
|
-
|
|
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: `{{
|
|
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: "{{
|
|
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
|
-
{
|
|
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 \`{{
|
|
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).
|
|
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).
|
|
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
|
|
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:
|
|
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]));
|