argsbarg 3.6.4 → 4.0.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 +32 -2
- package/README.md +21 -9
- package/docs/README.md +12 -8
- package/docs/bundled-docs.md +1 -1
- package/docs/cli-program.md +62 -2
- package/docs/config-schema.md +192 -0
- package/docs/developing.md +13 -0
- package/docs/install.md +38 -1
- package/docs/mcp.md +43 -19
- package/docs/output-schema.md +74 -52
- package/docs/templates/cursor/rules/cli-program.mdc +10 -5
- package/examples/config-app/main.ts +20 -0
- package/examples/config-app/program.ts +81 -0
- package/examples/config-app/schema.ts +37 -0
- package/examples/config-app/types.ts +19 -0
- package/examples/consumer-app/README.md +56 -0
- package/examples/consumer-app/bun.lock +75 -0
- package/examples/consumer-app/capabilities.test.ts +69 -0
- package/examples/consumer-app/package.json +17 -0
- package/examples/consumer-app/schemas/configSchemas.ts +6 -0
- package/examples/consumer-app/schemas/generated/app-config.json +40 -0
- package/examples/consumer-app/schemas/generated/status.json +28 -0
- package/examples/consumer-app/schemas/outputSchemas.ts +6 -0
- package/examples/consumer-app/scripts/schemagen/discover-schema-roots.test.ts +25 -0
- package/examples/consumer-app/scripts/schemagen/discover-schema-roots.ts +93 -0
- package/examples/consumer-app/scripts/schemagen/naming.ts +82 -0
- package/examples/consumer-app/scripts/schemagen.ts +76 -0
- package/examples/consumer-app/src/commands/status/types.ts +11 -0
- package/examples/consumer-app/src/main.ts +15 -0
- package/examples/consumer-app/src/program.ts +116 -0
- package/examples/consumer-app/src/types.ts +23 -0
- package/examples/consumer-app/tsconfig.json +14 -0
- package/examples/formats.ts +10 -3
- package/examples/mcp-test.ts +27 -8
- package/examples/minimal.ts +4 -3
- package/examples/nested.ts +5 -4
- package/examples/option-required.ts +8 -4
- package/index.d.ts +158 -75
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +13 -0
- package/src/builtins/config.test.ts +82 -0
- package/src/builtins/config.ts +220 -0
- package/src/builtins/dispatch.ts +18 -9
- package/src/builtins/export.ts +8 -33
- package/src/builtins/index.ts +1 -0
- package/src/builtins/install.ts +13 -0
- package/src/builtins/mcp.ts +1 -1
- package/src/builtins/presentation.ts +2 -17
- package/src/builtins/registry.ts +40 -0
- package/src/capabilities.ts +46 -0
- package/src/cli-errors.ts +15 -0
- package/src/cli.ts +389 -0
- package/src/config/bootstrap.ts +265 -0
- package/src/config/context.test.ts +79 -0
- package/src/config/context.ts +110 -0
- package/src/config/entry.ts +81 -0
- package/src/config/file.test.ts +112 -0
- package/src/config/file.ts +120 -0
- package/src/config/manifest.ts +62 -0
- package/src/config/resolve.test.ts +88 -0
- package/src/config/resolve.ts +167 -0
- package/src/config/schema.ts +101 -0
- package/src/config/validate.test.ts +63 -0
- package/src/config/validate.ts +292 -0
- package/src/config.integration.test.ts +100 -0
- package/src/context.ts +5 -0
- package/src/docs/docs.test.ts +16 -16
- package/src/docs/mcp-guide.ts +35 -11
- package/src/hidden-mcpb.test.ts +40 -2
- package/src/index.ts +4 -3
- package/src/install/index.ts +46 -3
- package/src/install/paths.ts +5 -20
- package/src/install/plan.ts +6 -0
- package/src/install/status.ts +12 -0
- package/src/install/uninstall.ts +11 -0
- package/src/install/update.test.ts +5 -5
- package/src/invoke.test.ts +207 -0
- package/src/mcp/bundle.ts +9 -116
- package/src/mcp/claude.test.ts +73 -0
- package/src/mcp/claude.ts +168 -0
- package/src/mcp/env.ts +3 -37
- package/src/mcp/server.ts +18 -10
- package/src/mcp/tools.ts +3 -7
- package/src/mcp/zip.ts +82 -0
- package/src/mcp.integration.test.ts +502 -0
- package/src/{index.test.ts → parse.test.ts} +24 -935
- package/src/paths/host.ts +40 -0
- package/src/schema.ts +1 -1
- package/src/skill/generate.ts +18 -5
- package/src/skill/hint.ts +18 -0
- package/src/skill/install.ts +1 -5
- package/src/test-fixtures.ts +192 -0
- package/src/types.ts +39 -13
- package/src/validate.ts +70 -0
- package/src/completion.ts +0 -13
- package/src/invoke.ts +0 -217
- package/src/mcp.ts +0 -28
- package/src/runtime.ts +0 -134
package/docs/mcp.md
CHANGED
|
@@ -125,14 +125,13 @@ Any host that spawns a subprocess and wires stdin/stdout works the same way: the
|
|
|
125
125
|
|
|
126
126
|
## Configuration
|
|
127
127
|
|
|
128
|
-
Set `mcpServer` on the **program root only** (the `CliProgram` passed to `
|
|
128
|
+
Set `mcpServer` on the **program root only** (the `CliProgram` passed to `new Cli(program)`). Validation rejects `mcpServer` on nested nodes.
|
|
129
129
|
|
|
130
130
|
| Field | Default | Purpose |
|
|
131
131
|
| --- | --- | --- |
|
|
132
132
|
| `enabled` | *(required)* | Must be `true` when `mcpServer` is set |
|
|
133
133
|
| `schemaResourceUri` | `<sanitized root key>://schema` | URI for the built-in schema resource |
|
|
134
134
|
| `shellEnv` | off | Capture login-shell `env` at startup (`true` uses `$SHELL`, or pass a shell path) |
|
|
135
|
-
| `envFile` | off | Load a `.env` file after `shellEnv` (`~` supported); warns on stderr if missing |
|
|
136
135
|
| `resources` | `[]` | Custom `CliMcpResource` entries (additive; schema resource is always included) |
|
|
137
136
|
|
|
138
137
|
MCP `serverInfo.name` and the default schema URI use the sanitized program `key` (non-alphanumeric characters become `_`). Program `version` comes from `CliProgram.version` (also used by the `version` built-in).
|
|
@@ -143,7 +142,6 @@ Example with optional fields:
|
|
|
143
142
|
mcpServer: {
|
|
144
143
|
enabled: true,
|
|
145
144
|
shellEnv: true,
|
|
146
|
-
envFile: "~/.config/myapp/mcp.env",
|
|
147
145
|
}
|
|
148
146
|
```
|
|
149
147
|
|
|
@@ -163,7 +161,7 @@ Tool names are derived from the command path, with each segment sanitized (non-a
|
|
|
163
161
|
|
|
164
162
|
### Tool descriptions
|
|
165
163
|
|
|
166
|
-
Each tool’s `description` includes the human CLI path and the leaf’s help text, separated by an em dash. Leaf **`notes`** are appended after a blank line (with `{argsbarg:program}` resolved). Tool arguments are defined in `inputSchema` (options and positionals with their descriptions).
|
|
164
|
+
Each tool’s `description` includes the human CLI path and the leaf’s help text, separated by an em dash. Leaf **`notes`** are appended after a blank line (with `{argsbarg:program}` resolved). Tool arguments are defined in `inputSchema` (options and positionals with their descriptions).
|
|
167
165
|
|
|
168
166
|
| CLI path | MCP `description` (example) |
|
|
169
167
|
| --- | --- |
|
|
@@ -194,14 +192,12 @@ Omitted or `enabled: true` exposes the command (default). `mcpTool` is only vali
|
|
|
194
192
|
mcpTool: {
|
|
195
193
|
enabled: true,
|
|
196
194
|
description: "Custom tools/list text (overrides auto-generated path + help).",
|
|
197
|
-
requiresEnv: ["API_TOKEN", "DATABASE_URL"],
|
|
198
195
|
}
|
|
199
196
|
```
|
|
200
197
|
|
|
201
198
|
Set **`outputSchema` on the leaf** (not under `mcpTool`) — see [cli-program.md — Structured stdout](cli-program.md#structured-stdout).
|
|
202
199
|
|
|
203
|
-
- **`description`** — when set, replaces the auto-generated `path — help` description entirely
|
|
204
|
-
- **`requiresEnv`** — on auto-generated descriptions, appended as `[requires env: …]`. Enforced at `tools/call` time before the handler runs. Empty or unset env values count as missing.
|
|
200
|
+
- **`description`** — when set, replaces the auto-generated `path — help` description entirely.
|
|
205
201
|
|
|
206
202
|
### Tool arguments
|
|
207
203
|
|
|
@@ -269,7 +265,7 @@ URIs must be unique and must not equal `schemaResourceUri`. `load()` runs synchr
|
|
|
269
265
|
|
|
270
266
|
## Invocation context
|
|
271
267
|
|
|
272
|
-
Handlers receive `ctx.invocation`: `"cli"` for normal `
|
|
268
|
+
Handlers receive `ctx.invocation`: `"cli"` for normal `Cli.run()` dispatch, `"mcp"` for MCP `tools/call`.
|
|
273
269
|
|
|
274
270
|
MCP is always non-interactive. Commands that can mount Ink or prompts should implement a **headless fast path** (same path as non-TTY CLI with `--yes` / `--json`) — see [cli-program.md — Headless-capable handlers](cli-program.md#headless-capable-handlers).
|
|
275
271
|
|
|
@@ -287,9 +283,9 @@ handler: async (ctx) => {
|
|
|
287
283
|
|
|
288
284
|
`Bun.spawn({ stdout: "inherit" })` under MCP corrupts the wire. Prefer `"pipe"` and let argsbarg return captured handler stdout in the tool result.
|
|
289
285
|
|
|
290
|
-
### `
|
|
286
|
+
### `Cli.invoke` (public API)
|
|
291
287
|
|
|
292
|
-
`
|
|
288
|
+
`new Cli(root).invoke(argv)` runs a leaf handler without exiting the process — useful for tests and headless integrations. Returns `{ kind, exitCode, stdout, stderr }`. MCP tool dispatch uses this internally.
|
|
293
289
|
|
|
294
290
|
**Note:** Tool output is buffered until the handler completes. Live streaming (e.g. `tail -f`) is not supported yet; see [Design notes](#design-notes).
|
|
295
291
|
|
|
@@ -297,12 +293,12 @@ handler: async (ctx) => {
|
|
|
297
293
|
|
|
298
294
|
MCP hosts (e.g. Cursor) often spawn your server with a minimal environment — missing `PATH` entries for Homebrew, nvm, rbenv, etc.
|
|
299
295
|
|
|
300
|
-
At server start (`
|
|
296
|
+
At server start (`Cli.serveMcp()`), before the NDJSON loop:
|
|
301
297
|
|
|
302
298
|
| Order | Source | Behavior |
|
|
303
299
|
| --- | --- | --- |
|
|
304
300
|
| 1 | `shellEnv` | Spawns `$SHELL -l -c env`; merges into `process.env` |
|
|
305
|
-
| 2 |
|
|
301
|
+
| 2 | App config file | Loads `program.appConfig` keys from flat JSON when unset in host env |
|
|
306
302
|
|
|
307
303
|
**`shellEnv` merge rules:**
|
|
308
304
|
|
|
@@ -310,18 +306,26 @@ At server start (`cliMcpServeStdio`), before the NDJSON loop:
|
|
|
310
306
|
- **Other vars** — set only when absent from the host environment (host wins).
|
|
311
307
|
- On failure — one-line warning on **stderr**; server continues.
|
|
312
308
|
|
|
313
|
-
|
|
309
|
+
**App config (`program.appConfig`):**
|
|
314
310
|
|
|
315
|
-
-
|
|
316
|
-
-
|
|
317
|
-
-
|
|
311
|
+
- Default path: `$XDG_CONFIG_HOME/<sanitized-key>/config` (Linux/macOS) or `%APPDATA%/<key>/config` (Windows). Override with `config.path`.
|
|
312
|
+
- JSON shape: flat object keyed by schema names — `{ "apiToken": "…" }`. Unknown keys rejected on load.
|
|
313
|
+
- Loaded at MCP startup; host `process.env` wins for mapped env vars already set.
|
|
314
|
+
- Missing required config does **not** exit the MCP server — enforced at `tools/call` with a helpful error.
|
|
315
|
+
- Configure interactively: `myapp install --configure` (see [install.md](install.md)).
|
|
316
|
+
- Built-in `config get` / `config set` when `program.appConfig.commands` is enabled (default). Hosts inject `user_config` → env at spawn; they never write the argsbarg config file.
|
|
318
317
|
|
|
319
318
|
Example:
|
|
320
319
|
|
|
321
320
|
```typescript
|
|
321
|
+
appConfig: {
|
|
322
|
+
entries: {
|
|
323
|
+
apiToken: { description: "Create at https://example.com/settings/tokens", env: "API_TOKEN", sensitive: true },
|
|
324
|
+
},
|
|
325
|
+
},
|
|
322
326
|
mcpServer: {
|
|
327
|
+
enabled: true,
|
|
323
328
|
shellEnv: true,
|
|
324
|
-
envFile: "~/.config/myapp/mcp.env",
|
|
325
329
|
},
|
|
326
330
|
```
|
|
327
331
|
|
|
@@ -355,15 +359,35 @@ You should get one JSON line on stdout with `result.capabilities` and `result.se
|
|
|
355
359
|
|
|
356
360
|
## MCP Bundle (`mcp bundle`)
|
|
357
361
|
|
|
358
|
-
When `mcpServer.enabled` is true, **`mcp bundle`**
|
|
362
|
+
When `mcpServer.enabled` is true, **`mcp bundle`** writes two distribution artifacts:
|
|
359
363
|
|
|
360
364
|
```bash
|
|
361
365
|
just build
|
|
362
366
|
./dist/myapp mcp bundle
|
|
363
367
|
# → dist/myapp.mcpb
|
|
368
|
+
# → dist/claude-plugin/myapp.zip
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Expects the compiled binary at **`dist/<program.key>`**.
|
|
372
|
+
|
|
373
|
+
| Output | Purpose |
|
|
374
|
+
| --- | --- |
|
|
375
|
+
| **`dist/<key>.mcpb`** | Claude Desktop MCP Bundle (`.mcpb` zip) |
|
|
376
|
+
| **`dist/claude-plugin/<name>.zip`** | Claude Code plugin zip (`<name>` is kebab-case from `program.key`) |
|
|
377
|
+
|
|
378
|
+
Manifest metadata is generated from your schema (`mcpServerId`, tools, `program.appConfig` user config for env-mapped entries). Optional pack-time fields live under **`mcpServer.bundle`** (`author`, `icon`, `longDescription`).
|
|
379
|
+
|
|
380
|
+
**Claude Code plugin zip layout** (paths at archive root):
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
.claude-plugin/plugin.json
|
|
384
|
+
.mcp.json
|
|
385
|
+
bin/myapp
|
|
386
|
+
skills/<dirName>/SKILL.md
|
|
387
|
+
skills/<dirName>/reference.md
|
|
364
388
|
```
|
|
365
389
|
|
|
366
|
-
|
|
390
|
+
Load locally with `claude --plugin-dir ./dist/claude-plugin/myapp.zip`.
|
|
367
391
|
|
|
368
392
|
Bare **`myapp mcp`** still runs the stdio MCP server (unchanged for `install --mcp` and MCP hosts). Use **`install --mcp`** for Cursor, Claude Code, Claude Desktop, and OpenCode JSON config.
|
|
369
393
|
|
package/docs/output-schema.md
CHANGED
|
@@ -7,12 +7,12 @@ How to describe JSON stdout on leaf commands — and a **recommended codegen pip
|
|
|
7
7
|
On **leaf commands**, set `outputSchema` to a JSON Schema object when the handler emits JSON (typically with `--json`, always for JSON-only commands, or on the MCP headless path).
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
|
-
import {
|
|
10
|
+
import { STATUS_JSON_OUTPUT_SCHEMA } from "../schemas/outputSchemas.js";
|
|
11
11
|
|
|
12
12
|
export const status = {
|
|
13
13
|
key: "status",
|
|
14
14
|
description: "Show environment status.",
|
|
15
|
-
outputSchema:
|
|
15
|
+
outputSchema: STATUS_JSON_OUTPUT_SCHEMA,
|
|
16
16
|
handler: async (ctx) => { /* writes JSON to stdout */ },
|
|
17
17
|
} satisfies CliLeaf;
|
|
18
18
|
```
|
|
@@ -43,74 +43,94 @@ Production CLIs with several JSON commands tend to use **codegen** so types, han
|
|
|
43
43
|
|
|
44
44
|
## Recommended pipeline (copy per repo)
|
|
45
45
|
|
|
46
|
-
No shared npm package — each app copies the same **contract**. Reference implementations: **sqsp-qa-tools**, **idp-trees**, **sqsp-i18n-tools** (see each repo’s `docs/architecture.md` for
|
|
46
|
+
No shared npm package — each app copies the same **contract**. Reference implementations: **sqsp-qa-tools**, **idp-trees**, **sqsp-i18n-tools** (see each repo’s `docs/architecture.md` for which commands use which schema root).
|
|
47
47
|
|
|
48
48
|
```mermaid
|
|
49
49
|
flowchart LR
|
|
50
50
|
subgraph types [Schema-facing TS + JSDoc]
|
|
51
|
-
|
|
51
|
+
TypesTs["src/**/types.ts"]
|
|
52
|
+
Marker["JSDoc contains JSON payload"]
|
|
52
53
|
Narrow["Narrowed types where needed"]
|
|
53
54
|
end
|
|
54
55
|
subgraph gen [just schemagen]
|
|
56
|
+
Discover["scripts/schemagen/discover-schema-roots.ts"]
|
|
55
57
|
Script["scripts/generate-output-schemas.ts"]
|
|
56
58
|
Gen["ts-json-schema-generator"]
|
|
57
59
|
end
|
|
58
60
|
subgraph artifacts [Committed]
|
|
59
61
|
Json["src/schemas/generated/*.json"]
|
|
62
|
+
Bridge["src/schemas/outputSchemas.ts"]
|
|
60
63
|
end
|
|
61
64
|
subgraph runtime [Runtime]
|
|
62
|
-
Bridge["src/schemas/outputSchemas.ts"]
|
|
63
65
|
Leaves["leaf outputSchema"]
|
|
64
66
|
Docgen["just docgen"]
|
|
65
67
|
end
|
|
66
|
-
|
|
67
|
-
Narrow -->
|
|
68
|
-
Script --> Gen --> Json
|
|
68
|
+
TypesTs --> Marker --> Discover
|
|
69
|
+
Narrow --> Discover
|
|
70
|
+
Discover --> Script --> Gen --> Json
|
|
71
|
+
Script --> Bridge --> Leaves --> Docgen
|
|
69
72
|
```
|
|
70
73
|
|
|
71
74
|
| Piece | Convention |
|
|
72
75
|
| --- | --- |
|
|
73
|
-
| Generator | [`ts-json-schema-generator`](https://github.com/vega/ts-json-schema-generator) (
|
|
76
|
+
| Generator | [`ts-json-schema-generator`](https://github.com/vega/ts-json-schema-generator) (`createGenerator` with `jsDoc: "extended"`) |
|
|
74
77
|
| Config | `tsconfig: "tsconfig.json"`, `topRef: false`, `skipTypeCheck: false` |
|
|
75
|
-
|
|
|
76
|
-
| Artifacts | Commit `src/schemas/generated/*.json` |
|
|
77
|
-
| Bridge | `src/schemas/outputSchemas.ts` imports JSON → `*_OUTPUT_SCHEMA` constants |
|
|
78
|
+
| Discovery | Walk `src/**/types.ts`; treat `export interface` as a schema root when its JSDoc contains **`JSON payload`** |
|
|
79
|
+
| Artifacts | Commit `src/schemas/generated/*.json` **and** auto-generated `src/schemas/outputSchemas.ts` |
|
|
78
80
|
| tsconfig | `"resolveJsonModule": true` |
|
|
79
|
-
| CI | `just check`:
|
|
81
|
+
| CI | `just check`: `schemagen` → `git diff --exit-code src/schemas/generated/ src/schemas/outputSchemas.ts` → typecheck |
|
|
80
82
|
| Docgen | `docgen` depends on `schemagen` so saved `./docs/api.md` and `./docs/schema.json` are fresh |
|
|
81
83
|
|
|
82
|
-
|
|
84
|
+
Copy these scripts into each consumer repo (they are intentionally duplicated, not published):
|
|
85
|
+
|
|
86
|
+
- `scripts/generate-output-schemas.ts` — generate JSON + rewrite the bridge
|
|
87
|
+
- `scripts/schemagen/discover-schema-roots.ts` — find roots and map names → filenames / export constants
|
|
88
|
+
- `scripts/schemagen/discover-schema-roots.test.ts` — lock discovery and naming per app
|
|
89
|
+
|
|
90
|
+
### Marking a schema root
|
|
83
91
|
|
|
84
|
-
|
|
92
|
+
Put schema-facing interfaces in **`src/**/types.ts`** (e.g. `src/commands/status/types.ts`, `src/ui/runHeadless/types.ts`, `src/core/types.ts`). Add a JSDoc line containing **`JSON payload`** on the exported interface:
|
|
85
93
|
|
|
86
94
|
```typescript
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
topRef: false,
|
|
98
|
-
skipTypeCheck: false,
|
|
99
|
-
jsDoc: "extended",
|
|
100
|
-
});
|
|
101
|
-
fs.writeFileSync(
|
|
102
|
-
path.join(OUT_DIR, entry.outfile),
|
|
103
|
-
`${JSON.stringify(generator.createSchema(entry.typeName), null, 2)}\n`,
|
|
104
|
-
);
|
|
95
|
+
/** JSON payload for `myapp status --json`. */
|
|
96
|
+
export interface StatusJsonOutput {
|
|
97
|
+
items: StatusJsonItem[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** JSON payload written to stdout after a headless mutating command. */
|
|
101
|
+
export interface HeadlessOpResult {
|
|
102
|
+
command: string;
|
|
103
|
+
exitCode: number;
|
|
104
|
+
tasks: HeadlessTaskResult[];
|
|
105
105
|
}
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
`discoverSchemaRoots` scans only files named `types.ts` under `src/`. Nested helper interfaces in the same file are included in the generated schema when referenced by a root; they are **not** separate JSON files unless they are also marked roots.
|
|
109
|
+
|
|
110
|
+
### Stable naming (outfile + bridge export)
|
|
111
|
+
|
|
112
|
+
Discovery maps each root type name to a generated filename and `outputSchemas.ts` constant. Suffix conventions (implemented in `outfileForType` / `schemaExportName`):
|
|
113
|
+
|
|
114
|
+
| Type suffix | Example type | Generated file | Bridge export |
|
|
115
|
+
| --- | --- | --- | --- |
|
|
116
|
+
| `JsonOutput` | `StatusJsonOutput` | `status.json` | `STATUS_JSON_OUTPUT_SCHEMA` |
|
|
117
|
+
| `OpResult` | `HeadlessOpResult` | `headless-op-result.json` | `HEADLESS_OP_RESULT_OUTPUT_SCHEMA` |
|
|
118
|
+
| `Output` | `OpenUrlOutput` | `open-url.json` | `OPEN_URL_OUTPUT_SCHEMA` |
|
|
119
|
+
| `Result` | `UidsResult` | `uids.json` | `UIDS_OUTPUT_SCHEMA` |
|
|
120
|
+
|
|
121
|
+
Prefer these suffixes for new roots so filenames and import constants stay predictable across repos.
|
|
122
|
+
|
|
123
|
+
### Generated bridge
|
|
124
|
+
|
|
125
|
+
`scripts/generate-output-schemas.ts` rewrites `src/schemas/outputSchemas.ts` on every run:
|
|
109
126
|
|
|
110
127
|
```typescript
|
|
111
|
-
|
|
128
|
+
// Auto-generated by scripts/generate-output-schemas.ts — do not edit by hand.
|
|
129
|
+
|
|
130
|
+
import status from "./generated/status.json";
|
|
112
131
|
|
|
113
|
-
|
|
132
|
+
/** JSON Schema for `myapp status --json`. */
|
|
133
|
+
export const STATUS_JSON_OUTPUT_SCHEMA = status as Record<string, unknown>;
|
|
114
134
|
```
|
|
115
135
|
|
|
116
136
|
Wire the constant on each leaf that emits that shape (several commands may share one schema, e.g. mutating ops sharing `HeadlessOpResult`).
|
|
@@ -119,21 +139,21 @@ Wire the constant on each leaf that emits that shape (several commands may share
|
|
|
119
139
|
|
|
120
140
|
**Goal:** generated schemas match what handlers actually print, with descriptions agents can read in `docs api`.
|
|
121
141
|
|
|
122
|
-
1. **
|
|
142
|
+
1. **Schema roots** — `export interface` in a `types.ts` file, with **`JSON payload`** in the interface JSDoc naming which command(s) emit it.
|
|
123
143
|
2. **Per property** — `/** … */` on every field that should appear in JSON Schema `properties` (including nested named types).
|
|
124
144
|
3. **Unions / enums** — document the alias; generator emits `enum` / `anyOf` with type-level description.
|
|
125
145
|
4. **Formats** — property JSDoc can include `@format date-time` for ISO timestamps; add a smoke test that the generated property has `format: "date-time"`.
|
|
126
|
-
5. **Do not hand-edit** `src/schemas/generated/` — change types/JSDoc, run `just schemagen`, commit
|
|
146
|
+
5. **Do not hand-edit** `src/schemas/generated/` or `src/schemas/outputSchemas.ts` — change types/JSDoc, run `just schemagen`, commit both.
|
|
127
147
|
|
|
128
148
|
### Narrowing when runtime ≠ stdout
|
|
129
149
|
|
|
130
|
-
When a shared runtime type is **wider** than one command’s JSON, add a **schema-facing**
|
|
150
|
+
When a shared runtime type is **wider** than one command’s JSON, add a **schema-facing** root in `types.ts` (still marked `JSON payload`):
|
|
131
151
|
|
|
132
152
|
```typescript
|
|
133
153
|
/** Runtime union across commands. */
|
|
134
154
|
export type ResultSource = TranslationReadinessSource | { kind: "uids"; uids: string[] };
|
|
135
155
|
|
|
136
|
-
/** JSON for `pr` and `file
|
|
156
|
+
/** JSON payload for `myapp pr` and `myapp file`. */
|
|
137
157
|
export interface TranslationReadinessResult {
|
|
138
158
|
source: TranslationReadinessSource;
|
|
139
159
|
evaluatedAt: string;
|
|
@@ -144,34 +164,36 @@ export interface TranslationReadinessResult {
|
|
|
144
164
|
Patterns:
|
|
145
165
|
|
|
146
166
|
- **Shallow dashboard types** — separate interfaces from fat API types so generated schema stays readable.
|
|
147
|
-
- **Assignability tests** —
|
|
167
|
+
- **Assignability tests** — ensure runtime rows satisfy schema-facing types so refactors cannot drift.
|
|
148
168
|
|
|
149
|
-
Handlers keep using runtime types; only
|
|
169
|
+
Handlers keep using runtime types; only discovered roots (and their type graph) feed codegen.
|
|
150
170
|
|
|
151
171
|
## Tests
|
|
152
172
|
|
|
153
|
-
|
|
173
|
+
Per repo:
|
|
154
174
|
|
|
155
|
-
-
|
|
156
|
-
-
|
|
157
|
-
- Spot-check enums, required fields, and `@format date-time` where it matters.
|
|
175
|
+
- **`scripts/schemagen/discover-schema-roots.test.ts`** — asserts which roots are discovered and stable outfile / export-name mapping.
|
|
176
|
+
- **`src/schemas/outputSchemas.test.ts`** (optional) — schema shape smoke tests: object root, key `description` fields, enums, `@format date-time`.
|
|
158
177
|
|
|
159
178
|
## Contributor workflow
|
|
160
179
|
|
|
161
|
-
1.
|
|
162
|
-
2. `just schemagen` — refresh `src/schemas/generated
|
|
163
|
-
3.
|
|
164
|
-
4.
|
|
165
|
-
5.
|
|
180
|
+
1. Add or edit schema-facing interfaces in `src/**/types.ts` with **`JSON payload`** JSDoc and per-field descriptions.
|
|
181
|
+
2. `just schemagen` — refresh `src/schemas/generated/` and `src/schemas/outputSchemas.ts`.
|
|
182
|
+
3. Import the bridge constant on the relevant leaf `outputSchema` fields.
|
|
183
|
+
4. Commit generated JSON and the bridge with the type changes.
|
|
184
|
+
5. `just docgen` / `myapp docs api --save` — refresh consumer docs.
|
|
185
|
+
6. Document which commands use which roots in **your** `docs/architecture.md` (argsbarg does not maintain per-app tables).
|
|
166
186
|
|
|
167
187
|
Add a bullet under your app’s `**… conventions:**` block in `.cursor/rules/cli-program.mdc` pointing at `node_modules/argsbarg/docs/output-schema.md` and your `src/schemas/` layout.
|
|
168
188
|
|
|
189
|
+
**Reference implementation:** [`examples/consumer-app/`](../examples/consumer-app/) in this repo (shipped in npm as `node_modules/argsbarg/examples/consumer-app/`) — discovery script, bridges, and `status` leaf with `outputSchema`.
|
|
190
|
+
|
|
169
191
|
## Out of scope
|
|
170
192
|
|
|
171
193
|
- Shared codegen package or monorepo tooling
|
|
172
194
|
- Runtime Zod / `.parse()` on stdout in argsbarg
|
|
173
195
|
- `outputSchema` for plain-text, streaming, or Ink-only commands
|
|
174
|
-
-
|
|
196
|
+
- Schema roots outside `src/**/types.ts` (use a dedicated `types.ts` next to handlers instead of `resolve.ts`)
|
|
175
197
|
|
|
176
198
|
## See also
|
|
177
199
|
|
|
@@ -9,18 +9,23 @@ When adding or changing argsbarg schema, leaf handlers, or MCP exposure:
|
|
|
9
9
|
1. **Read** `node_modules/argsbarg/docs/cli-program.md` (required — authoritative guide).
|
|
10
10
|
2. MCP tools, varargs, `inputSchema` → also `node_modules/argsbarg/docs/mcp.md`.
|
|
11
11
|
3. JSON stdout / `outputSchema` guide and codegen → `node_modules/argsbarg/docs/output-schema.md`.
|
|
12
|
-
4. `
|
|
13
|
-
5.
|
|
12
|
+
4. App config / `program.appConfig` guide and codegen → `node_modules/argsbarg/docs/config-schema.md`.
|
|
13
|
+
5. `install`, completions, skills → `node_modules/argsbarg/docs/install.md`.
|
|
14
|
+
6. Bundled `docs` built-in → `node_modules/argsbarg/docs/bundled-docs.md`.
|
|
15
|
+
7. **Examples** (shipped under `node_modules/argsbarg/examples/`):
|
|
16
|
+
- Concepts / minimal config → `examples/config-app/`
|
|
17
|
+
- **Copy template** (all builtins, schemagen, `outputSchema`) → `examples/consumer-app/`
|
|
14
18
|
|
|
15
19
|
**Hard rules** (details and examples are in the docs above — do not contradict them):
|
|
16
20
|
|
|
17
|
-
- Reserved root commands: `completion`, `install`, `mcp`, `version`, `docs`, `update`.
|
|
21
|
+
- Reserved root commands: `completion`, `install`, `mcp`, `version`, `docs`, `update`, `config`.
|
|
18
22
|
- `satisfies CliProgram` / `CliLeaf`; action-oriented `description` on root, commands, options, and positionals.
|
|
19
|
-
- Omit `mcpTool` unless genuinely CLI-only (`enabled: false`)
|
|
23
|
+
- Omit `mcpTool` unless genuinely CLI-only (`enabled: false`) or an irreducible wire limit — fix schema and headless handlers first.
|
|
20
24
|
- Interactive leaves: one headless path for MCP, non-TTY CLI, and `--yes` / `--dry-run` / `--json` (`shouldRunHeadless*`, `requireYesInNonTty`); not raw `isTTY`.
|
|
21
25
|
- String options: `format` / `default` / `pattern` per `cli-program.md`; `readLeafInputs()` for multi-flag leaves.
|
|
22
26
|
- Varargs (`argMax: 0`): CLI space-separated; MCP JSON array only — no comma-splitting positionals.
|
|
23
27
|
- Multi-surface leaves (Ink + headless + MCP): one **`read*Flags(ctx)`** per command (or shared family helper + extensions); one **`resolve*Input(flags)`** for cross-field rules — handler reads ctx once, all paths share the struct.
|
|
24
|
-
- JSON stdout: `outputSchema` on the leaf from generated constants — see `output-schema.md`; do not hand-edit `src/schemas/generated
|
|
28
|
+
- JSON stdout: `outputSchema` on the leaf from generated constants in `outputSchemas.ts` — see `output-schema.md`; mark roots with **`JSON payload`** JSDoc in `src/**/types.ts`; do not hand-edit `src/schemas/generated/` or `outputSchemas.ts`.
|
|
29
|
+
- App config: `program.appConfig.jsonSchema` from generated `configSchemas.ts` — see `config-schema.md`; mark roots with **`Config schema`** JSDoc; prefer the layout in `node_modules/argsbarg/examples/consumer-app/` when adding new schema roots.
|
|
25
30
|
|
|
26
31
|
**App-specific conventions:** replace this line with a `**<your-app> conventions:**` section (bullets only). Keep it at the bottom of this file — `just consumer-dev` / `just consumers-sync` in the argsbarg repo refresh the template above and preserve this block. Do not duplicate `cli-program.md` here; link paths and patterns only. For a second rule file (e.g. `.cursor/argsbarg.mdc`), that is fine too.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/*
|
|
3
|
+
Multi-file consumer example for program.appConfig.
|
|
4
|
+
|
|
5
|
+
Files:
|
|
6
|
+
types.ts — AppConfig interface (Config schema JSDoc marker for schemagen)
|
|
7
|
+
schema.ts — APP_CONFIG_JSON_SCHEMA (inline; production apps generate this)
|
|
8
|
+
program.ts — CliProgram with appConfig block and commands using ctx.appConfig
|
|
9
|
+
|
|
10
|
+
Try:
|
|
11
|
+
CONFIG_APP_API_TOKEN=dev bun ./examples/config-app/main.ts show --json
|
|
12
|
+
CONFIG_APP_API_TOKEN=dev bun ./examples/config-app/main.ts config get
|
|
13
|
+
CONFIG_APP_API_TOKEN=dev bun ./examples/config-app/main.ts ping
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Cli } from "../../src/index.ts";
|
|
17
|
+
import { program } from "./program.ts";
|
|
18
|
+
|
|
19
|
+
const cli = new Cli(program);
|
|
20
|
+
await cli.run();
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
CliProgram for the config-app example — program.appConfig with jsonSchema + metadata overlay.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import pkg from "../../package.json" with { type: "json" };
|
|
6
|
+
import {
|
|
7
|
+
type CliAppConfig,
|
|
8
|
+
type CliAppConfigEntry,
|
|
9
|
+
CliOptionKind,
|
|
10
|
+
type CliProgram,
|
|
11
|
+
} from "../../src/index.ts";
|
|
12
|
+
import { APP_CONFIG_JSON_SCHEMA } from "./schema.ts";
|
|
13
|
+
|
|
14
|
+
const configPath = process.env.CONFIG_APP_CONFIG_FILE;
|
|
15
|
+
|
|
16
|
+
const configSchema = {
|
|
17
|
+
apiToken: {
|
|
18
|
+
description: "Create at https://example.com/settings/tokens",
|
|
19
|
+
env: "CONFIG_APP_API_TOKEN",
|
|
20
|
+
sensitive: true,
|
|
21
|
+
},
|
|
22
|
+
defaultRegion: {
|
|
23
|
+
description: "AWS region for API calls.",
|
|
24
|
+
required: false,
|
|
25
|
+
},
|
|
26
|
+
maxRetries: {
|
|
27
|
+
description: "HTTP retry count (0–10).",
|
|
28
|
+
},
|
|
29
|
+
prefs: {
|
|
30
|
+
description: "Local cache preferences (not exported to env).",
|
|
31
|
+
required: false,
|
|
32
|
+
},
|
|
33
|
+
} as const satisfies Record<string, CliAppConfigEntry>;
|
|
34
|
+
|
|
35
|
+
export const program = {
|
|
36
|
+
key: "config-app",
|
|
37
|
+
version: pkg.version,
|
|
38
|
+
description: "Demonstrates program.appConfig, ctx.appConfig, and built-in config get/set.",
|
|
39
|
+
appConfig: {
|
|
40
|
+
...(configPath ? { path: configPath } : {}),
|
|
41
|
+
jsonSchema: APP_CONFIG_JSON_SCHEMA,
|
|
42
|
+
entries: configSchema,
|
|
43
|
+
} satisfies CliAppConfig,
|
|
44
|
+
commands: [
|
|
45
|
+
{
|
|
46
|
+
key: "show",
|
|
47
|
+
description: "Print resolved config (secrets redacted).",
|
|
48
|
+
options: [
|
|
49
|
+
{
|
|
50
|
+
name: "json",
|
|
51
|
+
description: "Emit JSON.",
|
|
52
|
+
kind: CliOptionKind.Presence,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
handler: (ctx) => {
|
|
56
|
+
const out = {
|
|
57
|
+
defaultRegion: ctx.appConfig.get("defaultRegion"),
|
|
58
|
+
maxRetries: ctx.appConfig.get("maxRetries"),
|
|
59
|
+
prefs: ctx.appConfig.get("prefs"),
|
|
60
|
+
apiTokenSet: ctx.appConfig.get("apiToken") !== undefined,
|
|
61
|
+
};
|
|
62
|
+
if (ctx.hasFlag("json")) {
|
|
63
|
+
console.log(JSON.stringify(out, null, 2));
|
|
64
|
+
} else {
|
|
65
|
+
console.log(`region=${out.defaultRegion ?? "(not set)"}`);
|
|
66
|
+
console.log(`maxRetries=${out.maxRetries ?? "(not set)"}`);
|
|
67
|
+
console.log(`prefs=${out.prefs ? JSON.stringify(out.prefs) : "(not set)"}`);
|
|
68
|
+
console.log(`apiToken=${out.apiTokenSet ? "set" : "missing"}`);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: "ping",
|
|
74
|
+
description: "Require apiToken and print a short confirmation.",
|
|
75
|
+
handler: (ctx) => {
|
|
76
|
+
const token = ctx.appConfig.require("apiToken");
|
|
77
|
+
console.log(`ok (token length ${String(token).length})`);
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
} satisfies CliProgram;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Inline draft-07 JSON Schema for AppConfig.
|
|
3
|
+
Production apps commit generated JSON and import via configSchemas.ts — see docs/config-schema.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AppConfig } from "./types.ts";
|
|
7
|
+
|
|
8
|
+
/** JSON Schema root for program.appConfig.jsonSchema (hand-written stand-in for schemagen). */
|
|
9
|
+
export const APP_CONFIG_JSON_SCHEMA = {
|
|
10
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
11
|
+
type: "object",
|
|
12
|
+
additionalProperties: false,
|
|
13
|
+
required: ["apiToken", "maxRetries"],
|
|
14
|
+
properties: {
|
|
15
|
+
apiToken: { type: "string", minLength: 1 },
|
|
16
|
+
defaultRegion: { type: "string", default: "us-east-1" },
|
|
17
|
+
maxRetries: { type: "integer", minimum: 0, maximum: 10, default: 3 },
|
|
18
|
+
prefs: {
|
|
19
|
+
type: "object",
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
required: ["ttl"],
|
|
22
|
+
properties: {
|
|
23
|
+
ttl: { type: "integer", minimum: 1 },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
} as const satisfies Record<string, unknown>;
|
|
28
|
+
|
|
29
|
+
/** Compile-time check that schema keys align with AppConfig (documentation only). */
|
|
30
|
+
type _SchemaKeys = keyof typeof APP_CONFIG_JSON_SCHEMA.properties;
|
|
31
|
+
type _ConfigKeys = keyof AppConfig;
|
|
32
|
+
const _assertKeysAlign: _SchemaKeys extends _ConfigKeys
|
|
33
|
+
? _ConfigKeys extends _SchemaKeys
|
|
34
|
+
? true
|
|
35
|
+
: never
|
|
36
|
+
: never = true;
|
|
37
|
+
void _assertKeysAlign;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config schema
|
|
3
|
+
*
|
|
4
|
+
* Application settings persisted in a flat JSON file (`program.appConfig`).
|
|
5
|
+
* In a production app, generate `APP_CONFIG_JSON_SCHEMA` from this interface
|
|
6
|
+
* with ts-json-schema-generator — see docs/config-schema.md.
|
|
7
|
+
*/
|
|
8
|
+
export interface AppConfig {
|
|
9
|
+
/** API token from the provider dashboard. */
|
|
10
|
+
apiToken: string;
|
|
11
|
+
/** AWS region (default us-east-1). */
|
|
12
|
+
defaultRegion?: string;
|
|
13
|
+
/** HTTP retry count (default 3). */
|
|
14
|
+
maxRetries: number;
|
|
15
|
+
/** Local preferences (file-only; not mapped to process.env). */
|
|
16
|
+
prefs?: {
|
|
17
|
+
ttl: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# consumer-app
|
|
2
|
+
|
|
3
|
+
**Kitchen-sink argsbarg reference** — copy this layout when bootstrapping a production CLI. For a minimal `program.appConfig` intro, see [`../config-app/`](../config-app/).
|
|
4
|
+
|
|
5
|
+
## What this demonstrates
|
|
6
|
+
|
|
7
|
+
| Area | Files / wiring |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| All builtins | `completion`, `version`, `install` (+ `--update`), `docs`, `mcp`, `config get`/`set` |
|
|
10
|
+
| `program.appConfig` | `src/types.ts` (`AppConfig`) → `schemas/configSchemas.ts` |
|
|
11
|
+
| `outputSchema` | `src/commands/status/types.ts` (`StatusJsonOutput`) → `schemas/outputSchemas.ts` |
|
|
12
|
+
| Schemagen | `scripts/schemagen.ts` + `scripts/schemagen/discover-schema-roots.ts` |
|
|
13
|
+
| Handler access | `ctx.appConfig` in `src/program.ts` |
|
|
14
|
+
| Package import | `from "argsbarg"` (not relative to argsbarg `src/`) |
|
|
15
|
+
|
|
16
|
+
## Quick start (in this repo)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cd examples/consumer-app
|
|
20
|
+
bun install
|
|
21
|
+
bun run schemagen # after changing src/**/types.ts
|
|
22
|
+
CONSUMER_APP_API_TOKEN=dev bun run start status --json
|
|
23
|
+
CONSUMER_APP_API_TOKEN=dev bun run start config get apiToken --json
|
|
24
|
+
CONSUMER_APP_API_TOKEN=dev bun run start docs readme
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Copy into a new app
|
|
28
|
+
|
|
29
|
+
1. Copy this directory into your repo (e.g. `apps/my-cli/`).
|
|
30
|
+
2. Set `"argsbarg": "^<version>"` in `package.json` (replace `file:../..`).
|
|
31
|
+
3. Run `bun run schemagen` and commit `schemas/generated/` + bridge `.ts` files.
|
|
32
|
+
4. Copy [`node_modules/argsbarg/docs/templates/cursor/rules/cli-program.mdc`](../../docs/templates/cursor/rules/cli-program.mdc) to `.cursor/rules/`.
|
|
33
|
+
|
|
34
|
+
## Schemagen markers
|
|
35
|
+
|
|
36
|
+
| Marker in interface JSDoc | Artifact |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `Config schema` | `schemas/configSchemas.ts` + `schemas/generated/*-config.json` |
|
|
39
|
+
| `JSON payload` | `schemas/outputSchemas.ts` + `schemas/generated/*.json` |
|
|
40
|
+
|
|
41
|
+
Discovery walks `src/**/types.ts` only.
|
|
42
|
+
|
|
43
|
+
## Environment
|
|
44
|
+
|
|
45
|
+
| Variable | Purpose |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `CONSUMER_APP_API_TOKEN` | Overrides `apiToken` via `program.appConfig` env mapping |
|
|
48
|
+
| `CONSUMER_APP_CONFIG_FILE` | Overrides config file path (`config.path`) |
|
|
49
|
+
|
|
50
|
+
## Maintainers (argsbarg repo)
|
|
51
|
+
|
|
52
|
+
When adding or changing builtins, update this example and run:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
just consumer-app-schemagen
|
|
56
|
+
```
|