@duyquangnvx/create-agent-cli 0.0.0-stage → 0.2.1
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 +41 -0
- package/README.md +10 -2
- package/dist/bin.js +7645 -0
- package/package.json +26 -3
- package/template/AGENTS.md +30 -0
- package/template/CHANGELOG.md +5 -0
- package/template/CLAUDE.md +1 -0
- package/template/README.md +14 -0
- package/template/_gitattributes +1 -0
- package/template/_github/workflows/ci.yml +25 -0
- package/template/_gitignore +3 -0
- package/template/biome.json +4 -0
- package/template/package.json +39 -0
- package/template/pnpm-workspace.yaml +4 -0
- package/template/scripts/release.ts +6 -0
- package/template/skills/{{binName}}/SKILL.md +50 -0
- package/template/src/bin.ts +5 -0
- package/template/src/build.ts +6 -0
- package/template/src/cli.ts +32 -0
- package/template/test/cli.test.ts +27 -0
- package/template/tsconfig.json +18 -0
- package/template/tsdown.config.ts +30 -0
- package/template/vitest.config.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duyquangnvx/create-agent-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.12.0"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"create-agent-cli": "./dist/bin.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"template",
|
|
15
|
+
"CHANGELOG.md"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"registry": "https://registry.npmjs.org/",
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@duyquangnvx/agent-cli": "0.2.1",
|
|
23
|
+
"tsdown": "0.23.0",
|
|
24
|
+
"zod": "4.6.7"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsdown"
|
|
28
|
+
}
|
|
6
29
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# {{packageName}}
|
|
2
|
+
|
|
3
|
+
{{description}} An Agent CLI: its main callers are LLM coding agents running it through a shell, so it never prompts, keeps output short, and tells the caller what to run next when something fails. It is built on [`@duyquangnvx/agent-cli`](https://www.npmjs.com/package/@duyquangnvx/agent-cli), the Runtime, which owns parsing, help, output, errors and exit codes.
|
|
4
|
+
|
|
5
|
+
## Project status
|
|
6
|
+
|
|
7
|
+
Greenfield with no deployments or users: change code directly. Breaking changes are welcome; no feature flags, shims, or backward compatibility.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
| | |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `pnpm test` | vitest |
|
|
14
|
+
| `pnpm typecheck` | tsc |
|
|
15
|
+
| `pnpm lint` | Biome; `pnpm exec biome check --write` fixes formatting |
|
|
16
|
+
| `pnpm build` | bundles `dist/`, the `{{binName}}` bin, with the Runtime and the commit it is built from |
|
|
17
|
+
| `pnpm release` | publishes to npm through `@duyquangnvx/release`; run `pnpm release --dry-run` to check it |
|
|
18
|
+
|
|
19
|
+
## Coding standards
|
|
20
|
+
|
|
21
|
+
- Every Command is declared in `src/cli.ts` with `defineCommand`: a description, realistic examples, Zod schemas for args and options (with `.describe()` on each field), and its Effect.
|
|
22
|
+
- A Command never prints. A `read` Command returns data from `run` and turns it into short text in `render`. A `write` or `destructive` Command returns a Plan from `plan`, with no side effects, and performs it in `apply`; the Runtime adds `--dry-run`, and `--confirm` for a destructive one.
|
|
23
|
+
- A failure the caller can act on is a `CliError` with a `code`, a `message`, a `hint` naming the command to run next, and `retryable`. Anything else becomes an internal error, whose stack trace `{{debugVariable}}=1` shows.
|
|
24
|
+
- A Command that lists many items declares its row schema as `rows` and returns `paginate(items, { limit, cursor })`; the Runtime adds `--limit`, `--cursor` and `--fields`.
|
|
25
|
+
- Tests drive the CLI through `runCli` and assert only stdout, stderr and the exit code, as `test/cli.test.ts` does.
|
|
26
|
+
- A change a user of `{{binName}}` would notice gets an entry under `## Unreleased` in `CHANGELOG.md`, in the format its opening paragraph states.
|
|
27
|
+
|
|
28
|
+
## Agent skills
|
|
29
|
+
|
|
30
|
+
`skills/{{binName}}/SKILL.md` is the agent skill: when to use `{{binName}}`, its workflows and how to recover from its errors. Update it with any change to those, and leave flag details to `--help`.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Changelog
|
|
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)`.
|
|
4
|
+
|
|
5
|
+
## Unreleased
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# {{packageName}}
|
|
2
|
+
|
|
3
|
+
{{description}}
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm test # vitest
|
|
9
|
+
pnpm typecheck # tsc
|
|
10
|
+
pnpm lint # biome
|
|
11
|
+
pnpm build # bundles dist/, the {{binName}} bin, stamped with the commit
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Set `{{debugVariable}}=1` to see the stack trace of an internal error.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* text=auto eol=lf
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
check:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, windows-latest]
|
|
14
|
+
runs-on: ${{ matrix.os }}
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v7
|
|
17
|
+
- uses: pnpm/action-setup@v6
|
|
18
|
+
- uses: actions/setup-node@v7
|
|
19
|
+
with:
|
|
20
|
+
node-version: 24
|
|
21
|
+
cache: pnpm
|
|
22
|
+
- run: pnpm install --frozen-lockfile
|
|
23
|
+
- run: pnpm typecheck
|
|
24
|
+
- run: pnpm lint
|
|
25
|
+
- run: pnpm test
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{packageName}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "{{description}}",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@12.9.1",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.18.0"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"{{binName}}": "./dist/bin.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"CHANGELOG.md"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"registry": "https://registry.npmjs.org/",
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsdown",
|
|
23
|
+
"typecheck": "tsc",
|
|
24
|
+
"lint": "biome ci",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"release": "node scripts/release.ts"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@biomejs/biome": "2.5.15",
|
|
30
|
+
"@duyquangnvx/agent-cli": "{{runtimeVersion}}",
|
|
31
|
+
"@duyquangnvx/harness": "0.3.1",
|
|
32
|
+
"@duyquangnvx/release": "0.2.0",
|
|
33
|
+
"@types/node": "^22.19.0",
|
|
34
|
+
"tsdown": "0.23.0",
|
|
35
|
+
"typescript": "7.0.2",
|
|
36
|
+
"vitest": "5.0.5",
|
|
37
|
+
"zod": "4.6.7"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: {{binName}}
|
|
3
|
+
description: >-
|
|
4
|
+
{{description}} Use when a task needs what `{{binName}}` does, or when a `{{binName}}` command fails.
|
|
5
|
+
allowed-tools: Bash({{binName}} --version)
|
|
6
|
+
metadata:
|
|
7
|
+
version: "0.0.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<!-- A skeleton to rewrite around {{binName}}'s real Commands. Leave flags out: `--help` is generated from the code and stays in sync with it, this file does not. -->
|
|
11
|
+
|
|
12
|
+
# {{binName}}
|
|
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.
|
|
15
|
+
|
|
16
|
+
## Installed CLI
|
|
17
|
+
|
|
18
|
+
!`{{binName}} --version 2>&1 || echo "{{binName}}: not installed"`
|
|
19
|
+
|
|
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, tell the user before going on.
|
|
21
|
+
|
|
22
|
+
## When to use
|
|
23
|
+
|
|
24
|
+
<!-- The tasks {{binName}} is for, and the nearby tasks it is not for. -->
|
|
25
|
+
|
|
26
|
+
- Greeting someone by name.
|
|
27
|
+
|
|
28
|
+
## Workflows
|
|
29
|
+
|
|
30
|
+
<!-- Each main task as steps naming the Commands it runs, in order. Move a long workflow to a file under references/ and link it. -->
|
|
31
|
+
|
|
32
|
+
### Greet someone
|
|
33
|
+
|
|
34
|
+
1. Run `{{binName}} greet <name> --json`.
|
|
35
|
+
2. Read `greeting` from the JSON on stdout.
|
|
36
|
+
|
|
37
|
+
Pass `--json` whenever you parse the output: stdout then holds only the data. A Command that changes something takes `--dry-run`, which prints its Plan and changes nothing: run it first when unsure. A destructive Command also needs `--confirm=<target>`, and its error's hint gives the exact command.
|
|
38
|
+
|
|
39
|
+
## Recovering from errors
|
|
40
|
+
|
|
41
|
+
Errors go to stderr and end with a `hint:` line naming the next step. Under `--json` they are one object with `code`, `message`, `hint` and `retryable`. Match the first row that fits:
|
|
42
|
+
|
|
43
|
+
| Code or exit | What to do |
|
|
44
|
+
|---|---|
|
|
45
|
+
| exit 2 | You called it wrong. Fix the input the message names, as the hint says, and run it again. |
|
|
46
|
+
| `INTERNAL` | A bug in {{binName}}. Run it once more with `{{debugVariable}}=1` for the stack trace, then give the user stderr verbatim. |
|
|
47
|
+
| exit 1, `retryable: true` | Run it again once. |
|
|
48
|
+
| exit 1 | Fix what the message and hint point at, then run it again. Ask the user when the fix is their choice. |
|
|
49
|
+
|
|
50
|
+
<!-- Add a row for each CliError code {{binName}} throws, saying what to do about it. -->
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Cli } from "@duyquangnvx/agent-cli";
|
|
2
|
+
|
|
3
|
+
// tsdown.config.ts stamps this into the bundle; the source run unbundled, as in tests, has none.
|
|
4
|
+
declare const __BUILD__: Cli["build"];
|
|
5
|
+
|
|
6
|
+
export const build: Cli["build"] = typeof __BUILD__ === "undefined" ? undefined : __BUILD__;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineCli, defineCommand } from "@duyquangnvx/agent-cli";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import manifest from "../package.json" with { type: "json" };
|
|
4
|
+
import { build } from "./build.js";
|
|
5
|
+
|
|
6
|
+
const greet = defineCommand({
|
|
7
|
+
name: "greet",
|
|
8
|
+
description: "Greet someone by name",
|
|
9
|
+
examples: [
|
|
10
|
+
{ command: "{{binName}} greet Ana", note: "say hello to Ana" },
|
|
11
|
+
{ command: "{{binName}} greet Ana --json", note: "get the greeting as JSON" },
|
|
12
|
+
],
|
|
13
|
+
effect: "read",
|
|
14
|
+
args: z.object({
|
|
15
|
+
name: z.string().min(1).describe("who to greet"),
|
|
16
|
+
}),
|
|
17
|
+
run: ({ name }) => ({ greeting: `Hello, ${name}!` }),
|
|
18
|
+
render: ({ greeting }) => greeting,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const cli = defineCli({
|
|
22
|
+
name: "{{binName}}",
|
|
23
|
+
version: manifest.version,
|
|
24
|
+
build,
|
|
25
|
+
install: {
|
|
26
|
+
mode: "global",
|
|
27
|
+
package: manifest.name,
|
|
28
|
+
registry: manifest.publishConfig.registry,
|
|
29
|
+
},
|
|
30
|
+
engines: manifest.engines,
|
|
31
|
+
commands: { greet },
|
|
32
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { runCli } from "@duyquangnvx/agent-cli";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { cli } from "../src/cli.ts";
|
|
4
|
+
|
|
5
|
+
const options = { env: {}, cwd: "." };
|
|
6
|
+
|
|
7
|
+
describe("greet", () => {
|
|
8
|
+
test("prints the greeting", async () => {
|
|
9
|
+
const result = await runCli(cli, ["greet", "Ana"], options);
|
|
10
|
+
|
|
11
|
+
expect(result).toEqual({ stdout: "Hello, Ana!\n", stderr: "", exitCode: 0 });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("prints the data as JSON with --json", async () => {
|
|
15
|
+
const result = await runCli(cli, ["greet", "Ana", "--json"], options);
|
|
16
|
+
|
|
17
|
+
expect(JSON.parse(result.stdout)).toEqual({ greeting: "Hello, Ana!" });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("a missing name is a usage error", async () => {
|
|
21
|
+
const result = await runCli(cli, ["greet"], options);
|
|
22
|
+
|
|
23
|
+
expect(result.exitCode).toBe(2);
|
|
24
|
+
expect(result.stdout).toBe("");
|
|
25
|
+
expect(result.stderr).toMatch(/<name>: required/);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2023",
|
|
4
|
+
"lib": ["es2023"],
|
|
5
|
+
"module": "nodenext",
|
|
6
|
+
"types": ["node"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noUncheckedIndexedAccess": true,
|
|
9
|
+
"noImplicitOverride": true,
|
|
10
|
+
"noFallthroughCasesInSwitch": true,
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"allowImportingTsExtensions": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src", "test", "scripts", "tsdown.config.ts", "vitest.config.ts"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { defineConfig } from "tsdown";
|
|
3
|
+
|
|
4
|
+
function git(args: readonly string[]): string {
|
|
5
|
+
try {
|
|
6
|
+
return execFileSync("git", args, {
|
|
7
|
+
encoding: "utf8",
|
|
8
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
9
|
+
}).trim();
|
|
10
|
+
} catch (error) {
|
|
11
|
+
throw new Error("the build stamps the commit it is made from: commit to git first", {
|
|
12
|
+
cause: error,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const build = {
|
|
18
|
+
sha: git(["rev-parse", "--short=7", "HEAD"]),
|
|
19
|
+
dirty: git(["status", "--porcelain"]) !== "",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default defineConfig({
|
|
23
|
+
entry: ["src/bin.ts"],
|
|
24
|
+
platform: "node",
|
|
25
|
+
format: "esm",
|
|
26
|
+
fixedExtension: false,
|
|
27
|
+
// The bin bundles the Runtime and zod, so only this CLI's own release changes its behaviour.
|
|
28
|
+
deps: { onlyBundle: false },
|
|
29
|
+
define: { __BUILD__: JSON.stringify(build) },
|
|
30
|
+
});
|