argsbarg 7.0.4 → 7.0.6
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 -1
- package/README.md +1 -1
- package/docs/ai-skills.md +9 -6
- package/docs/bundled-docs.md +8 -8
- package/docs/cli-program.md +2 -2
- package/docs/output-schema.md +1 -1
- package/examples/full-example/AGENTS.md +3 -1
- package/examples/full-example/docs/README.md +1 -1
- package/examples/full-example/docs/http.md +1 -1
- package/examples/full-example/docs/mcp.md +1 -1
- package/examples/full-example/docs/openapi.json +1 -6
- package/examples/full-example/{docs/skill.md → skills/full-example/SKILL.md} +10 -6
- package/examples/full-example-json/AGENTS.md +3 -1
- package/examples/full-example-json/docs/README.md +1 -1
- package/examples/full-example-json/docs/http.md +1 -1
- package/examples/full-example-json/docs/mcp.md +5 -5
- package/examples/full-example-json/docs/openapi.json +1 -6
- package/examples/full-example-json/{docs/skill.md → skills/full-example-json/SKILL.md} +15 -11
- package/package.json +1 -1
- package/src/cli-tool/create.test.ts +1 -0
- package/src/cli-tool/create.ts +5 -1
- package/src/core/parse.test.ts +209 -12
- package/src/core/parse.ts +54 -54
- package/src/docs/docs.test.ts +5 -3
- package/src/docs/save.ts +63 -15
- package/src/skill/generate.ts +21 -17
- package/src/skill/hint.ts +7 -20
- package/src/skill/install.ts +16 -5
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [7.0.6] - 2026-09-15
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Repository skill convention (`skills/<app>/SKILL.md`)** — `docs skill --save` now writes to `./skills/<app>/SKILL.md` instead of `./docs/skill.md`, aligning with the open skill repository standard. Automatically removes legacy `./docs/skill.md` when saving. Updated `argsbarg create` to scaffold `skills/<app>/SKILL.md`.
|
|
15
|
+
- **Intent-based agent skill router** — `skill.md` / `SKILL.md` is now an intent-based router directing agents to specific subcommands and guiding them to use `<subcommand> --help` for JIT option and positional discovery. Dropped `reference.md` generation and install, preventing agent context window bloat and outdated flag hallucinations. `cliSkillInstall` automatically cleans up legacy `reference.md` when refreshing.
|
|
16
|
+
- **Consumer agent instructions** — updated `AGENTS.md` managed template to instruct coding agents to use `<cli> <subcommand> --help` instead of reading large API markdown documentation files.
|
|
17
|
+
|
|
18
|
+
## [7.0.5] - 2026-09-15
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **Interleaved options between positionals** — options (presence flags and value options) can now be placed anywhere between bounded or optional positional arguments (e.g. `cmd file1 --force file2`), matching varargs tail behavior rather than requiring all options to precede or follow all bounded positionals.
|
|
23
|
+
|
|
10
24
|
## [7.0.4] - 2026-08-17
|
|
11
25
|
|
|
12
26
|
### Changed
|
|
@@ -959,7 +973,9 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
|
959
973
|
- Migrate schemas: rename every `children` property to **`commands`**; move positional definitions to **`CliPositional`** objects on `positionals` and strip `positional` / `argMin` / `argMax` from flag definitions under `options` (flags only carry `name`, `description`, `kind`, and optional `shortName`).
|
|
960
974
|
- Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
|
|
961
975
|
|
|
962
|
-
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v7.0.
|
|
976
|
+
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v7.0.6...HEAD
|
|
977
|
+
[7.0.6]: https://github.com/bdombro/bun-argsbarg/releases/tag/v7.0.6
|
|
978
|
+
[7.0.5]: https://github.com/bdombro/bun-argsbarg/releases/tag/v7.0.5
|
|
963
979
|
[7.0.4]: https://github.com/bdombro/bun-argsbarg/releases/tag/v7.0.4
|
|
964
980
|
[7.0.3]: https://github.com/bdombro/bun-argsbarg/releases/tag/v7.0.3
|
|
965
981
|
[7.0.2]: https://github.com/bdombro/bun-argsbarg/releases/tag/v7.0.2
|
package/README.md
CHANGED
|
@@ -385,7 +385,7 @@ This refreshes the argsbarg-managed section in `AGENTS.md` while preserving your
|
|
|
385
385
|
|
|
386
386
|
### 3. Generated Skills & Workspace Configuration
|
|
387
387
|
|
|
388
|
-
Running `myapp configure install` installs
|
|
388
|
+
Running `myapp configure install` installs an intent-based `SKILL.md` router to `~/.agents/skills/<key>/` when `program.skill: { enabled: true }`, directing agents to run `<subcommand> --help` for just-in-time option and argument discovery.
|
|
389
389
|
|
|
390
390
|
See **[docs/configure.md](docs/configure.md)** and **[docs/ai-skills.md](docs/ai-skills.md)** for developer setup and automated Homebrew pipeline integration.
|
|
391
391
|
|
package/docs/ai-skills.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> This feature is experimental.
|
|
4
4
|
|
|
5
|
-
ArgsBarg can generate agent skill directories (`skill.md` + `
|
|
5
|
+
ArgsBarg can generate agent skill directories (`skill.md` + `SKILL.md`) from your CLI schema and install them to `~/.agents/skills/<key>/` per the open standard at https://dotagentsprotocol.com/.
|
|
6
6
|
|
|
7
7
|
## Enable on the program root
|
|
8
8
|
|
|
@@ -36,11 +36,10 @@ import { cliSkillInstall } from "argsbarg/skill/install"; // internal module
|
|
|
36
36
|
|
|
37
37
|
## Generated content
|
|
38
38
|
|
|
39
|
-
- **`skill.md`** — https://dotagentsprotocol.com frontmatter (`id`, `name`, `description`, `enabled`), compact command
|
|
39
|
+
- **`skill.md`** — https://dotagentsprotocol.com frontmatter (`id`, `name`, `description`, `enabled`), execution guidance, options & help discovery protocol (`--help`), compact command router, workflow & pitfalls, and client setup
|
|
40
40
|
- **`SKILL.md`** — compatibility copy of `skill.md` for tools that expect uppercase filenames
|
|
41
|
-
- **`reference.md`** — full `docs cli` markdown reference
|
|
42
41
|
|
|
43
|
-
Installed files include an HTML comment hint (`Generated by myapp configure; do not edit.`) after skill frontmatter
|
|
42
|
+
Installed files include an HTML comment hint (`Generated by myapp configure; do not edit.`) after skill frontmatter.
|
|
44
43
|
|
|
45
44
|
## Client setup
|
|
46
45
|
|
|
@@ -61,10 +60,14 @@ Skills describe **shell invocation only** — no MCP setup or `tools/call` guida
|
|
|
61
60
|
| Mechanism | Role |
|
|
62
61
|
| --- | --- |
|
|
63
62
|
| **`myapp mcp`** (requires `mcpServer`) | Runtime tool execution over MCP |
|
|
64
|
-
| **`program.skill.enabled`** + **`configure install`** | Persists optimized skill
|
|
63
|
+
| **`program.skill.enabled`** + **`configure install`** | Persists optimized intent-based skill router at `~/.agents/skills/<key>/` |
|
|
65
64
|
| **`myapp docs`** (requires `docs`) | Bundled markdown on stdout (`docs mcp` when MCP enabled) |
|
|
66
65
|
|
|
67
|
-
`skill.md` is
|
|
66
|
+
`skill.md` is an intent-based router that directs agents to specific subcommands and guides them to use `<subcommand> --help` for JIT option discovery. Prefer `configure install` over `docs skill` for installed agents. See [cli-program.md](cli-program.md).
|
|
67
|
+
|
|
68
|
+
## Repository skill (`skills/<app>/SKILL.md`)
|
|
69
|
+
|
|
70
|
+
Running `myapp docs skill --save` writes `skills/<app>/SKILL.md` directly into the consumer repository. Committing this file adheres to the repository skill convention (`skills/<name>/SKILL.md`), allowing agents, tools, and skill managers to discover and install your CLI's skill directly from the source repository.
|
|
68
71
|
|
|
69
72
|
**Claude Code plugin** (`mcpServer.claudePlugin: true` → `mcp bundle` writes `dist/claude-plugin/<name>.zip`) ships a separate MCP pointer skill (`SKILL.md` only) that routes agents to the bundled MCP server. This is a **dist artifact**, not installed via `configure`.
|
|
70
73
|
|
package/docs/bundled-docs.md
CHANGED
|
@@ -63,6 +63,7 @@ myapp docs openapi # OpenAPI 3.1 JSON when httpServer.enabled
|
|
|
63
63
|
myapp docs readme --save # write ./docs/readme.md
|
|
64
64
|
myapp docs cli-schema --save # write ./docs/cli-schema.json
|
|
65
65
|
myapp docs openapi --save # write ./docs/openapi.json
|
|
66
|
+
myapp docs skill --save # write ./skills/myapp/SKILL.md
|
|
66
67
|
```
|
|
67
68
|
|
|
68
69
|
Top-level `myapp --help` points agents at `myapp docs skill`. The `docs skill` subcommand description recommends `configure` for a persisted bundle.
|
|
@@ -97,7 +98,7 @@ By default (unless `docs.enabled: false`):
|
|
|
97
98
|
|
|
98
99
|
- **`docs cli-schema`** — same JSON as the former root `--schema` flag (handlers omitted; built-in subtrees included for leaf roots).
|
|
99
100
|
- **`docs cli`** — markdown rendering of the same command tree (options, positionals, subcommands, fallback routing).
|
|
100
|
-
- **`docs skill`** — prints the compact `SKILL.md`
|
|
101
|
+
- **`docs skill`** — prints the compact `SKILL.md` router. Prefer `configure install` for agents (persists `skill.md` + `SKILL.md` to `~/.agents/skills/<key>/`).
|
|
101
102
|
|
|
102
103
|
## MCP guide (`docs mcp`)
|
|
103
104
|
|
|
@@ -123,7 +124,7 @@ All `docs` subcommands are hidden from MCP `tools/list` (`mcpTool: { enabled: fa
|
|
|
123
124
|
|
|
124
125
|
| Channel | Role |
|
|
125
126
|
| --- | --- |
|
|
126
|
-
| `configure` (skill targets) | Writes
|
|
127
|
+
| `configure` (skill targets) | Writes intent-based `skill.md` + `SKILL.md` router to disk |
|
|
127
128
|
| `docs skill` | Print generated `SKILL.md` to stdout |
|
|
128
129
|
| `docs cli` | Print command tree markdown to stdout |
|
|
129
130
|
| `docs cli-schema` | Print command tree JSON to stdout |
|
|
@@ -135,21 +136,20 @@ Do not declare a top-level command named **`docs`** unless `docs.enabled: false`
|
|
|
135
136
|
|
|
136
137
|
## Agent artifact contract
|
|
137
138
|
|
|
138
|
-
Load one primary artifact per task — avoid pulling
|
|
139
|
+
Load one primary artifact per task — avoid pulling full CLI markdown trees or JSON schemas unless you need exact shapes. Agents should rely on `<subcommand> --help` for on-demand option and positional discovery.
|
|
139
140
|
|
|
140
141
|
| Goal | Load |
|
|
141
142
|
| --- | --- |
|
|
142
143
|
| Route to the right command | `SKILL.md` (via `configure` or `docs skill`) |
|
|
143
|
-
|
|
|
144
|
+
| Inspect options and positional slots | `<subcommand> --help` |
|
|
145
|
+
| Full command tree + option prose | `docs cli` |
|
|
144
146
|
| Machine-readable CLI tree + schemas | `docs cli-schema` |
|
|
145
147
|
| HTTP request/response shapes | `docs openapi` or `GET /openapi.json` |
|
|
146
148
|
| MCP tool list + env config | `docs mcp` |
|
|
147
149
|
|
|
148
|
-
Skill `reference.md` is **compact** (no embedded `outputSchema` JSON blocks). Fetch `cli-schema` or OpenAPI when you need exact shapes.
|
|
149
|
-
|
|
150
150
|
## Save to disk (`--save`)
|
|
151
151
|
|
|
152
|
-
Pass **`--save`** on `docs` or any docs subcommand to write files under **`./docs/`** (relative to the current working directory). Each saved path is printed on stdout.
|
|
152
|
+
Pass **`--save`** on `docs` or any docs subcommand to write files under **`./docs/`** (or **`./skills/<app>/`** for skills) (relative to the current working directory). Each saved path is printed on stdout.
|
|
153
153
|
|
|
154
154
|
| Command | Output |
|
|
155
155
|
| --- | --- |
|
|
@@ -157,6 +157,6 @@ Pass **`--save`** on `docs` or any docs subcommand to write files under **`./doc
|
|
|
157
157
|
| `docs cli-schema --save` | `./docs/cli-schema.json` |
|
|
158
158
|
| `docs openapi --save` | `./docs/openapi.json` |
|
|
159
159
|
| `docs cli --save` | `./docs/cli.md` |
|
|
160
|
-
| `docs skill --save` | `./
|
|
160
|
+
| `docs skill --save` | `./skills/<app>/SKILL.md` |
|
|
161
161
|
|
|
162
162
|
Argsbarg-generated markdown (`mcp`, `http`, `skill`) includes a `Generated by … docs … --save` HTML comment (`skill` places it after YAML frontmatter so parsers still work). `cli-schema.json`, `openapi.json`, and argsbarg-generated markdown resolve `{argsbarg:program}` in `notes` to the program key. Consumer-authored topic files are written as-is.
|
package/docs/cli-program.md
CHANGED
|
@@ -112,7 +112,7 @@ Descriptions and schemas are copied into MCP tools, HTTP OpenAPI, and generated
|
|
|
112
112
|
- Keep **`description`** strings short and action-oriented; put examples in **`notes`**, not duplicated in every option.
|
|
113
113
|
- Use **`hidden: true`** or **`mcpTool.enabled: false`** for debug/internal commands.
|
|
114
114
|
- For shape discovery: HTTP agents load **`docs openapi`** or `GET /openapi.json`; MCP agents use **`docs cli-schema`**; load full **`docs cli`** only when prose is needed.
|
|
115
|
-
-
|
|
115
|
+
- Generated **`SKILL.md`** acts as an intent-based router that directs agents to `<subcommand> --help` — see [bundled-docs.md](bundled-docs.md#agent-artifact-contract).
|
|
116
116
|
|
|
117
117
|
Validation: [json-schema-subset.md](json-schema-subset.md) (Draft-07 default; 2019-09 / 2020-12 when `$schema` is set — including Zod-generated schemas).
|
|
118
118
|
|
|
@@ -160,7 +160,7 @@ On **leaf commands**, set `outputSchema` to a JSON Schema describing stdout when
|
|
|
160
160
|
}
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
Exported in `docs cli-schema`, `docs cli`,
|
|
163
|
+
Exported in `docs cli-schema`, `docs cli`, and MCP `tools/list`. Not validated at runtime yet. Pair with `notes` for prose examples; do not duplicate the full schema in `notes`.
|
|
164
164
|
|
|
165
165
|
For a **outputSchema codegen guidelines** (TypeScript types → JSON Schema → `outputSchema` constants), see [output-schema.md](output-schema.md).
|
|
166
166
|
|
package/docs/output-schema.md
CHANGED
|
@@ -21,7 +21,7 @@ export const status = {
|
|
|
21
21
|
| --- | --- |
|
|
22
22
|
| `myapp docs cli-schema` | Full command tree JSON export |
|
|
23
23
|
| `myapp docs cli` | Markdown per-command **Output** section |
|
|
24
|
-
| `myapp docs skill` |
|
|
24
|
+
| `myapp docs skill` | Intent-based SKILL.md router for agent skills |
|
|
25
25
|
| MCP `tools/list` | Optional `outputSchema` on each tool |
|
|
26
26
|
| HTTP `GET /openapi.json` | Response schema per tool |
|
|
27
27
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
- `README.md` — user-facing install/commands
|
|
10
10
|
- `docs/architecture.md` — maintainer internals (create if missing)
|
|
11
|
-
- Generated: `just docgen` → `docs/cli.md`, `docs/cli-schema.json`, `
|
|
11
|
+
- Generated: `just docgen` → `docs/cli.md`, `docs/cli-schema.json`, `skills/full-example/SKILL.md`
|
|
12
12
|
|
|
13
13
|
<!-- argsbarg:managed -->
|
|
14
14
|
|
|
@@ -67,6 +67,8 @@ When adding commands: `src/commands/<name>/command.ts`; register in `program.ts`
|
|
|
67
67
|
|
|
68
68
|
- **CLI:** `bun ./src/index.ts …` or `just run …`
|
|
69
69
|
- **Tests:** `just test` (after `just check`)
|
|
70
|
+
- **Agent discovery via `--help`:** Prefer running `<cli> <subcommand> --help` to inspect flags, defaults, and positionals on-the-fly. Avoid reading long API documentation files (`docs/cli.md`) into context.
|
|
71
|
+
- **Agent skill:** `skills/<key>/SKILL.md` is an intent-based router. Run `configure install` to persist it to `~/.agents/skills/<key>/`.
|
|
70
72
|
|
|
71
73
|
<!-- /argsbarg:managed -->
|
|
72
74
|
|
|
@@ -11,7 +11,7 @@ Reference template for argsbarg consumer docgen. Every builtin is enabled in `sr
|
|
|
11
11
|
| **Full command tree (markdown)** | [cli.md](cli.md) — generated |
|
|
12
12
|
| **Full command tree (JSON)** | [cli-schema.json](cli-schema.json) — generated |
|
|
13
13
|
| **OpenAPI 3.1** | [openapi.json](openapi.json) — generated |
|
|
14
|
-
| **Agent skill
|
|
14
|
+
| **Agent skill router** | [../skills/full-example/SKILL.md](../skills/full-example/SKILL.md) — generated |
|
|
15
15
|
|
|
16
16
|
## Framework docs vs this directory
|
|
17
17
|
|
|
@@ -60,7 +60,7 @@ See the argsbarg [logging guide](https://github.com/bdombro/bun-argsbarg/blob/ma
|
|
|
60
60
|
## REST routes
|
|
61
61
|
|
|
62
62
|
- `POST /echo` (CLI: `full-example echo`) — Echo a message (MCP-friendly leaf).
|
|
63
|
-
- `POST /status` (CLI: `full-example status`) — Show app version.
|
|
63
|
+
- `POST /status` (CLI: `full-example status`) — Show app version.
|
|
64
64
|
|
|
65
65
|
## Request bodies
|
|
66
66
|
|
|
@@ -92,7 +92,7 @@ full-example mcp
|
|
|
92
92
|
## Exposed tools
|
|
93
93
|
|
|
94
94
|
- `full-example echo` — echo — Echo a message (MCP-friendly leaf).
|
|
95
|
-
- `full-example status` — status — Show app version.
|
|
95
|
+
- `full-example status` — status — Show app version.
|
|
96
96
|
|
|
97
97
|
## Tool arguments
|
|
98
98
|
|
|
@@ -19,18 +19,22 @@ Invoke via shell:
|
|
|
19
19
|
full-example <subcommand> [options] [args]
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
## Options & Help Discovery
|
|
23
|
+
|
|
24
|
+
- Run `full-example <subcommand> --help` to inspect flags, choices, and positional arguments before running unfamiliar subcommands.
|
|
25
|
+
- Run `full-example --help` at the root for top-level options and command routing.
|
|
26
|
+
|
|
22
27
|
## Commands
|
|
23
28
|
|
|
24
29
|
- **`full-example echo`** — Echo a message (MCP-friendly leaf).
|
|
25
|
-
- **`full-example status`** — Show app version.
|
|
30
|
+
- **`full-example status`** — Show app version.
|
|
26
31
|
|
|
27
|
-
## Pitfalls
|
|
32
|
+
## Workflow & Pitfalls
|
|
28
33
|
|
|
34
|
+
- Always run `full-example <subcommand> --help` instead of guessing options or reading large doc files.
|
|
29
35
|
- Pass `--` before arguments that look like flags.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
For full detail, open `reference.md` in this skill directory (same as `full-example docs cli`).
|
|
36
|
+
- Pass `--yes` for non-interactive execution when confirmation is required.
|
|
37
|
+
- Pass `--json` when machine-readable structured output is supported.
|
|
34
38
|
|
|
35
39
|
## Install location
|
|
36
40
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
- `README.md` — user-facing install/commands
|
|
10
10
|
- `docs/architecture.md` — maintainer internals (create if missing)
|
|
11
|
-
- Generated: `just docgen` → `docs/cli.md`, `docs/cli-schema.json`, `
|
|
11
|
+
- Generated: `just docgen` → `docs/cli.md`, `docs/cli-schema.json`, `skills/full-example-json/SKILL.md`
|
|
12
12
|
|
|
13
13
|
<!-- argsbarg:managed -->
|
|
14
14
|
|
|
@@ -72,6 +72,8 @@ When adding commands: `src/commands/<name>/command.ts` + `types.ts` when schemas
|
|
|
72
72
|
- **Runtime:** Bun (`just test`, `just dev`).
|
|
73
73
|
- **Tests:** colocate `*.test.ts` next to the module.
|
|
74
74
|
- **Schemagen:** after changing `/** @sg */` types in `src/`, run `just schemagen` (`__generated__/` is gitignored).
|
|
75
|
+
- **Agent discovery via `--help`:** Prefer running `<cli> <subcommand> --help` to inspect flags, defaults, and positionals on-the-fly. Avoid reading long API documentation files (`docs/cli.md`) into context.
|
|
76
|
+
- **Agent skill:** `skills/<key>/SKILL.md` is an intent-based router. Run `configure install` to persist it to `~/.agents/skills/<key>/`.
|
|
75
77
|
|
|
76
78
|
### Abstractions
|
|
77
79
|
|
|
@@ -11,7 +11,7 @@ Reference template for argsbarg consumer docgen. Every builtin is enabled in `sr
|
|
|
11
11
|
| **Full command tree (markdown)** | [cli.md](cli.md) — generated |
|
|
12
12
|
| **Full command tree (JSON)** | [cli-schema.json](cli-schema.json) — generated |
|
|
13
13
|
| **OpenAPI 3.1** | [openapi.json](openapi.json) — generated |
|
|
14
|
-
| **Agent skill
|
|
14
|
+
| **Agent skill router** | [../skills/full-example-json/SKILL.md](../skills/full-example-json/SKILL.md) — generated |
|
|
15
15
|
|
|
16
16
|
## Framework docs vs this directory
|
|
17
17
|
|
|
@@ -61,7 +61,7 @@ See the argsbarg [logging guide](https://github.com/bdombro/bun-argsbarg/blob/ma
|
|
|
61
61
|
|
|
62
62
|
- `POST /echo` (CLI: `full-example-json echo`) — Echo a message (MCP-friendly leaf).
|
|
63
63
|
- `POST /render-json` (CLI: `full-example-json render-json`) — Echo a JSON message (schema-first JSON leaf demo).
|
|
64
|
-
- `POST /status` (CLI: `full-example-json status`) — Show app version.
|
|
64
|
+
- `POST /status` (CLI: `full-example-json status`) — Show app version.
|
|
65
65
|
- `GET /workspaces` (CLI: `full-example-json workspaces get`) — List workspaces.
|
|
66
66
|
- `POST /workspaces` (CLI: `full-example-json workspaces post`) — Create a workspace.
|
|
67
67
|
- `GET /workspaces/{id}` (CLI: `full-example-json workspaces :id get`) — Get one workspace.
|
|
@@ -93,13 +93,13 @@ full-example-json mcp
|
|
|
93
93
|
|
|
94
94
|
- `full-example-json echo` — echo — Echo a message (MCP-friendly leaf).
|
|
95
95
|
- `full-example-json render-json` — render-json — Echo a JSON message (schema-first JSON leaf demo).
|
|
96
|
-
- `full-example-json status` — status — Show app version.
|
|
97
|
-
- `full-example-json workspaces
|
|
98
|
-
- `full-example-json workspaces post` — workspaces post — Create a workspace.
|
|
96
|
+
- `full-example-json status` — status — Show app version.
|
|
97
|
+
- `full-example-json workspaces :id delete` — workspaces :id delete — Delete a workspace.
|
|
99
98
|
- `full-example-json workspaces :id get` — workspaces :id get — Get one workspace.
|
|
100
|
-
- `full-example-json workspaces :id put` — workspaces :id put — Replace a workspace.
|
|
101
99
|
- `full-example-json workspaces :id patch` — workspaces :id patch — Patch a workspace name.
|
|
102
|
-
- `full-example-json workspaces :id
|
|
100
|
+
- `full-example-json workspaces :id put` — workspaces :id put — Replace a workspace.
|
|
101
|
+
- `full-example-json workspaces get` — workspaces get — List workspaces.
|
|
102
|
+
- `full-example-json workspaces post` — workspaces post — Create a workspace.
|
|
103
103
|
|
|
104
104
|
## Tool arguments
|
|
105
105
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: full-example-json
|
|
3
3
|
name: full-example-json
|
|
4
|
-
description: Operates the full-example-json CLI (echo, render-json, status, workspaces
|
|
4
|
+
description: Operates the full-example-json CLI (echo, render-json, status, workspaces :id delete, workspaces :id get, and 4 more). Use when the user mentions full-example-json, echo, render-json, status, or related tasks.
|
|
5
5
|
enabled: true
|
|
6
6
|
---
|
|
7
7
|
<!-- Generated by full-example-json docs skill --save; do not edit. -->
|
|
@@ -19,25 +19,29 @@ Invoke via shell:
|
|
|
19
19
|
full-example-json <subcommand> [options] [args]
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
## Options & Help Discovery
|
|
23
|
+
|
|
24
|
+
- Run `full-example-json <subcommand> --help` to inspect flags, choices, and positional arguments before running unfamiliar subcommands.
|
|
25
|
+
- Run `full-example-json --help` at the root for top-level options and command routing.
|
|
26
|
+
|
|
22
27
|
## Commands
|
|
23
28
|
|
|
24
29
|
- **`full-example-json echo`** — Echo a message (MCP-friendly leaf).
|
|
25
30
|
- **`full-example-json render-json`** — Echo a JSON message (schema-first JSON leaf demo).
|
|
26
|
-
- **`full-example-json status`** — Show app version.
|
|
27
|
-
- **`full-example-json workspaces
|
|
28
|
-
- **`full-example-json workspaces post`** — Create a workspace.
|
|
31
|
+
- **`full-example-json status`** — Show app version.
|
|
32
|
+
- **`full-example-json workspaces :id delete`** — Delete a workspace.
|
|
29
33
|
- **`full-example-json workspaces :id get`** — Get one workspace.
|
|
30
|
-
- **`full-example-json workspaces :id put`** — Replace a workspace.
|
|
31
34
|
- **`full-example-json workspaces :id patch`** — Patch a workspace name.
|
|
32
|
-
- **`full-example-json workspaces :id
|
|
35
|
+
- **`full-example-json workspaces :id put`** — Replace a workspace.
|
|
36
|
+
- **`full-example-json workspaces get`** — List workspaces.
|
|
37
|
+
- **`full-example-json workspaces post`** — Create a workspace.
|
|
33
38
|
|
|
34
|
-
## Pitfalls
|
|
39
|
+
## Workflow & Pitfalls
|
|
35
40
|
|
|
41
|
+
- Always run `full-example-json <subcommand> --help` instead of guessing options or reading large doc files.
|
|
36
42
|
- Pass `--` before arguments that look like flags.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
For full detail, open `reference.md` in this skill directory (same as `full-example-json docs cli`).
|
|
43
|
+
- Pass `--yes` for non-interactive execution when confirmation is required.
|
|
44
|
+
- Pass `--json` when machine-readable structured output is supported.
|
|
41
45
|
|
|
42
46
|
## Install location
|
|
43
47
|
|
package/package.json
CHANGED
|
@@ -83,6 +83,7 @@ describe("argsbarg create", () => {
|
|
|
83
83
|
const tree = renderCreateTree(baseOpts({ key: "testapp" }));
|
|
84
84
|
expect(tree.has("justfile")).toBe(true);
|
|
85
85
|
expect(tree.has("scripts/create-identity.ts")).toBe(true);
|
|
86
|
+
expect(tree.has("skills/testapp/SKILL.md")).toBe(true);
|
|
86
87
|
const identity = tree.get("scripts/create-identity.ts");
|
|
87
88
|
expect(identity).toContain('key: "testapp"');
|
|
88
89
|
expect(identity).toContain('template: "cli"');
|
package/src/cli-tool/create.ts
CHANGED
|
@@ -330,6 +330,7 @@ function listTemplateFiles(templateId: CreateTemplateId): string[] {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
export function renderCreateTree(opts: CreateOptions): Map<string, string> {
|
|
333
|
+
const tmpl = templateIdentity(opts.templateId);
|
|
333
334
|
const files = new Map<string, string>();
|
|
334
335
|
for (const rel of listTemplateFiles(opts.templateId)) {
|
|
335
336
|
if (rel === CREATE_IDENTITY_REL) {
|
|
@@ -338,7 +339,10 @@ export function renderCreateTree(opts: CreateOptions): Map<string, string> {
|
|
|
338
339
|
}
|
|
339
340
|
const src = join(templateDirFor(opts.templateId), rel);
|
|
340
341
|
const raw = readFileSync(src, "utf8");
|
|
341
|
-
|
|
342
|
+
const targetRel = rel.startsWith(`skills/${tmpl.key}/`)
|
|
343
|
+
? rel.replace(`skills/${tmpl.key}/`, `skills/${opts.key}/`)
|
|
344
|
+
: rel;
|
|
345
|
+
files.set(targetRel, substituteTemplateContent(raw, opts));
|
|
342
346
|
}
|
|
343
347
|
return files;
|
|
344
348
|
}
|
package/src/core/parse.test.ts
CHANGED
|
@@ -3,7 +3,7 @@ Domain-specific regression tests (split from index.test.ts).
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { expect, test } from "bun:test";
|
|
6
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { $ } from "bun";
|
|
@@ -370,6 +370,184 @@ test("trailing options after bounded positionals", () => {
|
|
|
370
370
|
expect(pr.opts.verbose).toBe("1");
|
|
371
371
|
});
|
|
372
372
|
|
|
373
|
+
/** Tests that options can be interleaved between bounded positionals. */
|
|
374
|
+
test("options interleaved between bounded positionals", () => {
|
|
375
|
+
const root = testProgram({
|
|
376
|
+
key: "app",
|
|
377
|
+
description: "",
|
|
378
|
+
commands: [
|
|
379
|
+
{
|
|
380
|
+
key: "copy",
|
|
381
|
+
description: "copy",
|
|
382
|
+
options: [
|
|
383
|
+
{
|
|
384
|
+
name: "force",
|
|
385
|
+
description: "",
|
|
386
|
+
kind: CliOptionKind.Presence,
|
|
387
|
+
shortName: "f",
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
name: "mode",
|
|
391
|
+
description: "",
|
|
392
|
+
kind: CliOptionKind.String,
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
positionals: [
|
|
396
|
+
{
|
|
397
|
+
name: "src",
|
|
398
|
+
description: "",
|
|
399
|
+
kind: CliOptionKind.String,
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: "dest",
|
|
403
|
+
description: "",
|
|
404
|
+
kind: CliOptionKind.String,
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
handler: () => {},
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
});
|
|
411
|
+
cliValidateProgram(root);
|
|
412
|
+
|
|
413
|
+
// Presence flag interleaved between positionals
|
|
414
|
+
const prPresence = postParseValidate(root, parse(root, ["copy", "file1", "--force", "file2"]));
|
|
415
|
+
expect(prPresence.kind).toBe(ParseKind.Ok);
|
|
416
|
+
expect(prPresence.args).toEqual(["file1", "file2"]);
|
|
417
|
+
expect(prPresence.opts.force).toBe("1");
|
|
418
|
+
|
|
419
|
+
// String option with value interleaved between positionals
|
|
420
|
+
const prString = postParseValidate(root, parse(root, ["copy", "file1", "--mode", "fast", "file2"]));
|
|
421
|
+
expect(prString.kind).toBe(ParseKind.Ok);
|
|
422
|
+
expect(prString.args).toEqual(["file1", "file2"]);
|
|
423
|
+
expect(prString.opts.mode).toBe("fast");
|
|
424
|
+
|
|
425
|
+
// Multiple flags interleaved between positionals
|
|
426
|
+
const prMulti = postParseValidate(root, parse(root, ["copy", "file1", "--mode", "fast", "-f", "file2"]));
|
|
427
|
+
expect(prMulti.kind).toBe(ParseKind.Ok);
|
|
428
|
+
expect(prMulti.args).toEqual(["file1", "file2"]);
|
|
429
|
+
expect(prMulti.opts.mode).toBe("fast");
|
|
430
|
+
expect(prMulti.opts.force).toBe("1");
|
|
431
|
+
|
|
432
|
+
// Unknown option interleaved between positionals returns error
|
|
433
|
+
const prUnknown = postParseValidate(root, parse(root, ["copy", "file1", "--unknown", "file2"]));
|
|
434
|
+
expect(prUnknown.kind).toBe(ParseKind.Error);
|
|
435
|
+
expect(prUnknown.errorMsg).toContain("Unknown option: --unknown");
|
|
436
|
+
|
|
437
|
+
// Interleaved help request triggers contextual help
|
|
438
|
+
const prHelp = parse(root, ["copy", "file1", "-h"]);
|
|
439
|
+
expect(prHelp.kind).toBe(ParseKind.Help);
|
|
440
|
+
expect(prHelp.helpPath).toEqual(["copy"]);
|
|
441
|
+
|
|
442
|
+
// Double dash between positionals disables option consumption
|
|
443
|
+
const prDoubleDash = postParseValidate(root, parse(root, ["copy", "file1", "--", "--force"]));
|
|
444
|
+
expect(prDoubleDash.kind).toBe(ParseKind.Ok);
|
|
445
|
+
expect(prDoubleDash.args).toEqual(["file1", "--force"]);
|
|
446
|
+
expect(prDoubleDash.opts.force).toBeUndefined();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
/** Tests that options can be interleaved with optional positionals. */
|
|
450
|
+
test("options interleaved with optional positionals", () => {
|
|
451
|
+
const root = testProgram({
|
|
452
|
+
key: "app",
|
|
453
|
+
description: "",
|
|
454
|
+
commands: [
|
|
455
|
+
{
|
|
456
|
+
key: "deploy",
|
|
457
|
+
description: "deploy",
|
|
458
|
+
options: [
|
|
459
|
+
{
|
|
460
|
+
name: "force",
|
|
461
|
+
description: "",
|
|
462
|
+
kind: CliOptionKind.Presence,
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
positionals: [
|
|
466
|
+
{
|
|
467
|
+
name: "env",
|
|
468
|
+
description: "",
|
|
469
|
+
kind: CliOptionKind.String,
|
|
470
|
+
argMin: 0,
|
|
471
|
+
argMax: 1,
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: "target",
|
|
475
|
+
description: "",
|
|
476
|
+
kind: CliOptionKind.String,
|
|
477
|
+
argMin: 0,
|
|
478
|
+
argMax: 1,
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
handler: () => {},
|
|
482
|
+
},
|
|
483
|
+
],
|
|
484
|
+
});
|
|
485
|
+
cliValidateProgram(root);
|
|
486
|
+
|
|
487
|
+
// Interleaved between two optional positionals
|
|
488
|
+
const prBoth = postParseValidate(root, parse(root, ["deploy", "prod", "--force", "us-east"]));
|
|
489
|
+
expect(prBoth.kind).toBe(ParseKind.Ok);
|
|
490
|
+
expect(prBoth.args).toEqual(["prod", "us-east"]);
|
|
491
|
+
expect(prBoth.opts.force).toBe("1");
|
|
492
|
+
|
|
493
|
+
// Option after first optional positional when second is omitted
|
|
494
|
+
const prOne = postParseValidate(root, parse(root, ["deploy", "prod", "--force"]));
|
|
495
|
+
expect(prOne.kind).toBe(ParseKind.Ok);
|
|
496
|
+
expect(prOne.args).toEqual(["prod"]);
|
|
497
|
+
expect(prOne.opts.force).toBe("1");
|
|
498
|
+
|
|
499
|
+
// Option before optional positionals when all are omitted
|
|
500
|
+
const prNone = postParseValidate(root, parse(root, ["deploy", "--force"]));
|
|
501
|
+
expect(prNone.kind).toBe(ParseKind.Ok);
|
|
502
|
+
expect(prNone.args).toEqual([]);
|
|
503
|
+
expect(prNone.opts.force).toBe("1");
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
/** Tests that options can be interleaved between bounded positional and varargs tail. */
|
|
507
|
+
test("options interleaved between bounded positional and varargs tail", () => {
|
|
508
|
+
const root = testProgram({
|
|
509
|
+
key: "app",
|
|
510
|
+
description: "",
|
|
511
|
+
commands: [
|
|
512
|
+
{
|
|
513
|
+
key: "upload",
|
|
514
|
+
description: "upload",
|
|
515
|
+
options: [
|
|
516
|
+
{
|
|
517
|
+
name: "json",
|
|
518
|
+
description: "",
|
|
519
|
+
kind: CliOptionKind.Presence,
|
|
520
|
+
},
|
|
521
|
+
],
|
|
522
|
+
positionals: [
|
|
523
|
+
{
|
|
524
|
+
name: "target",
|
|
525
|
+
description: "",
|
|
526
|
+
kind: CliOptionKind.String,
|
|
527
|
+
argMin: 1,
|
|
528
|
+
argMax: 1,
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
name: "files",
|
|
532
|
+
description: "",
|
|
533
|
+
kind: CliOptionKind.String,
|
|
534
|
+
argMin: 1,
|
|
535
|
+
argMax: 0,
|
|
536
|
+
},
|
|
537
|
+
],
|
|
538
|
+
handler: () => {},
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
});
|
|
542
|
+
cliValidateProgram(root);
|
|
543
|
+
|
|
544
|
+
// Flag between target and files does not get captured as first file
|
|
545
|
+
const pr = postParseValidate(root, parse(root, ["upload", "s3", "--json", "a.txt", "b.txt"]));
|
|
546
|
+
expect(pr.kind).toBe(ParseKind.Ok);
|
|
547
|
+
expect(pr.args).toEqual(["s3", "a.txt", "b.txt"]);
|
|
548
|
+
expect(pr.opts.json).toBe("1");
|
|
549
|
+
});
|
|
550
|
+
|
|
373
551
|
/** Tests that options on routing groups are rejected at schema validation. */
|
|
374
552
|
test("rejects options on routing groups", () => {
|
|
375
553
|
const root = testProgram({
|
|
@@ -1174,29 +1352,29 @@ test("configure.prefix is rejected", () => {
|
|
|
1174
1352
|
expect(() => cliValidateProgram(root)).toThrow(/configure\.prefix removed/);
|
|
1175
1353
|
});
|
|
1176
1354
|
|
|
1177
|
-
/** Tests that generateSkillBundle includes frontmatter and
|
|
1178
|
-
test("generateSkillBundle includes frontmatter and
|
|
1355
|
+
/** Tests that generateSkillBundle includes frontmatter and intent-based router. */
|
|
1356
|
+
test("generateSkillBundle includes frontmatter and intent-based router", () => {
|
|
1179
1357
|
const bundle = generateSkillBundle(nestedMcpFixture);
|
|
1180
1358
|
expect(bundle.dirName).toBe("nested.ts");
|
|
1181
1359
|
expect(bundle.skillMd).toMatch(/^---\nid: nested\.ts\nname: nested\.ts\n/);
|
|
1182
1360
|
expect(bundle.skillMd).toContain("enabled: true");
|
|
1183
1361
|
expect(bundle.skillMd).toContain("dotagentsprotocol.com");
|
|
1184
1362
|
expect(bundle.skillMd).toContain("~/.claude/skills/nested.ts");
|
|
1363
|
+
expect(bundle.skillMd).toContain("Options & Help Discovery");
|
|
1364
|
+
expect(bundle.skillMd).toContain("Run `nested.ts <subcommand> --help`");
|
|
1185
1365
|
expect(bundle.skillMd).toContain("## Commands");
|
|
1186
1366
|
expect(bundle.skillMd).toContain("`nested.ts stat owner lookup <path>`");
|
|
1187
1367
|
expect(bundle.skillMd).toContain("Invoke via shell:");
|
|
1188
|
-
expect(bundle.skillMd).toContain("
|
|
1368
|
+
expect(bundle.skillMd).toContain("Workflow & Pitfalls");
|
|
1189
1369
|
expect(bundle.skillMd).toContain("~/.agents/skills/nested.ts/");
|
|
1370
|
+
expect(bundle.skillMd).not.toContain("reference.md");
|
|
1190
1371
|
expect(bundle.skillMd).not.toContain("#### Options");
|
|
1191
1372
|
expect(bundle.skillMd).not.toContain("CLI API reference");
|
|
1192
1373
|
expect(bundle.skillMd).not.toContain("mcp.json");
|
|
1193
1374
|
expect(bundle.skillMd).not.toContain("Prefer MCP");
|
|
1194
1375
|
expect(bundle.skillMd).not.toContain("tools/call");
|
|
1195
1376
|
expect(bundle.skillMd).not.toContain("Generated by");
|
|
1196
|
-
expect(bundle.referenceMd).
|
|
1197
|
-
expect(bundle.referenceMd).toContain("#### Options");
|
|
1198
|
-
expect(bundle.referenceMd).not.toContain("Generated by");
|
|
1199
|
-
expect(bundle.referenceMd).not.toContain("```json");
|
|
1377
|
+
expect((bundle as unknown as Record<string, unknown>).referenceMd).toBeUndefined();
|
|
1200
1378
|
});
|
|
1201
1379
|
|
|
1202
1380
|
/** Tests that generatePluginSkillBundle is MCP routing stub without shell catalog. */
|
|
@@ -1224,15 +1402,34 @@ test("cliSkillInstall writes project agent skill files", () => {
|
|
|
1224
1402
|
expect(files.some((f) => f.includes(".agents/skills/nested.ts/"))).toBe(true);
|
|
1225
1403
|
const skillDir = join(cwd, ".agents", "skills", "nested.ts");
|
|
1226
1404
|
expect(existsSync(join(skillDir, "SKILL.md"))).toBe(true);
|
|
1227
|
-
expect(existsSync(join(skillDir, "
|
|
1405
|
+
expect(existsSync(join(skillDir, "skill.md"))).toBe(true);
|
|
1406
|
+
expect(existsSync(join(skillDir, "reference.md"))).toBe(false);
|
|
1228
1407
|
expect(readFileSync(join(skillDir, "SKILL.md"), "utf8")).toContain("## Commands");
|
|
1229
|
-
expect(readFileSync(join(skillDir, "
|
|
1408
|
+
expect(readFileSync(join(skillDir, "SKILL.md"), "utf8")).toContain("Options & Help Discovery");
|
|
1230
1409
|
const skillText = readFileSync(join(skillDir, "SKILL.md"), "utf8");
|
|
1231
1410
|
expect(skillText.startsWith("---\n")).toBe(true);
|
|
1232
1411
|
const hint = "<!-- Generated by nested.ts configure; do not edit. -->";
|
|
1233
1412
|
expect(skillText.indexOf(hint)).toBeGreaterThan(skillText.indexOf("---\n", 4));
|
|
1234
|
-
|
|
1235
|
-
|
|
1413
|
+
} finally {
|
|
1414
|
+
process.chdir(prev);
|
|
1415
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
/** CliSkillInstall removes legacy reference.md if present. */
|
|
1420
|
+
test("cliSkillInstall removes legacy reference.md if present", () => {
|
|
1421
|
+
const cwd = mkdtempSync(join(tmpdir(), "argsbarg-skill-legacy-"));
|
|
1422
|
+
const prev = process.cwd();
|
|
1423
|
+
process.chdir(cwd);
|
|
1424
|
+
try {
|
|
1425
|
+
const skillDir = join(cwd, ".agents", "skills", "nested.ts");
|
|
1426
|
+
mkdirSync(skillDir, { recursive: true });
|
|
1427
|
+
writeFileSync(join(skillDir, "reference.md"), "legacy reference content", "utf8");
|
|
1428
|
+
expect(existsSync(join(skillDir, "reference.md"))).toBe(true);
|
|
1429
|
+
|
|
1430
|
+
cliSkillInstall(nestedMcpFixture, { global: false, rimraf: false });
|
|
1431
|
+
expect(existsSync(join(skillDir, "SKILL.md"))).toBe(true);
|
|
1432
|
+
expect(existsSync(join(skillDir, "reference.md"))).toBe(false);
|
|
1236
1433
|
} finally {
|
|
1237
1434
|
process.chdir(prev);
|
|
1238
1435
|
rmSync(cwd, { recursive: true, force: true });
|
package/src/core/parse.ts
CHANGED
|
@@ -351,9 +351,47 @@ function finishLeaf(
|
|
|
351
351
|
const args: string[] = [];
|
|
352
352
|
let forcePositionals = forcePositionalsIn;
|
|
353
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Consumes any pending options, `--`, or help flags at the current argv index.
|
|
356
|
+
* Sets `forcePositionals = true` when `--` is encountered.
|
|
357
|
+
* Returns a help or error ParseResult if parsing halts, or null to continue positional consumption.
|
|
358
|
+
*/
|
|
359
|
+
function consumePendingOptions(): ParseResult | null {
|
|
360
|
+
while (!forcePositionals && idx < argv.length) {
|
|
361
|
+
const tok = argv[idx];
|
|
362
|
+
if (tok === "--") {
|
|
363
|
+
forcePositionals = true;
|
|
364
|
+
idx += 1;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
if (isHelpTok(tok)) {
|
|
368
|
+
return helpResult(path, true, pathParams);
|
|
369
|
+
}
|
|
370
|
+
if (tok.startsWith("-")) {
|
|
371
|
+
const rep = consumeOptions(optionDefs, false, argv, idx, opts);
|
|
372
|
+
if (rep.report.err) {
|
|
373
|
+
return errorResult(rep.report.err, path, [], pathParams);
|
|
374
|
+
}
|
|
375
|
+
if (rep.report.sawDoubleDash) {
|
|
376
|
+
forcePositionals = true;
|
|
377
|
+
}
|
|
378
|
+
if (rep.nextIndex > idx) {
|
|
379
|
+
idx = rep.nextIndex;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
return errorResult(`Unexpected option token: ${tok}`, path, [], pathParams);
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
|
|
354
389
|
for (const p of node.positionals ?? []) {
|
|
355
390
|
const { argMin = 1, argMax = 1 } = p;
|
|
356
391
|
if (argMax === 1) {
|
|
392
|
+
const pendingErr = consumePendingOptions();
|
|
393
|
+
if (pendingErr) return pendingErr;
|
|
394
|
+
|
|
357
395
|
if (argMin >= 1) {
|
|
358
396
|
if (idx >= argv.length) {
|
|
359
397
|
return errorResult(`Missing positional argument: ${p.name}`, path, [], pathParams);
|
|
@@ -361,13 +399,8 @@ function finishLeaf(
|
|
|
361
399
|
args.push(argv[idx]);
|
|
362
400
|
idx += 1;
|
|
363
401
|
} else if (idx < argv.length) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
// Optional slot: leave `-` tokens for trailing option parsing.
|
|
367
|
-
} else {
|
|
368
|
-
args.push(tok);
|
|
369
|
-
idx += 1;
|
|
370
|
-
}
|
|
402
|
+
args.push(argv[idx]);
|
|
403
|
+
idx += 1;
|
|
371
404
|
}
|
|
372
405
|
continue;
|
|
373
406
|
}
|
|
@@ -375,40 +408,20 @@ function finishLeaf(
|
|
|
375
408
|
let count = 0;
|
|
376
409
|
if (argMax === 0) {
|
|
377
410
|
while (idx < argv.length) {
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
if (
|
|
381
|
-
forcePositionals = true;
|
|
382
|
-
idx++;
|
|
383
|
-
continue;
|
|
384
|
-
}
|
|
411
|
+
const pendingErr = consumePendingOptions();
|
|
412
|
+
if (pendingErr) return pendingErr;
|
|
413
|
+
if (idx >= argv.length) break;
|
|
385
414
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (!forcePositionals && tok.startsWith("-")) {
|
|
391
|
-
// MUST be false — lenient mode swallows unknown flags as positionals silently
|
|
392
|
-
const tailRep = consumeOptions(optionDefs, false, argv, idx, opts);
|
|
393
|
-
if (tailRep.report.err) {
|
|
394
|
-
return errorResult(tailRep.report.err, path, [], pathParams);
|
|
395
|
-
}
|
|
396
|
-
if (tailRep.report.sawDoubleDash) {
|
|
397
|
-
forcePositionals = true;
|
|
398
|
-
}
|
|
399
|
-
if (tailRep.nextIndex > idx) {
|
|
400
|
-
idx = tailRep.nextIndex;
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
return errorResult(`Unexpected option token: ${tok}`, path, [], pathParams);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
args.push(tok);
|
|
407
|
-
idx++;
|
|
408
|
-
count++;
|
|
415
|
+
args.push(argv[idx]);
|
|
416
|
+
idx += 1;
|
|
417
|
+
count += 1;
|
|
409
418
|
}
|
|
410
419
|
} else {
|
|
411
420
|
while (count < argMax && idx < argv.length) {
|
|
421
|
+
const pendingErr = consumePendingOptions();
|
|
422
|
+
if (pendingErr) return pendingErr;
|
|
423
|
+
if (idx >= argv.length) break;
|
|
424
|
+
|
|
412
425
|
args.push(argv[idx]);
|
|
413
426
|
idx += 1;
|
|
414
427
|
count += 1;
|
|
@@ -419,24 +432,11 @@ function finishLeaf(
|
|
|
419
432
|
}
|
|
420
433
|
}
|
|
421
434
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
return errorResult("Unexpected extra arguments", path, [], pathParams);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
if (isHelpTok(argv[idx])) {
|
|
428
|
-
return helpResult(path, true, pathParams);
|
|
429
|
-
}
|
|
435
|
+
const trailingErr = consumePendingOptions();
|
|
436
|
+
if (trailingErr) return trailingErr;
|
|
430
437
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
return errorResult(tailRep.report.err, path, [], pathParams);
|
|
434
|
-
}
|
|
435
|
-
idx = tailRep.nextIndex;
|
|
436
|
-
|
|
437
|
-
if (idx < argv.length) {
|
|
438
|
-
return errorResult("Unexpected extra arguments", path, [], pathParams);
|
|
439
|
-
}
|
|
438
|
+
if (idx < argv.length) {
|
|
439
|
+
return errorResult("Unexpected extra arguments", path, [], pathParams);
|
|
440
440
|
}
|
|
441
441
|
|
|
442
442
|
return {
|
package/src/docs/docs.test.ts
CHANGED
|
@@ -259,7 +259,8 @@ test("docs skill prints Cursor SKILL.md", async () => {
|
|
|
259
259
|
expect(result.stdout).toContain("---");
|
|
260
260
|
expect(result.stdout).toContain("name: myapp");
|
|
261
261
|
expect(result.stdout).toContain("## Commands");
|
|
262
|
-
expect(result.stdout).toContain("
|
|
262
|
+
expect(result.stdout).toContain("--help");
|
|
263
|
+
expect(result.stdout).not.toContain("reference.md");
|
|
263
264
|
expect(result.stdout).not.toContain("#### Options");
|
|
264
265
|
expect(result.stdout).not.toContain("mcp.json");
|
|
265
266
|
});
|
|
@@ -329,10 +330,11 @@ test("docs cli --save prepends generated hint", async () => {
|
|
|
329
330
|
expect(text).toContain("CLI API reference");
|
|
330
331
|
});
|
|
331
332
|
|
|
332
|
-
test("docs skill --save keeps frontmatter first", async () => {
|
|
333
|
+
test("docs skill --save writes skills/<app>/SKILL.md and keeps frontmatter first", async () => {
|
|
333
334
|
const result = await new Cli(docsFixture()).invoke(["docs", "skill", "--save"]);
|
|
334
335
|
expect(result.exitCode).toBe(0);
|
|
335
|
-
|
|
336
|
+
expect(result.stdout.trim()).toBe("skills/myapp/SKILL.md");
|
|
337
|
+
const text = readFileSync(join(workDir, "skills/myapp/SKILL.md"), "utf8");
|
|
336
338
|
expect(text.startsWith("---\n")).toBe(true);
|
|
337
339
|
expect(text).toContain("name: myapp");
|
|
338
340
|
const hint = "<!-- Generated by myapp docs skill --save; do not edit. -->";
|
package/src/docs/save.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/*
|
|
2
|
+
This module persists bundled documentation topics to disk when `--save` is passed.
|
|
3
|
+
It writes documentation under `./docs/` and agent skills under `./skills/<app-name>/SKILL.md`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
3
8
|
import type { CliProgram } from "../core/types.ts";
|
|
4
9
|
import { generatedFileHtmlComment, insertGeneratedHint } from "../skill/hint.ts";
|
|
10
|
+
import { skillDirName } from "../skill/naming.ts";
|
|
5
11
|
import { docsTopicContent } from "./resolve.ts";
|
|
6
12
|
|
|
7
13
|
/** Relative output directory for `docs --save`. */
|
|
@@ -11,17 +17,32 @@ export const DOCS_SAVE_DIR = "docs";
|
|
|
11
17
|
export const DOCS_GENERATED_SAVE_TOPICS = ["mcp", "cli", "skill", "http"] as const;
|
|
12
18
|
|
|
13
19
|
/** Whether `--save` should prepend a generated-file hint (argsbarg writers only). */
|
|
14
|
-
export function docsTopicIsGeneratedByArgsbarg(
|
|
20
|
+
export function docsTopicIsGeneratedByArgsbarg(
|
|
21
|
+
/** Topic name. */
|
|
22
|
+
topic: string,
|
|
23
|
+
): boolean {
|
|
15
24
|
return (DOCS_GENERATED_SAVE_TOPICS as readonly string[]).includes(topic);
|
|
16
25
|
}
|
|
17
26
|
|
|
18
27
|
/** HTML comment for generated markdown saved with `--save`. */
|
|
19
|
-
export function docsSaveGeneratedHint(
|
|
28
|
+
export function docsSaveGeneratedHint(
|
|
29
|
+
/** Program definition. */
|
|
30
|
+
program: CliProgram,
|
|
31
|
+
/** Topic name. */
|
|
32
|
+
topic: string,
|
|
33
|
+
): string {
|
|
20
34
|
return generatedFileHtmlComment(`${program.key} docs ${topic} --save`);
|
|
21
35
|
}
|
|
22
36
|
|
|
23
37
|
/** Inserts save hint without breaking YAML frontmatter (`docs skill`). */
|
|
24
|
-
export function applySaveGeneratedHint(
|
|
38
|
+
export function applySaveGeneratedHint(
|
|
39
|
+
/** Program definition. */
|
|
40
|
+
program: CliProgram,
|
|
41
|
+
/** Topic name. */
|
|
42
|
+
topic: string,
|
|
43
|
+
/** Markdown text. */
|
|
44
|
+
content: string,
|
|
45
|
+
): string {
|
|
25
46
|
if (!docsTopicIsGeneratedByArgsbarg(topic)) {
|
|
26
47
|
return content;
|
|
27
48
|
}
|
|
@@ -30,12 +51,20 @@ export function applySaveGeneratedHint(program: CliProgram, topic: string, conte
|
|
|
30
51
|
}
|
|
31
52
|
|
|
32
53
|
/** File body for `--save` (hint on argsbarg-generated markdown only). */
|
|
33
|
-
export function docsTopicContentForSave(
|
|
54
|
+
export function docsTopicContentForSave(
|
|
55
|
+
/** Program definition. */
|
|
56
|
+
program: CliProgram,
|
|
57
|
+
/** Topic name. */
|
|
58
|
+
topic: string,
|
|
59
|
+
): string {
|
|
34
60
|
return applySaveGeneratedHint(program, topic, docsTopicContent(program, topic));
|
|
35
61
|
}
|
|
36
62
|
|
|
37
63
|
/** Filename for a saved docs topic. */
|
|
38
|
-
export function docsSaveFilename(
|
|
64
|
+
export function docsSaveFilename(
|
|
65
|
+
/** Topic name. */
|
|
66
|
+
topic: string,
|
|
67
|
+
): string {
|
|
39
68
|
if (topic === "cli-schema") {
|
|
40
69
|
return "cli-schema.json";
|
|
41
70
|
}
|
|
@@ -45,18 +74,37 @@ export function docsSaveFilename(topic: string): string {
|
|
|
45
74
|
return `${topic}.md`;
|
|
46
75
|
}
|
|
47
76
|
|
|
48
|
-
/** Relative path under cwd for a saved docs topic. */
|
|
49
|
-
export function docsSaveRelativePath(
|
|
77
|
+
/** Relative path under cwd for a saved docs topic (or skills/<key>/SKILL.md for skill). */
|
|
78
|
+
export function docsSaveRelativePath(
|
|
79
|
+
/** Topic identifier. */
|
|
80
|
+
topic: string,
|
|
81
|
+
/** Program root for resolving app-specific paths like skill directories. */
|
|
82
|
+
program?: CliProgram,
|
|
83
|
+
): string {
|
|
84
|
+
if (topic === "skill" && program) {
|
|
85
|
+
return join("skills", skillDirName(program.key), "SKILL.md");
|
|
86
|
+
}
|
|
50
87
|
return join(DOCS_SAVE_DIR, docsSaveFilename(topic));
|
|
51
88
|
}
|
|
52
89
|
|
|
53
|
-
/** Writes one docs topic under `./docs
|
|
54
|
-
export function saveDocsTopic(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
90
|
+
/** Writes one docs topic under `./docs/` or `./skills/<key>/`; returns relative path written. */
|
|
91
|
+
export function saveDocsTopic(
|
|
92
|
+
/** Program definition root. */
|
|
93
|
+
program: CliProgram,
|
|
94
|
+
/** Topic identifier to save. */
|
|
95
|
+
topic: string,
|
|
96
|
+
): string {
|
|
97
|
+
const rel = docsSaveRelativePath(topic, program);
|
|
59
98
|
const abs = join(process.cwd(), rel);
|
|
99
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
100
|
+
|
|
101
|
+
if (topic === "skill") {
|
|
102
|
+
const legacyDoc = join(process.cwd(), DOCS_SAVE_DIR, "skill.md");
|
|
103
|
+
if (existsSync(legacyDoc)) {
|
|
104
|
+
rmSync(legacyDoc, { force: true });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
60
108
|
writeFileSync(abs, docsTopicContentForSave(program, topic), "utf8");
|
|
61
109
|
return rel;
|
|
62
110
|
}
|
package/src/skill/generate.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/*
|
|
2
|
-
This module generates Agent Skills content (SKILL.md
|
|
2
|
+
This module generates Agent Skills content (SKILL.md) from a CLI schema.
|
|
3
|
+
It creates an intent-based router that directs agents to specific subcommands
|
|
4
|
+
and guides them to use `--help` for option and flag discovery.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
import { defaultConfigEntryTitle } from "../config/entry.ts";
|
|
6
8
|
import { CliOptionKind, type CliProgram } from "../core/types.ts";
|
|
7
|
-
import { generateCliGuide } from "../docs/cli-guide.ts";
|
|
8
9
|
import {
|
|
9
10
|
collectMcpTools,
|
|
10
11
|
leafWireOptions,
|
|
@@ -15,15 +16,19 @@ import {
|
|
|
15
16
|
} from "../mcp/tools.ts";
|
|
16
17
|
import { skillDirName } from "./naming.ts";
|
|
17
18
|
|
|
19
|
+
/** Agent skill bundle containing the target directory name and SKILL.md router content. */
|
|
18
20
|
export interface SkillBundle {
|
|
21
|
+
/** Target directory name under `~/.agents/skills/`. */
|
|
19
22
|
dirName: string;
|
|
23
|
+
/** Generated SKILL.md router content. */
|
|
20
24
|
skillMd: string;
|
|
21
|
-
referenceMd: string;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
/** MCP routing skill for Claude Code plugin zips (SKILL.md only). */
|
|
25
28
|
export interface PluginSkillBundle {
|
|
29
|
+
/** Target directory name under `skills/`. */
|
|
26
30
|
dirName: string;
|
|
31
|
+
/** Generated plugin SKILL.md content. */
|
|
27
32
|
skillMd: string;
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -65,7 +70,7 @@ function commandCatalogPath(root: CliProgram, tool: McpToolDef): string {
|
|
|
65
70
|
return `${base} ${slots.join(" ")}`;
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
/** Formats one command line for the SKILL.md
|
|
73
|
+
/** Formats one command line for the SKILL.md router. */
|
|
69
74
|
function formatCommandEntry(root: CliProgram, tool: McpToolDef): string {
|
|
70
75
|
const cliPath = commandCatalogPath(root, tool);
|
|
71
76
|
let line = `- **\`${cliPath}\`** — ${tool.leaf.description}`;
|
|
@@ -85,6 +90,7 @@ function formatCommandEntry(root: CliProgram, tool: McpToolDef): string {
|
|
|
85
90
|
return line;
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
/** Builds configuration section for SKILL.md when appConfig entries exist. */
|
|
88
94
|
function buildConfigurationSection(root: CliProgram): string[] {
|
|
89
95
|
const schema = root.appConfig?.entries;
|
|
90
96
|
if (!schema || Object.keys(schema).length === 0) {
|
|
@@ -100,7 +106,7 @@ function buildConfigurationSection(root: CliProgram): string[] {
|
|
|
100
106
|
return lines;
|
|
101
107
|
}
|
|
102
108
|
|
|
103
|
-
/** Builds SKILL.md body for the agent skill bundle. */
|
|
109
|
+
/** Builds SKILL.md body for the agent skill bundle as an intent-based router. */
|
|
104
110
|
function buildSkillMd(root: CliProgram, dirName: string): string {
|
|
105
111
|
const name = dirName;
|
|
106
112
|
const description = skillDescription(root);
|
|
@@ -126,6 +132,11 @@ function buildSkillMd(root: CliProgram, dirName: string): string {
|
|
|
126
132
|
`${root.key} <subcommand> [options] [args]`,
|
|
127
133
|
"```",
|
|
128
134
|
"",
|
|
135
|
+
"## Options & Help Discovery",
|
|
136
|
+
"",
|
|
137
|
+
`- Run \`${root.key} <subcommand> --help\` to inspect flags, choices, and positional arguments before running unfamiliar subcommands.`,
|
|
138
|
+
`- Run \`${root.key} --help\` at the root for top-level options and command routing.`,
|
|
139
|
+
"",
|
|
129
140
|
"## Commands",
|
|
130
141
|
"",
|
|
131
142
|
];
|
|
@@ -142,13 +153,12 @@ function buildSkillMd(root: CliProgram, dirName: string): string {
|
|
|
142
153
|
lines.push(...buildConfigurationSection(root));
|
|
143
154
|
|
|
144
155
|
lines.push(
|
|
145
|
-
"## Pitfalls",
|
|
156
|
+
"## Workflow & Pitfalls",
|
|
146
157
|
"",
|
|
158
|
+
`- Always run \`${root.key} <subcommand> --help\` instead of guessing options or reading large doc files.`,
|
|
147
159
|
"- Pass `--` before arguments that look like flags.",
|
|
148
|
-
"",
|
|
149
|
-
"
|
|
150
|
-
"",
|
|
151
|
-
`For full detail, open \`reference.md\` in this skill directory (same as \`${root.key} docs cli\`).`,
|
|
160
|
+
"- Pass `--yes` for non-interactive execution when confirmation is required.",
|
|
161
|
+
"- Pass `--json` when machine-readable structured output is supported.",
|
|
152
162
|
"",
|
|
153
163
|
"## Install location",
|
|
154
164
|
"",
|
|
@@ -171,11 +181,6 @@ function buildSkillMd(root: CliProgram, dirName: string): string {
|
|
|
171
181
|
return lines.join("\n");
|
|
172
182
|
}
|
|
173
183
|
|
|
174
|
-
/** Builds reference.md with the compact `docs cli` markdown guide. */
|
|
175
|
-
function buildReferenceMd(root: CliProgram): string {
|
|
176
|
-
return generateCliGuide(root, { compact: true });
|
|
177
|
-
}
|
|
178
|
-
|
|
179
184
|
/** Builds MCP routing SKILL.md for Claude Code plugin zips. */
|
|
180
185
|
function buildPluginSkillMd(root: CliProgram, dirName: string): string {
|
|
181
186
|
const name = sanitizeToolSegment(root.key);
|
|
@@ -224,12 +229,11 @@ export function generatePluginSkillBundle(root: CliProgram): PluginSkillBundle {
|
|
|
224
229
|
};
|
|
225
230
|
}
|
|
226
231
|
|
|
227
|
-
/** Generates SKILL.md
|
|
232
|
+
/** Generates SKILL.md router content for agent skill install. */
|
|
228
233
|
export function generateSkillBundle(root: CliProgram): SkillBundle {
|
|
229
234
|
const dirName = skillDirName(root.key);
|
|
230
235
|
return {
|
|
231
236
|
dirName,
|
|
232
237
|
skillMd: buildSkillMd(root, dirName),
|
|
233
|
-
referenceMd: buildReferenceMd(root),
|
|
234
238
|
};
|
|
235
239
|
}
|
package/src/skill/hint.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This module provides HTML comment hints embedded in generated documentation
|
|
3
|
+
and agent skill artifacts to mark them as machine-generated.
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
import type { CliProgram } from "../core/types.ts";
|
|
2
7
|
|
|
3
8
|
/** YAML frontmatter block at the start of SKILL.md. */
|
|
@@ -24,16 +29,11 @@ export function skillInstallHint(program: CliProgram): string {
|
|
|
24
29
|
return generatedFileHtmlComment(`${program.key} configure`);
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
/** Applies install
|
|
28
|
-
export function applySkillInstallHints(
|
|
29
|
-
program: CliProgram,
|
|
30
|
-
skillMd: string,
|
|
31
|
-
referenceMd: string,
|
|
32
|
-
): { skillMd: string; referenceMd: string } {
|
|
32
|
+
/** Applies install hint to SKILL.md (after frontmatter). */
|
|
33
|
+
export function applySkillInstallHints(program: CliProgram, skillMd: string): { skillMd: string } {
|
|
33
34
|
const hint = skillInstallHint(program);
|
|
34
35
|
return {
|
|
35
36
|
skillMd: insertGeneratedHint(skillMd, hint, { afterFrontmatter: true }),
|
|
36
|
-
referenceMd: insertGeneratedHint(referenceMd, hint),
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -42,19 +42,6 @@ export function skillBundleHint(program: CliProgram): string {
|
|
|
42
42
|
return generatedFileHtmlComment(`${program.key} mcp bundle`);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/** Applies bundle hints to SKILL.md (after frontmatter) and reference.md. */
|
|
46
|
-
export function applySkillBundleHints(
|
|
47
|
-
program: CliProgram,
|
|
48
|
-
skillMd: string,
|
|
49
|
-
referenceMd: string,
|
|
50
|
-
): { skillMd: string; referenceMd: string } {
|
|
51
|
-
const hint = skillBundleHint(program);
|
|
52
|
-
return {
|
|
53
|
-
skillMd: insertGeneratedHint(skillMd, hint, { afterFrontmatter: true }),
|
|
54
|
-
referenceMd: insertGeneratedHint(referenceMd, hint),
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
45
|
/** Applies bundle hint to plugin SKILL.md (after frontmatter). */
|
|
59
46
|
export function applyPluginSkillHint(program: CliProgram, skillMd: string): string {
|
|
60
47
|
return insertGeneratedHint(skillMd, skillBundleHint(program), { afterFrontmatter: true });
|
package/src/skill/install.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This module installs agent skills to ~/.agents/skills/<key>/ per the dotagents protocol.
|
|
3
|
+
It writes skill.md and SKILL.md (compatibility copy) as an intent-based router.
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
7
|
import { join } from "node:path";
|
|
3
8
|
import type { CliProgram } from "../core/types.ts";
|
|
@@ -8,9 +13,13 @@ import { skillDirName } from "./naming.ts";
|
|
|
8
13
|
|
|
9
14
|
export { skillDirName } from "./naming.ts";
|
|
10
15
|
|
|
16
|
+
/** Options for agent skill installation. */
|
|
11
17
|
export interface SkillInstallOpts {
|
|
18
|
+
/** When true, installs to user home ~/.agents/skills/<key>/; otherwise project .agents/skills/<key>/. */
|
|
12
19
|
global?: boolean;
|
|
20
|
+
/** When true, removes existing directory before installing. */
|
|
13
21
|
rimraf?: boolean;
|
|
22
|
+
/** When true, computes file paths without writing to disk. */
|
|
14
23
|
dry?: boolean;
|
|
15
24
|
}
|
|
16
25
|
|
|
@@ -20,10 +29,10 @@ export function resolveAgentsSkillDir(root: CliProgram, global = true): string {
|
|
|
20
29
|
return join(base, ".agents", "skills", skillDirName(root.key));
|
|
21
30
|
}
|
|
22
31
|
|
|
23
|
-
/** Writes skill.md
|
|
32
|
+
/** Writes skill.md and SKILL.md (compatibility copy); returns changed file paths. */
|
|
24
33
|
export function cliSkillInstall(root: CliProgram, opts: SkillInstallOpts): string[] {
|
|
25
34
|
const bundle = generateSkillBundle(root);
|
|
26
|
-
const { skillMd
|
|
35
|
+
const { skillMd } = applySkillInstallHints(root, bundle.skillMd);
|
|
27
36
|
const dir = resolveAgentsSkillDir(root, opts.global ?? true);
|
|
28
37
|
const changed: string[] = [];
|
|
29
38
|
|
|
@@ -33,17 +42,19 @@ export function cliSkillInstall(root: CliProgram, opts: SkillInstallOpts): strin
|
|
|
33
42
|
|
|
34
43
|
const skillPath = join(dir, "skill.md");
|
|
35
44
|
const skillCompatPath = join(dir, "SKILL.md");
|
|
36
|
-
const refPath = join(dir, "reference.md");
|
|
37
45
|
|
|
38
46
|
if (!opts.dry) {
|
|
39
47
|
mkdirSync(dir, { recursive: true });
|
|
48
|
+
const legacyRef = join(dir, "reference.md");
|
|
49
|
+
if (existsSync(legacyRef)) {
|
|
50
|
+
rmSync(legacyRef, { force: true });
|
|
51
|
+
}
|
|
40
52
|
writeFileSync(skillPath, skillMd, "utf8");
|
|
41
53
|
writeFileSync(skillCompatPath, skillMd, "utf8");
|
|
42
|
-
writeFileSync(refPath, referenceMd, "utf8");
|
|
43
54
|
process.stdout.write(`Installed skill to ${displayHomePath(dir)}/\n`);
|
|
44
55
|
}
|
|
45
56
|
|
|
46
|
-
changed.push(skillPath, skillCompatPath
|
|
57
|
+
changed.push(skillPath, skillCompatPath);
|
|
47
58
|
return changed;
|
|
48
59
|
}
|
|
49
60
|
|