Skip to content

Commit a2eb7f4

Browse files
refactor(audiobook-curator): ship the CLI as <tool>.cli.ts projections of the curator tools (#734)
* refactor(audiobook-curator): ship the CLI as <tool>.cli.ts projections of the curator tools (#725) * test(workbench): projected commands hang off their tool leaves * chore: changeset PR number * deslop: one-line schema note, annotate relaxed --report/--receipt * examples(audiobook-curator): drop orphaned operation inputSchemas * examples(audiobook-curator): schema judges regions after the split; drop dead imports; explicit confirm * examples(audiobook-curator): drop orphaned JSDoc
1 parent a2a3fbe commit a2eb7f4

73 files changed

Lines changed: 762 additions & 1107 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"agent-bundle": patch
3+
---
4+
5+
Judge a projected command's `z.enum` choices with the canonical schema after `mapInput` runs when the `<tool>.cli.ts` projection exports `mapInput`, so a mapper can split a comma-separated enum list (`--regions us,uk`); a value outside the enum still exits 2 as `Invalid value for --<option>[<index>]`. The `audiobook-curator` example now ships its sixteen commands as `<tool>.cli.ts` projections of its sixteen curator tools instead of a parallel `src/cli/` tree and the bulk `routes.mcpCommands` group. (#734)

‎examples/audiobook-curator/README.md‎

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ and `@agent-bundle/runtime` exports with `workspace:*` dependencies.
5252

5353
### The route tree is the application
5454

55-
`agent-bundle.config.ts` declares the plugin identity, Node runtime, Claude and
56-
Codex targets, and MCP-to-CLI projection. File conventions discover the rest.
55+
`agent-bundle.config.ts` declares the plugin identity, Node runtime, and Claude
56+
and Codex targets. File conventions discover the rest.
5757
The MCP tree under `src/mcp/curator/` contains 16 tool routes, one catalog
5858
resource, and one curation prompt. Each executable route exports static
5959
`config`, `inputSchema`, and `resultSchema` values plus an async default Server
@@ -62,17 +62,23 @@ Flight worker, and MCP registrations; there is no `src/application.ts`,
6262
operation-array registry, handwritten `src/mcp/curator.ts`, or per-operation
6363
server selector.
6464

65-
The routed CLI under `src/cli/` contains 16 authored commands. The
66-
`routes.mcpCommands` setting projects all 16 MCP tools as
67-
`audiobook-curator curator <tool>`, giving the compiled graph 32 CLI commands.
68-
Projected tools accept one optional `--input '<JSON object>'`; tools annotated
69-
read-only run directly, while mutation-capable tools require `--yes`.
65+
The CLI is the same 16 tools. Each tool has a colocated `<tool>.cli.ts`
66+
projection module (#725) that declares its command name, option spellings,
67+
positionals, and confirmation policy — `search_audible.cli.ts` spells the tool
68+
as `audible-search` with `--duration` and a comma-separated or repeatable
69+
`--regions`, `prepare_audiobook.cli.ts` spells `outputRoot` as `--output`, and
70+
`inspect_sources.cli.ts` takes `<root>` as a positional — so the compiled graph
71+
carries exactly 16 CLI commands, each with the tool's identity
72+
(`tool:curator/<tool>`). There is no `src/cli/` directory, no second input
73+
schema or render body per command, and no `audiobook-curator curator <tool>`
74+
twin: one operation is one command. Every projection declares `confirm: false`;
75+
the plan-first commands gate their mutation on `--apply`, never on `--yes`.
7076

7177
### `src/layout.tsx` is the shared document shell
7278

7379
The conventional layout module wraps every rendered route once — the 16 MCP
74-
tools, the catalog resource, the curate prompt, the rendered CLI commands, and
75-
the projected `curator <tool>` commands — so no route imports a wrapper to get
80+
tools (on both surfaces), the catalog resource, and the curate prompt — so no
81+
route imports a wrapper to get
7682
the server's standard document structure. The layout renders a container
7783
`Agent.Result` and the runtime merges each route's own
7884
`<Agent.Result value={receipt}>` into it: the structured receipt, the MCP
@@ -95,21 +101,22 @@ export default async function Route({ input, signal }: ToolRouteProps<typeof inp
95101

96102
### `src/components/` is the shared presentation library
97103

98-
The route modules perform domain work and compose these report components
99-
instead of maintaining separate MCP and CLI presenters:
100-
101-
| Component | MCP composition | Rendered authored CLI composition |
102-
| --- | --- | --- |
103-
| `DataList`, `Field`, `Callout`, and `FileList` | Provide atomic report fields, prose callouts, and file-list blocks throughout the component library and directly in the catalog resource, curate prompt, cache route, and library audit | Provide the same primitives through the shared components and directly in `library-audit` |
104-
| `FileCard` and `EditionCard`, fed by `view-models` | Render file and edition models in `audit_library`, shelf, and ranking views | Reached through the receipt-specific shelves and ranking components |
105-
| `InspectionShelf`, `InventoryShelf`, and `SelectionShelf` | Compose receipt-specific inspection, inventory, and selection reports, each used directly by its MCP route; `audit_library` composes `AuditSummary` and `AuditFileCards` alongside the asynchronous `LibraryAnalysis` instead of a shelf | `InventoryShelf` and `SelectionShelf` compose `inventory` and `select` |
106-
| `SearchRanking`, `IdentifyRanking`, and `SelectionRanking` | Render the statically typed ranking for `search_audible`, `identify_audible_sample`, and `select_audible_edition` | `SearchRanking` composes `audible-search` |
107-
| `AcousticTrail`, `IdentifyTrail`, and `WhisperTrail` | Render the statically typed evidence for acoustic verification, acoustic identification, and Whisper verification | No authored rendered counterpart; those compatibility commands remain plain `.ts` routes |
108-
| `MetadataMutation`, `ChapterMutation`, `ConversionMutation`, and `PrepareMutation` | Render each statically typed metadata, chapter, conversion, or preparation mutation | `ConversionMutation` composes `convert` |
109-
| `ChapterOutline` with normalized `chapters` props | Composes integrity audit, conversion, and chapter application tools through receipt-specific mappers | Composes `audit` and `convert` |
110-
| `IntegrityAuditReport`, `MetadataIntegrityReport`, `ChapterIntegrityReport`, and `ConversionIntegrityReport` | Render each statically typed integrity report | `IntegrityAuditReport` and `ConversionIntegrityReport` compose `audit` and `convert` |
111-
| `CurationShelf` | Composes shelf review, Audible edition selection, and metadata/chapter application | Composes `shelf` |
112-
| `LibraryAnalysis` and `CandidateGroupCallout` | Resolve asynchronous duplicate and multipart analysis in `audit_library` with shared atomic candidate prose and file lists | Resolve the same analysis and candidate-group presentation in `library-audit` |
104+
The tool modules perform domain work and compose these report components once;
105+
the same rendered document serves the MCP client as text content and the
106+
command line as piped Markdown, so there is no second presenter to drift:
107+
108+
| Component | Composition |
109+
| --- | --- |
110+
| `DataList`, `Field`, `Callout`, and `FileList` | Atomic report fields, prose callouts, and file-list blocks throughout the component library and directly in the catalog resource, curate prompt, and cache route |
111+
| `FileCard` and `EditionCard`, fed by `view-models` | File and edition models in `audit_library`, shelf, and ranking views |
112+
| `InspectionShelf`, `InventoryShelf`, and `SelectionShelf` | Receipt-specific inspection, inventory, and selection reports for `inspect_sources`, `inventory_sources`, and `select_sources`; `audit_library` composes `AuditSummary` and `AuditFileCards` alongside the asynchronous `LibraryAnalysis` instead of a shelf |
113+
| `SearchRanking`, `IdentifyRanking`, and `SelectionRanking` | The statically typed ranking for `search_audible`, `identify_audible_sample`, and `select_audible_edition` |
114+
| `AcousticTrail`, `IdentifyTrail`, and `WhisperTrail` | The statically typed evidence for `verify_audible_sample`, `identify_audible_sample`, and `verify_with_whisper` |
115+
| `MetadataMutation`, `ChapterMutation`, `ConversionMutation`, and `PrepareMutation` | Each statically typed metadata, chapter, conversion, or preparation mutation |
116+
| `ChapterOutline` with normalized `chapters` props | Integrity audit, conversion, and chapter application through receipt-specific mappers |
117+
| `IntegrityAuditReport`, `MetadataIntegrityReport`, `ChapterIntegrityReport`, and `ConversionIntegrityReport` | Each statically typed integrity report |
118+
| `CurationShelf` | Shelf review, Audible edition selection, and metadata/chapter application |
119+
| `LibraryAnalysis` and `CandidateGroupCallout` | Asynchronous duplicate and multipart analysis in `audit_library` with shared atomic candidate prose and file lists |
113120

114121
The catalog resource at `src/mcp/curator/resources/catalog.tsx` and the prompt at
115122
`src/mcp/curator/prompts/curate.tsx` are compositions too: both return their
@@ -133,9 +140,9 @@ The conventional state module defines the workspace-durable
133140
`mutationApplied`, and `shelfCleared`. `select_audible_edition` dispatches the
134141
selection event; `apply_audiobook_metadata` and `apply_audiobook_chapters`
135142
dispatch mutation records and render the updated shelf. The read-only
136-
`review_curation_shelf` MCP tool and rendered `shelf` CLI command expose the
137-
same mounted state. If state is not mounted, both surfaces return an empty
138-
structured shelf and render an explicit unavailable notice.
143+
`review_curation_shelf` tool — `shelf` on the command line — exposes the
144+
mounted state. If state is not mounted, it returns an empty structured shelf
145+
and renders an explicit unavailable notice on both surfaces.
139146

140147
### Suspense becomes MCP progress
141148

@@ -146,27 +153,25 @@ node — and that node is the whole progress story: the generated MCP projector
146153
turns the streamed fallback into `notifications/progress` for a client that
147154
sent a progress token, then replaces it with the completed analysis without
148155
changing the final structured `LibraryAuditReceipt`. No `progress.report()`
149-
call repeats the fallback's message. The rendered `library-audit` CLI route
150-
composes the same analysis and fallback.
151-
152-
### CLI routes have rendered and plain modes
153-
154-
Seven authored `.tsx` commands render Agent Documents:
155-
`inventory`, `select`, `audible-search`, `convert`, `audit`, `library-audit`,
156-
and `shelf`. Interactive terminals can update reported progress in place;
157-
piped output is one final Markdown document. Nine compatibility commands remain
158-
plain `.ts` routes: `acoustic-identify`, `acoustic-verify`, `apply-chapters`,
159-
`apply-metadata`, `audible-cache`, `audible-select`, `inspect`, `prepare`, and
160-
`whisper-verify`.
161-
162-
The 16 projected MCP commands render the same tool components as their MCP
163-
counterparts. Across plain and rendered commands, `--json` selects machine
164-
output and emits one result-schema-validated JSON value followed by a newline.
165-
For rendered commands that value is the canonical final `Agent.Result` value,
166-
not the Markdown presentation or an intermediate Suspense fallback, so existing
167-
receipt consumers do not change when a command becomes rendered.
168-
169-
`src/operations/` owns shared operation handlers and schemas;
156+
call repeats the fallback's message. On an interactive terminal the
157+
`library-audit` command draws the same fallback in place.
158+
159+
### Every command renders its tool's document
160+
161+
Each command runs its tool's component: piped output is one final Markdown
162+
document — the same headline and report the MCP client receives as text
163+
content — and an interactive terminal draws the streamed `Agent.Progress`
164+
fallback in place. `--json` selects machine output and emits one
165+
result-schema-validated JSON value followed by a newline: the canonical final
166+
`Agent.Result` value, never the Markdown presentation or an intermediate
167+
Suspense fallback, and byte for byte the `structuredContent` of the tool call.
168+
`--report` and `--receipt` are optional on the command line exactly as they are
169+
on the tool; a command that gets one still writes the receipt file.
170+
171+
Each tool module declares its `inputSchema` as an inline zod literal, because
172+
the argv projection is compiled statically from that literal; it is the only
173+
input schema. `src/operations/` keeps each operation's handler and result
174+
schema;
170175
`src/cli-command.ts` names the `{ signal }` context every handler receives. Domain logic
171176
remains in `src/` over `foundation.ts` and `media-process.ts`, while
172177
`src/index.ts` remains the package library entry.
@@ -229,7 +234,8 @@ The completion contract and real-volume checklist are in
229234

230235
This example is the reference consumer of the framework-owned package build:
231236
one `agent-bundle.config.ts` declares the structure, conventional
232-
`src/skills/**`, `src/mcp/**`, `src/cli/**`, `src/providers/**`, and
233-
`src/state.ts` modules supply the application surfaces, and agent-bundle owns
237+
`src/skills/**`, `src/mcp/**` (tool routes and their `<tool>.cli.ts`
238+
projections), `src/providers/**`, and `src/state.ts` modules supply the
239+
application surfaces, and agent-bundle owns
234240
the generated package and host artifacts. See
235241
[`docs/entry-conventions.md`](../../docs/entry-conventions.md) for the contract.

‎examples/audiobook-curator/agent-bundle.config.ts‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ export default defineConfig({
1414
// status, and the `agent-bundle/meta` constant this plugin imports.
1515
},
1616
runtime: { node: '22.19.0' },
17-
// #102 stage 4 adopts the in-house G7 projection for every curator tool.
18-
routes: { mcpCommands: true },
19-
// No `scripts` or `bin` fields needed: the routed `src/cli/` commands
20-
// compile into the shared artifact/package executable (bin/audiobook-curator.mjs) by
21-
// convention (#102 stages 2-3).
17+
// No `routes`, `scripts`, or `bin` fields needed: every curator tool's
18+
// colocated `<tool>.cli.ts` projection compiles into the shared
19+
// artifact/package executable (bin/audiobook-curator.mjs) by convention,
20+
// so one operation is one command (#725).
2221
// No `skills` field needed: `src/skills/curate-audiobooks/SKILL.md` is
2322
// discovered by convention.
2423
targets: ['claude', 'codex'],

‎examples/audiobook-curator/src/cli-command.ts‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
/**
2-
* The context every `src/operations/*.ts` handler receives from the generated
3-
* MCP routes and the routed `src/cli/` commands. The manual CLI projection
4-
* (`cli.parse`/`usage`/`exitCode`) and its `runCliCommands` dispatcher were
5-
* retired by the #102 stage-3 migration — the framework compiles `src/cli/**`
6-
* routes into the executable instead.
2+
* The context every `src/operations/*.ts` handler receives from the curator
3+
* tool routes, whichever surface — MCP or the tools' `<tool>.cli.ts`
4+
* projections — invoked them.
75
*/
86

97
export interface CliCommandContext {

‎examples/audiobook-curator/src/cli/acoustic-identify.ts‎

Lines changed: 0 additions & 28 deletions
This file was deleted.

‎examples/audiobook-curator/src/cli/acoustic-verify.ts‎

Lines changed: 0 additions & 29 deletions
This file was deleted.

‎examples/audiobook-curator/src/cli/apply-chapters.ts‎

Lines changed: 0 additions & 23 deletions
This file was deleted.

‎examples/audiobook-curator/src/cli/apply-metadata.ts‎

Lines changed: 0 additions & 29 deletions
This file was deleted.

‎examples/audiobook-curator/src/cli/audible-cache.ts‎

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)