argsbarg 3.6.0 → 3.6.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 +8 -1
- package/package.json +10 -1
- package/.cursor/plans/cliprogram_capabilities_refactor_081e1737.plan.md +0 -224
- package/.cursor/plans/mcp_v1.1_polish_e9656029.plan.md +0 -260
- package/.cursor/plans/mcp_v1.2_invocation_and_extensions_a4f82c1e.plan.md +0 -647
- package/.cursor/plans/v1.3_parser_ergonomics_b3e91f02.plan.md +0 -455
- package/.cursor/rules/code.mdc +0 -9
- package/.github/copilot-instructions.md.md +0 -8
- package/.github/pull_request_template.md +0 -13
- package/.private/scratch.md +0 -4
- package/CLAUDE.md +0 -8
- package/biome.json +0 -40
- package/bun.lock +0 -43
- package/justfile +0 -54
- package/logo.png +0 -0
- package/plan.md +0 -19
- package/scripts/release.ts +0 -266
- package/tsconfig.json +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.6.1] - 2026-06-23
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **npm package** — `package.json` `files` whitelist so publish no longer ships `.cursor/`, `.private/`, `.github/`, or other dev-only paths (npm does not honor `.gitignore`).
|
|
15
|
+
|
|
10
16
|
## [3.6.0] - 2026-06-23
|
|
11
17
|
|
|
12
18
|
### Added
|
|
@@ -387,7 +393,8 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
|
387
393
|
- 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`).
|
|
388
394
|
- Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
|
|
389
395
|
|
|
390
|
-
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.6.
|
|
396
|
+
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.6.1...HEAD
|
|
397
|
+
[3.6.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.6.1
|
|
391
398
|
[3.6.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.6.0
|
|
392
399
|
[3.5.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.5.0
|
|
393
400
|
[3.4.2]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.4.2
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "argsbarg",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"bun": ">=1.3"
|
|
@@ -20,6 +20,15 @@
|
|
|
20
20
|
"default": "./src/index.ts"
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"index.d.ts",
|
|
26
|
+
"docs",
|
|
27
|
+
"examples",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"CHANGELOG.md"
|
|
31
|
+
],
|
|
23
32
|
"devDependencies": {
|
|
24
33
|
"@biomejs/biome": "^2.5.0",
|
|
25
34
|
"@types/bun": "^1.3.12",
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: CliProgram capabilities refactor
|
|
3
|
-
overview: Introduce `CliNode` / `CliProgram` types and an internal capabilities resolver, replacing `CliCommand` in the public API as a **2.0.0 breaking change**. Keep capability machinery private to avoid locking in unstable internals.
|
|
4
|
-
todos:
|
|
5
|
-
- id: types-split
|
|
6
|
-
content: "Refactor src/types.ts: CliNodeBase, CliLeaf, CliRouter, CliNode, CliProgram; remove CliCommand"
|
|
7
|
-
status: completed
|
|
8
|
-
- id: capabilities-module
|
|
9
|
-
content: Add internal src/capabilities.ts (resolveCapabilities, reservedCommandNames)
|
|
10
|
-
status: completed
|
|
11
|
-
- id: wire-validate-presentation
|
|
12
|
-
content: Update validate.ts, presentation.ts, export.ts, dispatch.ts, runtime.ts, invoke.ts to use CliProgram + caps
|
|
13
|
-
status: completed
|
|
14
|
-
- id: internal-walkers
|
|
15
|
-
content: Update parse, context, mcp/tools, completion scopes, install paths to CliNode/CliProgram as appropriate
|
|
16
|
-
status: completed
|
|
17
|
-
- id: public-api-2
|
|
18
|
-
content: Update index.ts exports (CliProgram only), run typegen, bump 2.0.0 CHANGELOG; verify qa-cli + idp-trees compile against local checkout
|
|
19
|
-
status: completed
|
|
20
|
-
- id: examples-docs
|
|
21
|
-
content: Migrate examples + README to CliProgram / satisfies pattern
|
|
22
|
-
status: completed
|
|
23
|
-
- id: tests-migrate
|
|
24
|
-
content: Migrate tests; keep runtime negative tests with cast helpers
|
|
25
|
-
status: completed
|
|
26
|
-
- id: nice-to-haves
|
|
27
|
-
content: "Optional: ctx.program getter, cliValidateProgram rename, satisfies examples, @ts-expect-error type tests"
|
|
28
|
-
status: completed
|
|
29
|
-
isProject: false
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
# CliProgram + internal capabilities (2.0)
|
|
33
|
-
|
|
34
|
-
## Goals
|
|
35
|
-
|
|
36
|
-
- **Types**: `CliNode` (user tree) + `CliProgram` (what `cliRun` accepts) with root-only `mcpServer` / `install` only on `CliProgram`.
|
|
37
|
-
- **Capabilities**: One internal resolver drives reserved names, help/schema/completion visibility, and dispatch guards.
|
|
38
|
-
- **Exports**: Minimal public surface — no `resolveCapabilities`, no presentation helpers, no builtin command builders.
|
|
39
|
-
- **Breaking**: Drop `CliCommand` from the public API — ship **2.0.0 directly** (Option B; no deprecated alias release).
|
|
40
|
-
|
|
41
|
-
## Type model
|
|
42
|
-
|
|
43
|
-
Add to [`src/types.ts`](src/types.ts):
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
interface CliNodeBase { key; description; notes?; options? }
|
|
47
|
-
|
|
48
|
-
type CliLeaf = CliNodeBase & { handler; positionals?; mcpTool? }
|
|
49
|
-
type CliRouter = CliNodeBase & { commands: CliNode[]; fallbackCommand?; fallbackMode? }
|
|
50
|
-
type CliNode = CliLeaf | CliRouter
|
|
51
|
-
|
|
52
|
-
type CliProgram = CliNode & { mcpServer?; install? }
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Remove `mcpServer`, `install`, `mcpTool` from a shared `CliCommandBase`. Remove the old `CliCommand` union entirely **inside the repo**.
|
|
56
|
-
|
|
57
|
-
**Leaf program roots** (`examples/minimal.ts`) remain valid: `CliProgram` = leaf + root config.
|
|
58
|
-
|
|
59
|
-
```mermaid
|
|
60
|
-
flowchart TB
|
|
61
|
-
subgraph public [Public API]
|
|
62
|
-
CliProgram
|
|
63
|
-
cliRun["cliRun(program)"]
|
|
64
|
-
cliInvoke["cliInvoke(program, argv)"]
|
|
65
|
-
end
|
|
66
|
-
subgraph internal [Internal only]
|
|
67
|
-
resolveCaps["resolveCapabilities(program)"]
|
|
68
|
-
presentation["presentationRoot(program)"]
|
|
69
|
-
dispatch["dispatchBuiltin(...)"]
|
|
70
|
-
end
|
|
71
|
-
CliProgram --> cliRun
|
|
72
|
-
CliProgram --> resolveCaps
|
|
73
|
-
resolveCaps --> presentation
|
|
74
|
-
resolveCaps --> dispatch
|
|
75
|
-
presentation --> help["help / schema / completions"]
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Export policy (intentionally narrow)
|
|
79
|
-
|
|
80
|
-
### Export from [`src/index.ts`](src/index.ts) only
|
|
81
|
-
|
|
82
|
-
| Symbol | Action |
|
|
83
|
-
|--------|--------|
|
|
84
|
-
| `CliProgram` | **Add** — primary schema type |
|
|
85
|
-
| `CliCommand` | **Remove** — breaking |
|
|
86
|
-
| `CliNode`, `CliLeaf`, `CliRouter` | **Do not export** — authors use `satisfies CliProgram` or `typeof program.commands[n]` |
|
|
87
|
-
| `CliOption`, `CliPositional`, config types | unchanged |
|
|
88
|
-
| `cliRun`, `cliInvoke`, `CliContext`, enums | unchanged signatures, `CliProgram` param |
|
|
89
|
-
|
|
90
|
-
Run `just typegen` so [`index.d.ts`](index.d.ts) reflects only the barrel.
|
|
91
|
-
|
|
92
|
-
### Keep internal (not in barrel, no deep `package.json` exports)
|
|
93
|
-
|
|
94
|
-
- [`src/capabilities.ts`](src/capabilities.ts) (new): `resolveCapabilities`, `reservedCommandNames`
|
|
95
|
-
- [`src/builtins/presentation.ts`](src/builtins/presentation.ts), [`export.ts`](src/builtins/export.ts), [`dispatch.ts`](src/builtins/dispatch.ts)
|
|
96
|
-
- Completion emitters, install module, `setCompiledExecutableOverride`
|
|
97
|
-
- [`src/completion.ts`](src/completion.ts) shim — trim re-exports if any leak toward public paths; tests import from `builtins/` or `completion.ts` directly in-repo only
|
|
98
|
-
|
|
99
|
-
**Rule**: If it decides *when* a builtin appears, it stays internal. Consumers only see the resulting CLI behavior.
|
|
100
|
-
|
|
101
|
-
## Internal capabilities module
|
|
102
|
-
|
|
103
|
-
New [`src/capabilities.ts`](src/capabilities.ts):
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
interface CliCapabilities {
|
|
107
|
-
completion: true;
|
|
108
|
-
mcp: boolean; // !!program.mcpServer
|
|
109
|
-
install: boolean; // isCompiledExecutable() && program.install?.enabled !== false
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function resolveCapabilities(program: CliProgram): CliCapabilities
|
|
113
|
-
function reservedCommandNames(caps: CliCapabilities): string[]
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Wire callers to use this instead of re-deriving:
|
|
117
|
-
|
|
118
|
-
| File | Change |
|
|
119
|
-
|------|--------|
|
|
120
|
-
| [`src/validate.ts`](src/validate.ts) | `cliValidateProgram(program)`; reserved names from `caps`; **keep** runtime root-only checks for untyped/JS abuse |
|
|
121
|
-
| [`src/builtins/presentation.ts`](src/builtins/presentation.ts) | `presentationBuiltins(program, caps)` |
|
|
122
|
-
| [`src/builtins/export.ts`](src/builtins/export.ts) | same |
|
|
123
|
-
| [`src/builtins/dispatch.ts`](src/builtins/dispatch.ts) | derive `caps` once from `program` |
|
|
124
|
-
| [`src/runtime.ts`](src/runtime.ts) | `cliRun(program: CliProgram)` |
|
|
125
|
-
|
|
126
|
-
Walkers (`parse`, `mcp/tools`, completion scopes) take `CliNode` where they recurse; entrypoints take `CliProgram`.
|
|
127
|
-
|
|
128
|
-
**Presentation vs user tree**: Help, `--schema`, and completion emitters consume `cliPresentationRoot(program)` (synthetic router with builtin stubs), not raw `CliProgram`. Capability logic decides what gets injected; emitters keep walking the same presentation shape.
|
|
129
|
-
|
|
130
|
-
**Validate edge cases** (keep at runtime even when TS catches most mistakes):
|
|
131
|
-
|
|
132
|
-
- `mcpServer` / `install` on inner nodes — reject (untyped/JS abuse)
|
|
133
|
-
- `mcpTool` on program root (leaf-shaped `CliProgram`) — reject (same as today)
|
|
134
|
-
- Reserved command names from `reservedCommandNames(caps)` — drop the old `cliPresentationRoot` escape hatch that skips injection when user already declared `completion`
|
|
135
|
-
|
|
136
|
-
## Context typing
|
|
137
|
-
|
|
138
|
-
[`src/context.ts`](src/context.ts):
|
|
139
|
-
|
|
140
|
-
- Change `ctx.schema` type to `CliProgram` (field name unchanged — avoids extra public surface).
|
|
141
|
-
- **Nice-to-have**: add `get program(): CliProgram` alias returning `this.schema` with JSDoc pointing to `schema` for familiarity. Do **not** export a new type for this.
|
|
142
|
-
|
|
143
|
-
## Consumer migration (2.0)
|
|
144
|
-
|
|
145
|
-
### Before/after (representative of qa-cli, idp-trees, and all `: CliCommand`-typed consumers)
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
// Before (1.x)
|
|
149
|
-
import { cliRun, type CliCommand, CliOptionKind, CliFallbackMode } from "argsbarg";
|
|
150
|
-
const cli: CliCommand = { key: 'myapp', commands: [...], mcpServer: {...} };
|
|
151
|
-
await cliRun(cli);
|
|
152
|
-
|
|
153
|
-
// After (2.0)
|
|
154
|
-
import { cliRun, type CliProgram, CliOptionKind, CliFallbackMode } from "argsbarg";
|
|
155
|
-
const cli = { key: 'myapp', commands: [...], mcpServer: {...} } satisfies CliProgram;
|
|
156
|
-
await cliRun(cli);
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
No structural change for well-formed apps — only import + type annotation. `: CliProgram` also works if the consumer prefers explicit annotation over `satisfies`. Both patterns are supported and type-check identically.
|
|
160
|
-
|
|
161
|
-
### Consumer impact (verified)
|
|
162
|
-
|
|
163
|
-
| Consumer | Files importing argsbarg | Uses `CliCommand`? | Uses `mcpServer`/`install`? | Migration cost |
|
|
164
|
-
|---|---|---|---|---|
|
|
165
|
-
| qa-cli | 3 (`index.tsx`, `mcpConfig.ts`, `mcp.ts`) | `const cli: CliCommand` | `mcpServer` on root | Replace `CliCommand` → `CliProgram` in 1 import, 1 annotation |
|
|
166
|
-
| idp-trees | 3 (`index.tsx`, `mcp/config.ts`, `headless/mode.ts`) | `const cli: CliCommand` | `mcpServer` on root | Replace `CliCommand` → `CliProgram` in 1 import, 1 annotation |
|
|
167
|
-
|
|
168
|
-
Both consumers only use `CliCommand` for the root schema annotation. Neither imports `CliNode`/`CliLeaf`/`CliRouter` (they can't — they don't exist yet). Neither has deeply nested command trees (max depth 2). All other imported types (`CliOptionKind`, `CliFallbackMode`, `CliMcpServerConfig`, `CliMcpToolConfig`, `CliInvocation`, `CliContext`, `isInteractiveTty`) remain unchanged — those files need zero changes.
|
|
169
|
-
|
|
170
|
-
### `install` builtin default behavior
|
|
171
|
-
|
|
172
|
-
Both qa-cli and idp-trees rely on the `install` builtin being **enabled by default** (they do not set `install` on the root). The capabilities resolver must preserve this: `install: isCompiledExecutable() && program.install?.enabled !== false` — which defaults to enabled when `install` is absent.
|
|
173
|
-
|
|
174
|
-
## Tests and negative cases
|
|
175
|
-
|
|
176
|
-
- Update fixtures in [`src/index.test.ts`](src/index.test.ts), [`src/builtins/builtins.test.ts`](src/builtins/builtins.test.ts), [`src/install/install.test.ts`](src/install/install.test.ts): `CliProgram` / `CliNode` internally.
|
|
177
|
-
- Runtime rejection tests (`mcpServer` on nested node) use `as unknown as CliProgram` or a small `invalidProgram()` helper — proves validate still catches misuse without TS.
|
|
178
|
-
|
|
179
|
-
## Docs and changelog
|
|
180
|
-
|
|
181
|
-
- [`README.md`](README.md): `CliProgram`, capabilities mental model (1 short paragraph), reserved names derived from config.
|
|
182
|
-
- [`CHANGELOG.md`](CHANGELOG.md): **2.0.0** — `CliCommand` removed; `CliProgram` added; show before/after migration snippet. Include a note that structural schema shape is unchanged — only the type name and annotation pattern differ.
|
|
183
|
-
- Optional short **Architecture** note in README (not a new doc file unless you want one).
|
|
184
|
-
|
|
185
|
-
### 2.0.0 migration snippet (for CHANGELOG)
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
// 1.x
|
|
189
|
-
import { type CliCommand } from "argsbarg";
|
|
190
|
-
const cli: CliCommand = { ... };
|
|
191
|
-
// 2.0
|
|
192
|
-
import { type CliProgram } from "argsbarg";
|
|
193
|
-
const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
## Version and release
|
|
197
|
-
|
|
198
|
-
**2.0.0** — rename-only breaking change for typed consumers; runtime behavior unchanged.
|
|
199
|
-
|
|
200
|
-
**Release path (chosen): Option B** — jump straight to 2.0.0. No `CliCommand` deprecated alias in 1.6. Breaking changes are acceptable; known consumers (qa-cli, idp-trees) migrate in one import + one annotation each.
|
|
201
|
-
|
|
202
|
-
### Pre-release checklist (required before tag)
|
|
203
|
-
|
|
204
|
-
1. `just test` green in argsbarg
|
|
205
|
-
2. `just typegen` — `index.d.ts` exports `CliProgram` only (no `CliCommand`)
|
|
206
|
-
3. Point qa-cli and idp-trees `package.json` at local argsbarg checkout; confirm `tsc` / build passes with `CliProgram` migration applied in those repos
|
|
207
|
-
4. CHANGELOG 2.0.0 entry with migration snippet
|
|
208
|
-
|
|
209
|
-
---
|
|
210
|
-
|
|
211
|
-
## Nice-to-haves (defer if time-boxed)
|
|
212
|
-
|
|
213
|
-
1. **`satisfies CliProgram`** in all examples (better DX than `: CliProgram` annotation). Verify that `satisfies` still infers precise sub-command types (not widening to `object`), particularly in nested structures like `examples/nested.ts`.
|
|
214
|
-
2. **`ctx.program` getter** — alias for `ctx.schema`; document `schema` as legacy name in JSDoc only (no removal in 2.0).
|
|
215
|
-
3. **Internal rename** `cliValidateRoot` → `cliValidateProgram` (not exported today; safe).
|
|
216
|
-
4. **Type tests** in `src/types.test.ts` — compile-only assertions that invalid shapes fail (e.g. `mcpServer` on `CliNode`) using `@ts-expect-error` snippets.
|
|
217
|
-
5. **README diagram** — small mermaid of user tree vs injected capabilities (documentation only).
|
|
218
|
-
|
|
219
|
-
## Explicitly out of scope (avoid future breaks)
|
|
220
|
-
|
|
221
|
-
- Exporting `CliCapabilities`, `resolveCapabilities`, `presentationRoot`, builtin command builders
|
|
222
|
-
- Exporting `CliNode` / `CliLeaf` / `CliRouter` (can add in 2.x if demand appears; not needed for `nested.ts`-style apps)
|
|
223
|
-
- `package.json` subpath exports (`argsbarg/builtins`, etc.)
|
|
224
|
-
- Renaming `ctx.schema` in 2.0 (would break handlers that read `.schema`)
|
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: MCP v1.1 polish
|
|
3
|
-
overview: "Ship four agent-focused MCP improvements: richer tool descriptions with CLI paths, per-leaf `mcpEnabled` opt-out, stderr surfaced on successful tool calls, and `structuredContent` when handler stdout is valid JSON."
|
|
4
|
-
todos:
|
|
5
|
-
- id: types-mcpEnabled
|
|
6
|
-
content: "Add mcpEnabled?: boolean to CliCommandBase; validate leaf-only in validate.ts"
|
|
7
|
-
status: completed
|
|
8
|
-
- id: tool-descriptions
|
|
9
|
-
content: Add mcpToolDescription(path, rootKey, description); enrich descriptions; filter mcpEnabled === false
|
|
10
|
-
status: completed
|
|
11
|
-
- id: result-builder
|
|
12
|
-
content: Create src/mcp/result.ts with buildToolCallSuccess (stderr blocks + JSON structuredContent)
|
|
13
|
-
status: completed
|
|
14
|
-
- id: server-wire
|
|
15
|
-
content: Wire buildToolCallSuccess into tools/call success path in server.ts
|
|
16
|
-
status: completed
|
|
17
|
-
- id: tests
|
|
18
|
-
content: Unit tests for description, mcpEnabled, result builder; subprocess tests for JSON structuredContent and stderr-on-success
|
|
19
|
-
status: completed
|
|
20
|
-
- id: docs-changelog
|
|
21
|
-
content: Update docs/mcp.md and CHANGELOG.md; run just typegen and just test
|
|
22
|
-
status: completed
|
|
23
|
-
isProject: false
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
# MCP follow-up improvements plan
|
|
27
|
-
|
|
28
|
-
Four targeted enhancements to the existing opt-in MCP server. No new dependencies, no protocol transport changes, no public export of internals beyond the new `mcpEnabled` schema field.
|
|
29
|
-
|
|
30
|
-
```mermaid
|
|
31
|
-
flowchart LR
|
|
32
|
-
subgraph toolsList [tools/list]
|
|
33
|
-
Walk[collectMcpTools]
|
|
34
|
-
Desc["path + description"]
|
|
35
|
-
Filter[mcpEnabled filter]
|
|
36
|
-
end
|
|
37
|
-
subgraph toolCall [tools/call]
|
|
38
|
-
Invoke[cliInvoke]
|
|
39
|
-
Build[buildToolCallResult]
|
|
40
|
-
Resp["content + structuredContent?"]
|
|
41
|
-
end
|
|
42
|
-
Walk --> Filter --> Desc
|
|
43
|
-
Invoke --> Build --> Resp
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## 1. CLI path in tool descriptions
|
|
49
|
-
|
|
50
|
-
**Goal:** Agents see the human invocation path without reading the schema resource first.
|
|
51
|
-
|
|
52
|
-
**Format:**
|
|
53
|
-
|
|
54
|
-
| Path | Leaf description | MCP `description` |
|
|
55
|
-
| --- | --- | --- |
|
|
56
|
-
| `["stat","owner","lookup"]` | `Resolve owner info.` | `stat owner lookup — Resolve owner info.` |
|
|
57
|
-
| `["read"]` | `Print the first line…` | `read — Print the first line…` |
|
|
58
|
-
| `[]` (root leaf app) | `Tiny demo.` | `{root.key} — Tiny demo.` |
|
|
59
|
-
|
|
60
|
-
Use em dash (`—`) between path and description. Path segments are raw `key` values (not sanitized tool names). Every tool description uses the `prefix — description` form — there is no bare-description fallback.
|
|
61
|
-
|
|
62
|
-
**Implementation:**
|
|
63
|
-
|
|
64
|
-
- Add helper in [`src/mcp/tools.ts`](src/mcp/tools.ts). The helper **must** receive `rootKey` so root-leaf apps (empty path) can use the program name as the prefix:
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
/** Builds MCP tool description: "{cli path} — {description}". */
|
|
68
|
-
function mcpToolDescription(path: string[], rootKey: string, description: string): string {
|
|
69
|
-
const prefix = path.length > 0 ? path.join(" ") : rootKey;
|
|
70
|
-
return `${prefix} — ${description}`;
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
- Call from `collectMcpTools` when building each `McpToolDef.description`:
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
description: mcpToolDescription(path, root.key, cmd.description),
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
- `tools/list` already maps `t.description` — no server change needed.
|
|
81
|
-
|
|
82
|
-
**Tests:** Update [`src/index.test.ts`](src/index.test.ts):
|
|
83
|
-
|
|
84
|
-
- `stat_owner_lookup` description is `stat owner lookup — Resolve owner info.`
|
|
85
|
-
- If testing a root-leaf fixture, description is `{root.key} — {description}`.
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## 2. Per-leaf opt-out via `mcpEnabled`
|
|
90
|
-
|
|
91
|
-
**Goal:** Authors can keep CLI commands for humans while hiding them from MCP tool discovery.
|
|
92
|
-
|
|
93
|
-
**Schema change** in [`src/types.ts`](src/types.ts):
|
|
94
|
-
|
|
95
|
-
```typescript
|
|
96
|
-
export interface CliCommandBase {
|
|
97
|
-
// ...existing fields...
|
|
98
|
-
/** Leaf-only. When `false`, omit this command from MCP tools (default: exposed). */
|
|
99
|
-
mcpEnabled?: boolean;
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
**Semantics:**
|
|
104
|
-
|
|
105
|
-
- Omitted or `true` → leaf is exposed (current behavior).
|
|
106
|
-
- `false` → skipped in `collectMcpTools` walk.
|
|
107
|
-
- Root `mcp?: CliMcpConfig` unchanged — it enables the server; `mcpEnabled` is unrelated.
|
|
108
|
-
|
|
109
|
-
**Validation** in [`src/validate.ts`](src/validate.ts) `walkCommand`:
|
|
110
|
-
|
|
111
|
-
- If `isRoot && cmd.mcpEnabled !== undefined` → throw `mcpEnabled is only supported on leaf commands`.
|
|
112
|
-
- If routing node (has `commands`, no `handler`) and `mcpEnabled !== undefined` → same error.
|
|
113
|
-
- No validation needed for `false` vs `true` beyond that.
|
|
114
|
-
|
|
115
|
-
**Tool collection** in [`src/mcp/tools.ts`](src/mcp/tools.ts) `walk`:
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
if ("handler" in cmd && cmd.handler) {
|
|
119
|
-
if (cmd.key === "completion" || cmd.key === "mcp") return;
|
|
120
|
-
if (cmd.mcpEnabled === false) return;
|
|
121
|
-
// push tool...
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
**Schema export:** Leave [`src/schema.ts`](src/schema.ts) unchanged for v1 — `--schema` still lists all commands (CLI discovery). Only MCP `tools/list` respects `mcpEnabled`. Document this distinction in [`docs/mcp.md`](docs/mcp.md).
|
|
126
|
-
|
|
127
|
-
**Example (optional):** Add a hidden leaf in `nestedMcpFixture` only (not `examples/nested.ts`) with `mcpEnabled: false` to prove filtering without cluttering the demo app.
|
|
128
|
-
|
|
129
|
-
---
|
|
130
|
-
|
|
131
|
-
## 3. stderr on successful tool calls
|
|
132
|
-
|
|
133
|
-
**Goal:** Warnings and diagnostic output on stderr are not silently dropped when `exitCode === 0`.
|
|
134
|
-
|
|
135
|
-
**Current behavior** ([`src/mcp/server.ts`](src/mcp/server.ts) ~L133–141): success returns only `invokeResult.stdout`.
|
|
136
|
-
|
|
137
|
-
**New behavior:** Extract a small builder (new file [`src/mcp/result.ts`](src/mcp/result.ts) recommended to keep server readable):
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
export interface McpToolCallSuccess {
|
|
141
|
-
content: { type: "text"; text: string }[];
|
|
142
|
-
structuredContent?: unknown;
|
|
143
|
-
isError: false;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function buildToolCallSuccess(stdout: string, stderr: string): McpToolCallSuccess
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
**stderr formatting rules:**
|
|
150
|
-
|
|
151
|
-
- If `stderr.trim()` is empty → single `content` item with stdout (or empty string if stdout empty).
|
|
152
|
-
- If stderr non-empty → **two** `content` items:
|
|
153
|
-
1. `{ type: "text", text: stdout }` (may be empty string)
|
|
154
|
-
2. `{ type: "text", text: stderr.trim() }` — **raw stderr text, no `stderr:` prefix**
|
|
155
|
-
|
|
156
|
-
Use multiple content blocks rather than concatenating into one string so hosts can distinguish streams; the second block’s position in the array is the signal that it came from stderr. Some MCP hosts render content blocks with their own labeling — a literal `stderr:` prefix would leak into user-visible tool output. Existing failure path (~L144–151) already prefers stderr — leave as-is.
|
|
157
|
-
|
|
158
|
-
**Tests:**
|
|
159
|
-
|
|
160
|
-
- Unit test `buildToolCallSuccess` with stdout-only, stderr-only, both.
|
|
161
|
-
- Subprocess test: add a fixture leaf (in test fixture or temporary nested command) that `console.warn`s on stderr but exits 0; assert two content blocks and `isError: false`.
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
## 4. `structuredContent` when stdout is valid JSON
|
|
166
|
-
|
|
167
|
-
**Goal:** When handlers emit JSON (e.g. `nested.ts` with `--json`), MCP clients get machine-readable output per [MCP tools spec](https://modelcontextprotocol.io/specification/draft/server/tools).
|
|
168
|
-
|
|
169
|
-
**Parsing rules** (in `buildToolCallSuccess`):
|
|
170
|
-
|
|
171
|
-
1. Let `trimmed = stdout.trim()`.
|
|
172
|
-
2. If `trimmed.length === 0` → no `structuredContent`.
|
|
173
|
-
3. Try `JSON.parse(trimmed)`.
|
|
174
|
-
4. On success → set `structuredContent` to the parsed value (object, array, or primitive — all valid per 2025 spec).
|
|
175
|
-
5. On `SyntaxError` → no `structuredContent` (plain text handlers unchanged).
|
|
176
|
-
6. **Always** keep `content[0].text` as the raw stdout string when stdout is non-empty (spec: structured tools SHOULD also return serialized JSON in `content` — we already have the raw stdout which satisfies this for JSON handlers).
|
|
177
|
-
|
|
178
|
-
**Primitive footgun (document, do not guard):** `JSON.parse("true")` yields `structuredContent: true`. A handler that intentionally prints the string `true` as human-readable output would get machine-typed output. This is spec-correct and rare in practice. Document in [`docs/mcp.md`](docs/mcp.md) rather than limiting to `typeof parsed === "object"` — that would reject valid JSON arrays and primitives that the 2025 spec explicitly allows.
|
|
179
|
-
|
|
180
|
-
**Do not** auto-generate `outputSchema` in this release — that requires schema author input or inference and is out of scope.
|
|
181
|
-
|
|
182
|
-
**Server wiring** in [`src/mcp/server.ts`](src/mcp/server.ts):
|
|
183
|
-
|
|
184
|
-
```typescript
|
|
185
|
-
const result = buildToolCallSuccess(invokeResult.stdout, invokeResult.stderr);
|
|
186
|
-
writeResponse({ jsonrpc: "2.0", id, result });
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
Spread `structuredContent` onto result only when defined.
|
|
190
|
-
|
|
191
|
-
**Tests:**
|
|
192
|
-
|
|
193
|
-
- Unit: `buildToolCallSuccess('{"a":1}\n', '')` → `structuredContent: { a: 1 }`, content text preserved.
|
|
194
|
-
- Unit: `buildToolCallSuccess('lookup user=x\n', '')` → no `structuredContent`.
|
|
195
|
-
- Unit: `buildToolCallSuccess('true\n', '')` → `structuredContent: true` (documents primitive behavior).
|
|
196
|
-
- Subprocess: extend existing `stat_owner_lookup` test or add parallel test with `json: true`:
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
params: {
|
|
200
|
-
name: "stat_owner_lookup",
|
|
201
|
-
arguments: { path: readme, "user-name": "test", json: true },
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Assert `result.structuredContent` deep-equals `{ user: "test", path: readme }` and `content[0].text` is valid JSON string.
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## Documentation and changelog
|
|
210
|
-
|
|
211
|
-
Update [`docs/mcp.md`](docs/mcp.md):
|
|
212
|
-
|
|
213
|
-
- **Tool names** section: document new description format with example (including root-leaf `{root.key} — …` case).
|
|
214
|
-
- **Per-leaf visibility**: `mcpEnabled: false` on leaves; note `--schema` still includes the command.
|
|
215
|
-
- **Tool results**: stderr on success as a second raw-text content block (no prefix); `structuredContent` when stdout is valid JSON (including primitives — document footgun); link to MCP spec.
|
|
216
|
-
- **Short flags**: one-line note that tool args use long option names only (unchanged, but good to document while editing).
|
|
217
|
-
|
|
218
|
-
Update [`CHANGELOG.md`](CHANGELOG.md) under `[Unreleased]`:
|
|
219
|
-
|
|
220
|
-
- Richer MCP tool descriptions with CLI paths.
|
|
221
|
-
- `mcpEnabled` leaf opt-out.
|
|
222
|
-
- MCP tool success responses include stderr when present.
|
|
223
|
-
- MCP tool success responses include `structuredContent` for JSON stdout.
|
|
224
|
-
|
|
225
|
-
Trim [`README.md`](README.md) MCP blurb only if needed — one line pointing to docs is enough.
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
|
|
229
|
-
## Type declarations and release hygiene
|
|
230
|
-
|
|
231
|
-
- Run `just typegen` after [`src/types.ts`](src/types.ts) change so [`index.d.ts`](index.d.ts) exports `mcpEnabled`.
|
|
232
|
-
- Run `just test` (typecheck + lint + `bun test`).
|
|
233
|
-
|
|
234
|
-
**Public API surface:** Only `mcpEnabled?: boolean` on `CliCommandBase` is new. `cliInvoke`, `buildToolCallSuccess`, and MCP runtime remain internal.
|
|
235
|
-
|
|
236
|
-
---
|
|
237
|
-
|
|
238
|
-
## File change summary
|
|
239
|
-
|
|
240
|
-
| File | Changes |
|
|
241
|
-
| --- | --- |
|
|
242
|
-
| [`src/types.ts`](src/types.ts) | Add `mcpEnabled?: boolean` with JSDoc |
|
|
243
|
-
| [`src/validate.ts`](src/validate.ts) | Reject `mcpEnabled` on root and routing nodes |
|
|
244
|
-
| [`src/mcp/tools.ts`](src/mcp/tools.ts) | `mcpToolDescription`, filter `mcpEnabled === false` |
|
|
245
|
-
| [`src/mcp/result.ts`](src/mcp/result.ts) | **New** — `buildToolCallSuccess` |
|
|
246
|
-
| [`src/mcp/server.ts`](src/mcp/server.ts) | Use builder for success path |
|
|
247
|
-
| [`src/index.test.ts`](src/index.test.ts) | Unit + subprocess tests for all four features |
|
|
248
|
-
| [`docs/mcp.md`](docs/mcp.md) | Document behavior |
|
|
249
|
-
| [`CHANGELOG.md`](CHANGELOG.md) | Unreleased entries |
|
|
250
|
-
| [`index.d.ts`](index.d.ts) | Regenerated via typegen |
|
|
251
|
-
|
|
252
|
-
---
|
|
253
|
-
|
|
254
|
-
## Out of scope (explicitly deferred)
|
|
255
|
-
|
|
256
|
-
- Tool list caching
|
|
257
|
-
- `outputSchema` on tools
|
|
258
|
-
- Hiding `mcpEnabled: false` commands from `--schema`
|
|
259
|
-
- Group-level `mcpEnabled` inheritance
|
|
260
|
-
- `mcp.json` / Cursor config changes
|