argsbarg 6.0.0 → 6.0.2

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +4 -4
  3. package/docs/api-server.md +1 -1
  4. package/docs/config-schema.md +27 -15
  5. package/docs/output-schema.md +107 -75
  6. package/examples/full-example/README.md +8 -7
  7. package/examples/full-example/bun.lock +0 -29
  8. package/examples/full-example/justfile +6 -3
  9. package/examples/full-example/package.json +0 -2
  10. package/examples/full-example/src/commands/status/__generated__/index.ts +5 -0
  11. package/examples/full-example/{schemas/generated/status.json → src/commands/status/__generated__/outputSchema.json} +1 -1
  12. package/examples/full-example/src/commands/status/command.ts +2 -2
  13. package/examples/full-example/src/commands/status/schema.ts +14 -0
  14. package/examples/full-example/src/commands/status/types.ts +1 -11
  15. package/examples/full-example/{schemas/generated/app-config.json → src/config/__generated__/configSchema.json} +1 -1
  16. package/examples/full-example/src/config/__generated__/index.ts +5 -0
  17. package/examples/full-example/src/{types.ts → config/schema.ts} +3 -2
  18. package/examples/full-example/src/program.ts +4 -4
  19. package/package.json +24 -17
  20. package/src/api/openapi.ts +4 -2
  21. package/src/api/result.ts +23 -1
  22. package/src/api/schema-deref.test.ts +99 -0
  23. package/src/api/schema-deref.ts +76 -0
  24. package/src/api.integration.test.ts +85 -2
  25. package/src/cli-errors.ts +3 -0
  26. package/src/cli-tool/full-example-capabilities.test.ts +2 -1
  27. package/src/cli-tool/post-create.ts +3 -3
  28. package/src/cli-tool/program.ts +35 -0
  29. package/src/cli-tool/run-schemagen.ts +23 -0
  30. package/src/cli-tool/schemagen/cleanup.ts +60 -0
  31. package/src/cli-tool/schemagen/discover-schema-roots.ts +103 -0
  32. package/src/cli-tool/schemagen/index.ts +3 -0
  33. package/src/cli-tool/schemagen/names.ts +22 -0
  34. package/src/cli-tool/schemagen/run.ts +108 -0
  35. package/src/cli-tool/schemagen/schemagen.test.ts +125 -0
  36. package/src/docs/mcp-resources.ts +5 -2
  37. package/src/headless/tool-call.ts +16 -6
  38. package/examples/full-example/schemas/configSchemas.ts +0 -6
  39. package/examples/full-example/schemas/outputSchemas.ts +0 -6
  40. package/examples/full-example/scripts/schemagen/discover-schema-roots.test.ts +0 -25
  41. package/examples/full-example/scripts/schemagen/discover-schema-roots.ts +0 -93
  42. package/examples/full-example/scripts/schemagen/naming.ts +0 -82
  43. package/examples/full-example/scripts/schemagen.ts +0 -80
package/CHANGELOG.md CHANGED
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [6.0.2] - 2026-07-22
11
+
12
+ ### Added
13
+
14
+ - **`argsbarg schemagen`** — centralized JSON Schema generation from `src/**/schema.ts` into colocated `__generated__/` directories (`configSchema.json`, `inputSchema.json`, `outputSchema.json`, plus `index.ts` re-exports). Removes orphan `__generated__/` trees and stale JSON when schema roots or kinds are removed.
15
+ - **`argsbarg/schemagen`** export — `runSchemagen`, `discoverSchemaRoots`, and naming helpers (`src/cli-tool/schemagen/`).
16
+
17
+ ### Changed
18
+
19
+ - **Schemagen convention** — replace per-repo `scripts/schemagen*` copies and `schema-types.ts` bindings with `schema.ts` + gitignored `__generated__/`. Run via `just schemagen` (`argsbarg schemagen` with `node_modules/.bin` on `PATH`).
20
+
21
+ ## [6.0.1] - 2026-07-22
22
+
23
+ ### Added
24
+
25
+ - **OpenAPI schema dereferencing** — inline internal `$ref` pointers when building OpenAPI documents so API reference UIs show nested request shapes.
26
+
27
+ ### Changed
28
+
29
+ - **Colocated schemagen** — `*.schema.json` beside each `schema-types.ts`; exports `configSchema` / `inputSchema` / `outputSchema` (replaces central `*Schemas.ts` bridges).
30
+ - **HTTP tool errors** — return a plain `{ "error": "..." }` JSON body without ANSI color codes or appended CLI help text.
31
+ - **`cliErrWithHelp`** — on `api` / `mcp` invocations, throws a plain error instead of printing contextual help.
32
+ - **`GET /openapi-browser`** — Scalar config preserves schema property order from the OpenAPI document (`orderSchemaPropertiesBy: "preserve"`).
33
+
10
34
  ## [6.0.0] - 2026-07-22
11
35
 
12
36
  ### Added
@@ -715,7 +739,9 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
715
739
  - 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`).
716
740
  - Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
717
741
 
718
- [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v6.0.0...HEAD
742
+ [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v6.0.2...HEAD
743
+ [6.0.2]: https://github.com/bdombro/bun-argsbarg/releases/tag/v6.0.2
744
+ [6.0.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v6.0.1
719
745
  [6.0.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v6.0.0
720
746
  [5.1.16]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.16
721
747
  [5.1.15]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.15
package/README.md CHANGED
@@ -167,7 +167,7 @@ Add app-specific conventions in a second rule if needed. Copy the rule from the
167
167
 
168
168
  1. Build a **program root** with `satisfies CliProgram` (or `: CliProgram`): `key` is the app name, `commands` are top-level subcommands, `options` are global flags. A router root must not set `handler` or declare `positionals` (validated at startup). A leaf root may set `handler` and `positionals` directly. Use `fallbackCommand` / `fallbackMode` on any **routing node** for default subcommand routing (not root-only).
169
169
  2. Call `await new Cli(program).run()` — validates, parses argv, renders help or errors, invokes the leaf handler, and `process.exit`s with status **0** on success, **1** on implicit help or error (explicit `--help` → **0**).
170
- 3. From a handler, `cliErrWithHelp(ctx, "message")` prints a red error line plus contextual help on stderr and exits **1**.
170
+ 3. From a handler, `cliErrWithHelp(ctx, "message")` prints a red error line plus contextual help on stderr and exits **1** (CLI only; API/MCP invocations throw a plain `Error`).
171
171
 
172
172
 
173
173
 
@@ -267,9 +267,9 @@ To refresh the Cursor rule in an existing consumer: `bun scripts/merge-cli-progr
267
267
  | Area | Files / wiring |
268
268
  | --------------------- | -------------------------------------------------------------------------------------- |
269
269
  | All builtins | `completion`, `version`, `configure`, `docs`, `mcp`, `api`, `configure get`/`set` |
270
- | `program.appConfig` | `src/types.ts` (`AppConfig`) → `schemas/configSchemas.ts` |
271
- | `outputSchema` | `src/commands/status/types.ts` → `schemas/outputSchemas.ts` |
272
- | Schemagen | `scripts/schemagen.ts` + `scripts/schemagen/discover-schema-roots.ts` |
270
+ | `program.appConfig` | `src/config/schema.ts` → `configSchema` from `__generated__/` |
271
+ | `outputSchema` | `src/commands/status/schema.ts` → `outputSchema` from `__generated__/` |
272
+ | Schemagen | `just schemagen` → `argsbarg schemagen` (justfile exports `node_modules/.bin` on `PATH`) |
273
273
  | Command layout | `src/commands/<name>/command.ts`; registration in `src/program.ts` |
274
274
  | MCP doc topics | `docs.topics` auto-exposed as `<key>://docs/<topic>` resources when docs + MCP enabled |
275
275
  | Package import | `from "argsbarg"` (not relative to argsbarg `src/`) |
@@ -134,7 +134,7 @@ All responses include wide-open CORS headers (`Access-Control-Allow-Origin: *`).
134
134
 
135
135
  ## OpenAPI
136
136
 
137
- Call `generateOpenApi(program)` or `openApiJson(program)` from the package, fetch `GET /openapi.json` from a running server, or run `myapp docs openapi` / `myapp docs openapi --save` (writes `./docs/openapi.json` when `docs.enabled` and `apiServer.enabled`). Tools with custom `inputSchema` on the leaf are reflected in the document.
137
+ Call `generateOpenApi(program)` or `openApiJson(program)` from the package, fetch `GET /openapi.json` from a running server, or run `myapp docs openapi` / `myapp docs openapi --save` (writes `./docs/openapi.json` when `docs.enabled` and `apiServer.enabled`). Nested `inputSchema` / `outputSchema` `$ref` pointers are dereferenced when the OpenAPI document is built so API reference UIs can show nested request shapes.
138
138
 
139
139
  ## Complex tool inputs
140
140
 
@@ -136,39 +136,51 @@ Interactive `configure` does not persist values supplied only by env or `resolve
136
136
  | **Omit `jsonSchema`** | Simple apps; all values stored as strings; use `entry.default` |
137
137
  | **Codegen from TypeScript** | Typed config, nested objects, shared with JSON Schema CI |
138
138
 
139
- ## Recommended pipeline (copy per repo)
139
+ ## Recommended pipeline (argsbarg schemagen)
140
140
 
141
141
  Mirror the [output-schema.md](output-schema.md) pattern for config:
142
142
 
143
143
  ```mermaid
144
144
  flowchart LR
145
- subgraph types [Schema-facing TS + JSDoc]
146
- TypesTs["src/**/types.ts"]
147
- Marker["JSDoc contains Config schema"]
145
+ subgraph types [schema.ts]
146
+ Marker["export type configType = AppConfig"]
148
147
  end
149
- subgraph gen [just schemagen]
150
- Script["scripts/generate-config-schemas.ts"]
148
+ subgraph gen [argsbarg schemagen]
149
+ Script["argsbarg schemagen"]
151
150
  Gen["ts-json-schema-generator"]
152
151
  end
153
- subgraph artifacts [Committed]
154
- Json["src/schemas/generated/app-config.json"]
155
- Bridge["src/schemas/configSchemas.ts"]
152
+ subgraph artifacts [Gitignored __generated__]
153
+ Json["configSchema.json"]
154
+ Index["index.ts"]
156
155
  end
157
156
  subgraph runtime [Runtime]
158
157
  Program["program.appConfig.jsonSchema"]
159
158
  Validate["argsbarg runtime subset validator"]
160
159
  end
161
- TypesTs --> Marker --> Script --> Gen --> Json
162
- Script --> Bridge --> Program --> Validate
160
+ types --> Script --> Gen --> Json
161
+ Gen --> Index --> Program --> Validate
163
162
  ```
164
163
 
165
164
  | Piece | Convention |
166
165
  | --- | --- |
167
- | Generator | [`ts-json-schema-generator`](https://github.com/vega/ts-json-schema-generator) |
168
- | Discovery | JSDoc marker **`Config schema`** on the root config interface |
169
- | Artifacts | Commit `app-config.json` and `configSchemas.ts` bridge exporting `APP_CONFIG_JSON_SCHEMA` |
166
+ | Generator | [`ts-json-schema-generator`](https://github.com/vega/ts-json-schema-generator) (bundled with argsbarg) |
167
+ | Discovery | `export type configType = …` in `src/config/schema.ts` (type defined in same file) |
168
+ | Artifacts | `src/config/__generated__/` — gitignored; run `just schemagen` after clone |
170
169
  | Consumer CI | Optional: `ajv` + `ajv-formats` against the same committed JSON (not an argsbarg runtime dep) |
171
170
 
171
+ Example:
172
+
173
+ ```typescript
174
+ // src/config/schema.ts
175
+ export interface AppConfig {
176
+ apiToken: string;
177
+ }
178
+
179
+ export type configType = AppConfig;
180
+ ```
181
+
182
+ Wire on the program root: `import { configSchema } from "./config/__generated__/index.ts"`.
183
+
172
184
  ### Supported AppConfig shapes (argsbarg runtime validator)
173
185
 
174
186
  | Supported (v1) | Deferred |
@@ -210,7 +222,7 @@ Object/array/`$ref` properties require `--json` on `configure set` when comma-se
210
222
 
211
223
  | Example | Role |
212
224
  | --- | --- |
213
- | [`examples/full-example/`](../examples/full-example/) | **Copy template** — schemagen discovery, `APP_CONFIG_JSON_SCHEMA` bridge, `program.appConfig`, built-in `configure get`/`set` |
225
+ | [`examples/full-example/`](../examples/full-example/) | **Copy template** — `argsbarg schemagen`, `program.appConfig`, built-in `configure get`/`set` |
214
226
 
215
227
  ```bash
216
228
  cd examples/full-example && just setup && just schemagen
@@ -1,18 +1,18 @@
1
1
  # Output schemas (`outputSchema`)
2
2
 
3
- How to describe JSON stdout on leaf commands — and a **recommended codegen pipeline** used in production argsbarg apps.
3
+ How to describe JSON stdout on leaf commands — and the **argsbarg schemagen** pipeline used in production apps.
4
4
 
5
5
  ## Argsbarg contract
6
6
 
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 { STATUS_JSON_OUTPUT_SCHEMA } from "../schemas/outputSchemas.js";
10
+ import { outputSchema } from "./__generated__/index.ts";
11
11
 
12
12
  export const status = {
13
13
  key: "status",
14
14
  description: "Show environment status.",
15
- outputSchema: STATUS_JSON_OUTPUT_SCHEMA,
15
+ outputSchema,
16
16
  handler: async (ctx) => { /* writes JSON to stdout */ },
17
17
  } satisfies CliLeaf;
18
18
  ```
@@ -23,8 +23,9 @@ export const status = {
23
23
  | `myapp docs api` | Markdown per-command **Output** section |
24
24
  | `myapp docs skill` | `reference.md` for agent skills |
25
25
  | MCP `tools/list` | Optional `outputSchema` on each tool |
26
+ | HTTP `GET /openapi.json` | Response schema per tool |
26
27
 
27
- **Not validated at runtime** — argsbarg does not parse or reject handler stdout against the schema today. The schema is documentation and MCP metadata.
28
+ **Not validated at runtime** — argsbarg does not parse or reject handler stdout against the schema today. The schema is documentation and MCP/HTTP metadata.
28
29
 
29
30
  **Set on the leaf only** — not under `mcpTool` (legacy `mcpTool.outputSchema` still resolves but is deprecated).
30
31
 
@@ -41,124 +42,156 @@ See [cli-program.md — Structured stdout](cli-program.md#structured-stdout) for
41
42
 
42
43
  Production CLIs with several JSON commands tend to use **codegen** so types, handlers, and schemas stay aligned.
43
44
 
44
- ## Recommended pipeline (copy per repo)
45
+ ## Schemagen pipeline (built into argsbarg)
45
46
 
46
- No shared npm package — each app copies the same **contract**. Reference implementations: **sqsp-qa-manager-poc**, **sqsp-workspaces**, **sqsp-i18n-tools-poc** (see each repo’s `docs/architecture.md` for which commands use which schema root).
47
+ No per-repo scripts to copy — run **`argsbarg schemagen`** (or `import { runSchemagen } from "argsbarg/schemagen"`).
48
+
49
+ Reference implementations: **sqsp-qa-manager-poc**, **sqsp-workspaces**, **sqsp-i18n-tools-poc**, **pdf-gen** (see each repo’s `docs/architecture.md` for which commands use which schema root).
47
50
 
48
51
  ```mermaid
49
52
  flowchart LR
50
- subgraph types [Schema-facing TS + JSDoc]
51
- TypesTs["src/**/types.ts"]
52
- Marker["JSDoc contains JSON payload"]
53
- Narrow["Narrowed types where needed"]
53
+ subgraph types [schema.ts]
54
+ Config["export type configType = AppConfig"]
55
+ Output["export type outputType = StatusJsonOutput"]
56
+ Input["export type inputType = ToolInput (optional)"]
54
57
  end
55
- subgraph gen [just schemagen]
56
- Discover["scripts/schemagen/discover-schema-roots.ts"]
57
- Script["scripts/generate-output-schemas.ts"]
58
+ subgraph gen [argsbarg schemagen]
59
+ Discover["discover schema.ts roots"]
58
60
  Gen["ts-json-schema-generator"]
59
61
  end
60
- subgraph artifacts [Committed]
61
- Json["src/schemas/generated/*.json"]
62
- Bridge["src/schemas/outputSchemas.ts"]
62
+ subgraph artifacts [Gitignored __generated__]
63
+ Json["configSchema.json / inputSchema.json / outputSchema.json"]
64
+ Index["index.ts re-exports"]
63
65
  end
64
66
  subgraph runtime [Runtime]
65
- Leaves["leaf outputSchema"]
67
+ Leaves["import { outputSchema } from ./__generated__/index.ts"]
66
68
  Docgen["just docgen"]
67
69
  end
68
- TypesTs --> Marker --> Discover
69
- Narrow --> Discover
70
- Discover --> Script --> Gen --> Json
71
- Script --> Bridge --> Leaves --> Docgen
70
+ types --> Discover --> Gen --> Json
71
+ Gen --> Index --> Leaves --> Docgen
72
72
  ```
73
73
 
74
74
  | Piece | Convention |
75
75
  | --- | --- |
76
- | Generator | [`ts-json-schema-generator`](https://github.com/vega/ts-json-schema-generator) (`createGenerator` with `jsDoc: "extended"`) |
77
- | Config | `tsconfig: "tsconfig.json"`, `topRef: false`, `skipTypeCheck: false` |
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` |
76
+ | Generator | [`ts-json-schema-generator`](https://github.com/vega/ts-json-schema-generator) (bundled as an argsbarg dependency) |
77
+ | Discovery | Walk `src/**/schema.ts`; generate from `export type outputType = …` / `inputType` / `configType` when the target type is **defined in that file** |
78
+ | Artifacts | `src/**/__generated__/` — gitignored; run schemagen after clone or when `schema.ts` changes |
79
+ | Invocation | `just schemagen` — justfile exports `node_modules/.bin` on `PATH` for local `argsbarg` |
80
80
  | tsconfig | `"resolveJsonModule": true` |
81
- | CI | `just check`: `schemagen` → `git diff --exit-code src/schemas/generated/ src/schemas/outputSchemas.ts` → typecheck |
81
+ | CI | `just check`: `schemagen` → typecheck (no git diff on generated files) |
82
+ | Cleanup | Schemagen removes orphan `__generated__/` dirs and stale JSON when roots or kinds are removed |
82
83
  | Docgen | `docgen` depends on `schemagen` so saved `./docs/api.md` and `./docs/cli-schema.json` are fresh |
83
84
 
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
85
+ ### Declaring a schema root
89
86
 
90
- ### Marking a schema root
91
-
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:
87
+ Put schema-facing interfaces in **`schema.ts`** next to the command (or in a shared module when several commands reuse one shape). Export a role alias:
93
88
 
94
89
  ```typescript
95
- /** JSON payload for `myapp status --json`. */
90
+ // src/commands/status/schema.ts
91
+ import type { WorkspaceStatus } from "./types.ts";
92
+
93
+ /** JSON stdout for `myapp status --json`. */
96
94
  export interface StatusJsonOutput {
97
- items: StatusJsonItem[];
95
+ workspaces: WorkspaceStatus[];
98
96
  }
99
97
 
100
- /** JSON payload written to stdout after a headless mutating command. */
98
+ /** Schemagen root for leaf outputSchema. */
99
+ export type outputType = StatusJsonOutput;
100
+ ```
101
+
102
+ ```typescript
103
+ // src/ui/runHeadless/schema.ts — shared by many mutating commands
104
+ import type { HeadlessTaskResult } from "./types.ts";
105
+
101
106
  export interface HeadlessOpResult {
102
107
  command: string;
103
108
  exitCode: number;
104
109
  tasks: HeadlessTaskResult[];
105
110
  }
111
+
112
+ export type outputType = HeadlessOpResult;
106
113
  ```
107
114
 
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.
115
+ ```typescript
116
+ // src/commands/render-invoice/schema.ts — custom HTTP/MCP body (pdf-gen)
117
+ export interface RenderInvoiceToolInput {
118
+ format: "pdf" | "html";
119
+ invoice: InvoiceData;
120
+ }
109
121
 
110
- ### Stable naming (outfile + bridge export)
122
+ export type inputType = RenderInvoiceToolInput;
123
+ export type outputType = RenderInvoiceWrittenOutput;
124
+ ```
125
+
126
+ | Export | Role |
127
+ | --- | --- |
128
+ | `export type configType = AppConfig` | `program.appConfig.jsonSchema` (one per repo, typically `src/config/schema.ts`) |
129
+ | `export type outputType = …` | `leaf.outputSchema` |
130
+ | `export type inputType = …` | `leaf.inputSchema` (only when the tool body is not flat CLI flags) |
111
131
 
112
- Discovery maps each root type name to a generated filename and `outputSchemas.ts` constant. Suffix conventions (implemented in `outfileForType` / `schemaExportName`):
132
+ **Domain helpers** stay in `types.ts` (or `core/types.ts`). Discovery scans only `schema.ts`. Re-export-only files (`export type outputType = HeadlessOpResult` pointing at another module) are **not** generation roots — generate once from the canonical definition file.
113
133
 
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` |
134
+ Commands without structured JSON omit `schema.ts`. Shared shapes (e.g. `HeadlessOpResult`) live in one canonical `schema.ts`; other commands import `{ outputSchema }` from that module’s `__generated__/index.ts`.
120
135
 
121
- Prefer these suffixes for new roots so filenames and import constants stay predictable across repos.
136
+ When you do **not** set `inputSchema`, argsbarg builds tool input from CLI `options` + `positionals`.
122
137
 
123
- ### Generated bridge
138
+ ### Generated artifacts
124
139
 
125
- `scripts/generate-output-schemas.ts` rewrites `src/schemas/outputSchemas.ts` on every run:
140
+ Schemagen writes under `__generated__/` beside each `schema.ts`:
141
+
142
+ | Kind | Generated file | Exported const (from `__generated__/index.ts`) |
143
+ | --- | --- | --- |
144
+ | config | `configSchema.json` | `configSchema` |
145
+ | output | `outputSchema.json` | `outputSchema` |
146
+ | input | `inputSchema.json` | `inputSchema` |
147
+
148
+ Wire on the leaf:
126
149
 
127
150
  ```typescript
128
- // Auto-generated by scripts/generate-output-schemas.ts — do not edit by hand.
151
+ import { outputSchema } from "./__generated__/index.ts";
129
152
 
130
- import status from "./generated/status.json";
153
+ export const statusCommand = {
154
+ outputSchema,
155
+ // …
156
+ } satisfies CliLeaf;
157
+ ```
131
158
 
132
- /** JSON Schema for `myapp status --json`. */
133
- export const STATUS_JSON_OUTPUT_SCHEMA = status as Record<string, unknown>;
159
+ Shared mutators:
160
+
161
+ ```typescript
162
+ import { outputSchema } from "../../../ui/runHeadless/__generated__/index.ts";
134
163
  ```
135
164
 
136
- Wire the constant on each leaf that emits that shape (several commands may share one schema, e.g. mutating ops sharing `HeadlessOpResult`).
165
+ App config:
166
+
167
+ ```typescript
168
+ import { configSchema } from "./config/__generated__/index.ts";
169
+
170
+ appConfig: { jsonSchema: configSchema, entries: { … } },
171
+ ```
137
172
 
138
173
  ## Schema-facing types
139
174
 
140
175
  **Goal:** generated schemas match what handlers actually print, with descriptions agents can read in `docs api`.
141
176
 
142
- 1. **Schema roots** — `export interface` in a `types.ts` file, with **`JSON payload`** in the interface JSDoc naming which command(s) emit it.
177
+ 1. **Schema roots** — `export interface` in `schema.ts`, with `export type outputType = …` (or `inputType` / `configType`).
143
178
  2. **Per property** — `/** … */` on every field that should appear in JSON Schema `properties` (including nested named types).
144
179
  3. **Unions / enums** — document the alias; generator emits `enum` / `anyOf` with type-level description.
145
180
  4. **Formats** — property JSDoc can include `@format date-time` for ISO timestamps; add a smoke test that the generated property has `format: "date-time"`.
146
- 5. **Do not hand-edit** `src/schemas/generated/` or `src/schemas/outputSchemas.ts` — change types/JSDoc, run `just schemagen`, commit both.
181
+ 5. **Do not hand-edit** `__generated__/` — change types/JSDoc in `schema.ts`, run `just schemagen`.
147
182
 
148
183
  ### Narrowing when runtime ≠ stdout
149
184
 
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`):
185
+ When a shared runtime type is **wider** than one command’s JSON, add a **schema-facing** root in `schema.ts`:
151
186
 
152
187
  ```typescript
153
- /** Runtime union across commands. */
154
- export type ResultSource = TranslationReadinessSource | { kind: "uids"; uids: string[] };
155
-
156
- /** JSON payload for `myapp pr` and `myapp file`. */
188
+ /** JSON stdout for `myapp pr` and `myapp file`. */
157
189
  export interface TranslationReadinessResult {
158
190
  source: TranslationReadinessSource;
159
191
  evaluatedAt: string;
160
- // ...
161
192
  }
193
+
194
+ export type outputType = TranslationReadinessResult;
162
195
  ```
163
196
 
164
197
  Patterns:
@@ -170,33 +203,32 @@ Handlers keep using runtime types; only discovered roots (and their type graph)
170
203
 
171
204
  ## Tests
172
205
 
173
- Per repo:
206
+ In argsbarg: `src/cli-tool/schemagen/schemagen.test.ts` locks discovery and generation against `examples/full-example/`.
207
+
208
+ Per consumer repo (optional):
174
209
 
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`.
210
+ - **`src/generated-schemas.test.ts`** — smoke-test that key `outputSchema` objects have expected shape.
177
211
 
178
212
  ## Contributor workflow
179
213
 
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).
214
+ 1. Add or edit schema roots in `src/**/schema.ts` with `outputType` / `inputType` / `configType` and per-field JSDoc.
215
+ 2. `just schemagen` — refresh `src/**/__generated__/`.
216
+ 3. Import `{ outputSchema }` / `{ inputSchema }` / `{ configSchema }` from the relevant `__generated__/index.ts`.
217
+ 4. `just docgen` / `myapp docs api --save` — refresh consumer docs.
218
+ 5. Document which commands use which roots in **your** `docs/architecture.md` (argsbarg does not maintain per-app tables).
186
219
 
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.
220
+ Add a bullet under your app’s `**… conventions:**` block in `.cursor/rules/cli-program.mdc` pointing at `node_modules/argsbarg/docs/output-schema.md`.
188
221
 
189
- **Reference implementation:** [`examples/full-example/`](../examples/full-example/) in this repo (shipped in npm as `node_modules/argsbarg/examples/full-example/`) — discovery script, bridges, and `status` leaf with `outputSchema`.
222
+ **Reference implementation:** [`examples/full-example/`](../examples/full-example/) in this repo — `schema.ts` roots, `__generated__/`, and `status` leaf with `outputSchema`.
190
223
 
191
224
  ## Out of scope
192
225
 
193
- - Shared codegen package or monorepo tooling
194
226
  - Runtime Zod / `.parse()` on stdout in argsbarg
195
227
  - `outputSchema` for plain-text, streaming, or Ink-only commands
196
- - Schema roots outside `src/**/types.ts` (use a dedicated `types.ts` next to handlers instead of `resolve.ts`)
197
228
 
198
229
  ## See also
199
230
 
231
+ - [config-schema.md](config-schema.md) — `configType` / `program.appConfig`
200
232
  - [cli-program.md](cli-program.md) — structured stdout, headless JSON, `read*Flags`
201
233
  - [mcp.md](mcp.md) — `tools/list`, `structuredContent`
202
234
  - [bundled-docs.md](bundled-docs.md) — `docs api` / `docs cli-schema` docgen
@@ -9,7 +9,7 @@ From a git checkout at this directory (requires [Homebrew](https://brew.sh), [ju
9
9
  ```bash
10
10
  brew install just bun
11
11
  just setup
12
- just schemagen # after changing src/**/types.ts
12
+ just schemagen # after changing src/**/schema.ts
13
13
  just run status --json
14
14
  just run docs readme
15
15
  ```
@@ -60,14 +60,15 @@ just test-release
60
60
 
61
61
  Undo a local dev install: `just uninstall` (formula + agent artifacts; app config removed by formula `uninstall`), `just uninstall-config` (app config only, without uninstalling the formula).
62
62
 
63
- ## Schemagen markers
63
+ ## Schemagen roots
64
64
 
65
- | Marker in interface JSDoc | Artifact |
66
- | --- | --- |
67
- | `Config schema` | `schemas/configSchemas.ts` + `schemas/generated/*-config.json` |
68
- | `JSON payload` | `schemas/outputSchemas.ts` + `schemas/generated/*.json` |
65
+ | Export in `schema.ts` | Generated artifact | Import on leaf / program |
66
+ | --- | --- | --- |
67
+ | `export type configType = …` | `__generated__/configSchema.json` | `{ configSchema }` from `config/__generated__/index.ts` → `program.appConfig.jsonSchema` |
68
+ | `export type outputType = …` | `__generated__/outputSchema.json` | `{ outputSchema }` from `__generated__/index.ts` → `leaf.outputSchema` |
69
+ | `export type inputType = …` | `__generated__/inputSchema.json` | `{ inputSchema }` from `__generated__/index.ts` → `leaf.inputSchema` |
69
70
 
70
- Discovery walks `src/**/types.ts` only.
71
+ Discovery walks `src/**/schema.ts` only. Domain helpers stay in sibling `types.ts`. `__generated__/` is gitignored — run `argsbarg schemagen` (via `just schemagen` or `just setup`).
71
72
 
72
73
  ## Consumer docs
73
74
 
@@ -10,7 +10,6 @@
10
10
  "devDependencies": {
11
11
  "@biomejs/biome": "^2.5.0",
12
12
  "@types/bun": "^1.3.12",
13
- "ts-json-schema-generator": "^2.3.0",
14
13
  "typescript": "^5.9.3",
15
14
  },
16
15
  },
@@ -36,40 +35,12 @@
36
35
 
37
36
  "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
38
37
 
39
- "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
40
-
41
38
  "@types/node": ["@types/node@26.0.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA=="],
42
39
 
43
40
  "argsbarg": ["argsbarg@file:../..", { "devDependencies": { "@biomejs/biome": "^2.5.0", "@types/bun": "^1.3.12", "typescript": "^5.9.3" }, "bin": { "argsbarg": "src/index.ts" } }],
44
41
 
45
- "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
46
-
47
- "brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
48
-
49
42
  "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
50
43
 
51
- "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
52
-
53
- "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
54
-
55
- "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
56
-
57
- "lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="],
58
-
59
- "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
60
-
61
- "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
62
-
63
- "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
64
-
65
- "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
66
-
67
- "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
68
-
69
- "ts-json-schema-generator": ["ts-json-schema-generator@2.9.0", "", { "dependencies": { "@types/json-schema": "^7.0.15", "commander": "^14.0.3", "glob": "^13.0.6", "json5": "^2.2.3", "normalize-path": "^3.0.0", "safe-stable-stringify": "^2.5.0", "tslib": "^2.8.1", "typescript": "^5.9.3" }, "bin": { "ts-json-schema-generator": "bin/ts-json-schema-generator.js" } }, "sha512-NR5ZE108uiPtBHBJNGnhwoUaUx5vWTDJzDFG9YlRoqxPU76n+5FClRh92dcGgysbe1smRmYalM9Saj97GW1J4Q=="],
70
-
71
- "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
72
-
73
44
  "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
74
45
 
75
46
  "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
@@ -1,5 +1,7 @@
1
1
  set shell := ["bash", "-eu", "-o", "pipefail", "-c"]
2
2
 
3
+ export PATH := justfile_directory() + "/node_modules/.bin:" + env_var("PATH")
4
+
3
5
  cli_key := `bun scripts/print-identity.ts key`
4
6
  tap_org := `bun scripts/print-identity.ts tapOrg`
5
7
  tap_repo := `bun scripts/print-identity.ts tapRepo`
@@ -19,9 +21,8 @@ build:
19
21
  bun build ./src/index.ts --compile --outfile=dist/{{cli_key}}
20
22
  @rm -f .*.bun-build
21
23
 
22
- # Run schemagen, git diff check, typecheck, and format
24
+ # Run schemagen, typecheck, and format
23
25
  check: schemagen format typecheck
24
- git diff --exit-code schemas/
25
26
 
26
27
  # Run the CLI from source with optional args; restarts on file changes
27
28
  dev *ARGS:
@@ -84,11 +85,13 @@ run *ARGS:
84
85
 
85
86
  # Generate JSON Schema artifacts from TypeScript types
86
87
  schemagen:
87
- bun run schemagen
88
+ argsbarg schemagen
88
89
 
89
90
  # Install bun/npm dependencies
91
+ # Install bun/npm dependencies and generate schemas
90
92
  setup:
91
93
  bun install
94
+ just schemagen
92
95
 
93
96
  # Run unit tests (after check)
94
97
  test: check
@@ -9,7 +9,6 @@
9
9
  },
10
10
  "scripts": {
11
11
  "biome": "biome",
12
- "schemagen": "bun run scripts/schemagen.ts",
13
12
  "start": "bun run src/index.ts"
14
13
  },
15
14
  "dependencies": {
@@ -18,7 +17,6 @@
18
17
  "devDependencies": {
19
18
  "@biomejs/biome": "^2.5.0",
20
19
  "@types/bun": "^1.3.12",
21
- "ts-json-schema-generator": "^2.3.0",
22
20
  "typescript": "^5.9.3"
23
21
  }
24
22
  }
@@ -0,0 +1,5 @@
1
+ // Auto-generated by argsbarg schemagen — do not edit by hand.
2
+
3
+ import outputSchemaJson from "./outputSchema.json";
4
+
5
+ export const outputSchema = outputSchemaJson as Record<string, unknown>;
@@ -23,6 +23,6 @@
23
23
  "apiTokenSet",
24
24
  "version"
25
25
  ],
26
- "description": "JSON payload for `full-example status --json`.",
26
+ "description": "JSON stdout for `full-example status --json`.",
27
27
  "definitions": {}
28
28
  }
@@ -3,7 +3,7 @@ Status leaf — demonstrates outputSchema and ctx.appConfig.
3
3
  */
4
4
 
5
5
  import { type CliLeaf, CliOptionKind } from "argsbarg";
6
- import { STATUS_JSON_OUTPUT_SCHEMA } from "../../../schemas/outputSchemas.ts";
6
+ import { outputSchema } from "./__generated__/index.ts";
7
7
  import type { StatusJsonOutput } from "./types.ts";
8
8
 
9
9
  export const statusCommand = {
@@ -16,7 +16,7 @@ export const statusCommand = {
16
16
  kind: CliOptionKind.Presence,
17
17
  },
18
18
  ],
19
- outputSchema: STATUS_JSON_OUTPUT_SCHEMA,
19
+ outputSchema,
20
20
  handler: (ctx) => {
21
21
  const out: StatusJsonOutput = {
22
22
  defaultRegion: ctx.appConfig.get("defaultRegion") as string | undefined,