argsbarg 6.1.1 → 6.1.3

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 (229) hide show
  1. package/CHANGELOG.md +72 -1
  2. package/README.md +17 -19
  3. package/bin/argsbarg +10 -0
  4. package/docs/README.md +4 -3
  5. package/docs/ai-skills.md +4 -2
  6. package/docs/bundled-docs.md +50 -25
  7. package/docs/cli-program.md +76 -10
  8. package/docs/config-schema.md +10 -11
  9. package/docs/configure.md +2 -0
  10. package/docs/decisions.md +40 -0
  11. package/docs/developing.md +43 -5
  12. package/docs/http-server.md +171 -0
  13. package/docs/json-schema-subset.md +51 -0
  14. package/docs/mcp.md +4 -2
  15. package/docs/output-schema.md +55 -62
  16. package/examples/formats.ts +6 -6
  17. package/examples/full-example/Formula/full-example.rb +35 -0
  18. package/examples/full-example/README.md +20 -21
  19. package/examples/full-example/docs/README.md +1 -1
  20. package/examples/full-example/docs/cli-schema.json +1790 -98
  21. package/examples/full-example/docs/cli.md +1990 -0
  22. package/examples/full-example/docs/http.md +28 -29
  23. package/examples/full-example/docs/mcp.md +8 -22
  24. package/examples/full-example/docs/openapi.json +783 -50
  25. package/examples/full-example/docs/skill.md +10 -10
  26. package/examples/full-example/justfile +11 -1
  27. package/examples/full-example/src/commands/render-json/__generated__/RenderJsonInputSchema.json +15 -0
  28. package/examples/full-example/src/commands/render-json/__generated__/index.ts +5 -0
  29. package/examples/full-example/src/commands/render-json/command.test.ts +46 -0
  30. package/examples/full-example/src/commands/render-json/command.ts +30 -0
  31. package/examples/full-example/src/commands/render-json/types.ts +9 -0
  32. package/examples/full-example/src/commands/status/__generated__/StatusJsonOutputSchema.json +15 -0
  33. package/examples/full-example/src/commands/status/__generated__/index.ts +2 -2
  34. package/examples/full-example/src/commands/status/command.ts +5 -13
  35. package/examples/full-example/src/commands/status/types.ts +1 -14
  36. package/examples/full-example/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json +15 -0
  37. package/examples/full-example/src/commands/workspaces/__generated__/index.ts +5 -0
  38. package/examples/full-example/src/commands/workspaces/command.test.ts +58 -0
  39. package/examples/full-example/src/commands/workspaces/command.ts +94 -0
  40. package/examples/full-example/src/commands/workspaces/types.ts +6 -0
  41. package/examples/full-example/src/db/index.test.ts +86 -0
  42. package/examples/full-example/src/db/index.ts +101 -0
  43. package/examples/full-example/src/db/migrate.test.ts +35 -0
  44. package/examples/full-example/src/db/migrate.ts +69 -0
  45. package/examples/full-example/src/db/migrations/001_workspaces.sql +6 -0
  46. package/examples/full-example/src/db/tables/workspaces.ts +66 -0
  47. package/examples/full-example/src/program.ts +11 -36
  48. package/examples/full-example/src/types/argsbarg.d.ts +11 -0
  49. package/examples/full-example/src/types/md.d.ts +4 -0
  50. package/examples/full-example/tsconfig.json +5 -2
  51. package/examples/mcp-test.ts +1 -2
  52. package/examples/minimal.ts +1 -7
  53. package/examples/nested.ts +1 -2
  54. package/examples/option-required.ts +1 -1
  55. package/examples/servers.ts +4 -5
  56. package/index.d.ts +440 -136
  57. package/package.json +19 -2
  58. package/src/builtins/builtins.test.ts +7 -7
  59. package/src/builtins/completion-bash.ts +1 -1
  60. package/src/builtins/completion-fish.ts +1 -1
  61. package/src/builtins/completion-group.ts +4 -4
  62. package/src/builtins/completion-simulate-shared.ts +9 -0
  63. package/src/builtins/completion-zsh.ts +1 -1
  64. package/src/builtins/config.test.ts +3 -3
  65. package/src/builtins/config.ts +9 -9
  66. package/src/builtins/configure-copy.ts +2 -2
  67. package/src/builtins/configure.ts +4 -4
  68. package/src/builtins/dispatch.ts +19 -18
  69. package/src/builtins/export.ts +7 -5
  70. package/src/builtins/http.ts +68 -0
  71. package/src/builtins/mcp.ts +28 -4
  72. package/src/builtins/presentation.ts +6 -6
  73. package/src/builtins/registry.ts +6 -6
  74. package/src/builtins/scopes.ts +2 -2
  75. package/src/builtins/version.ts +1 -1
  76. package/src/cli-tool/full-example-capabilities.test.ts +10 -15
  77. package/src/cli-tool/main.ts +1 -1
  78. package/src/cli-tool/program.ts +3 -2
  79. package/src/cli-tool/prompt.ts +1 -1
  80. package/src/cli-tool/run-schemagen.ts +1 -3
  81. package/src/cli-tool/schemagen/cleanup.ts +6 -7
  82. package/src/cli-tool/schemagen/discover-schema-roots.ts +66 -120
  83. package/src/cli-tool/schemagen/index.ts +2 -2
  84. package/src/cli-tool/schemagen/names.ts +8 -13
  85. package/src/cli-tool/schemagen/run.ts +21 -28
  86. package/src/cli-tool/schemagen/schemagen.test.ts +136 -46
  87. package/src/config/bindings.test.ts +1 -1
  88. package/src/config/bindings.ts +1 -1
  89. package/src/config/bootstrap.test.ts +1 -1
  90. package/src/config/bootstrap.ts +36 -4
  91. package/src/config/context.test.ts +1 -1
  92. package/src/config/context.ts +1 -1
  93. package/src/config/entry.ts +1 -1
  94. package/src/config/file.test.ts +1 -1
  95. package/src/config/file.ts +3 -3
  96. package/src/config/manifest.ts +1 -1
  97. package/src/config/resolve.test.ts +1 -1
  98. package/src/config/resolve.ts +1 -1
  99. package/src/config/schema.ts +1 -1
  100. package/src/config/validate.ts +1 -1
  101. package/src/{install → configure/artifacts}/binary-placement.test.ts +1 -1
  102. package/src/{install → configure/artifacts}/binary-placement.ts +1 -1
  103. package/src/{install → configure/artifacts}/gh-release-update.ts +1 -1
  104. package/src/{install → configure/artifacts}/install-validate.test.ts +3 -3
  105. package/src/{install → configure/artifacts}/mcp-config.ts +1 -1
  106. package/src/{install → configure/artifacts}/mcp-opencode.test.ts +1 -1
  107. package/src/{install → configure/artifacts}/mcp-opencode.ts +1 -1
  108. package/src/{install → configure/artifacts}/paths.ts +5 -5
  109. package/src/configure/artifacts/plan.ts +24 -0
  110. package/src/{install → configure/artifacts}/status.test.ts +1 -1
  111. package/src/{install → configure/artifacts}/status.ts +2 -2
  112. package/src/{install → configure/artifacts}/target-base.ts +1 -1
  113. package/src/{install → configure/artifacts}/target-detect.ts +1 -1
  114. package/src/{install → configure/artifacts}/target-effective.ts +3 -9
  115. package/src/{install → configure/artifacts}/target-mcp-cli.ts +1 -1
  116. package/src/{install → configure/artifacts}/target-mcp-json.ts +1 -1
  117. package/src/{install → configure/artifacts}/target-plan-build.ts +2 -2
  118. package/src/{install → configure/artifacts}/target-registry.ts +2 -2
  119. package/src/{install → configure/artifacts}/target-scope.ts +3 -3
  120. package/src/{install → configure/artifacts}/target-skill.ts +1 -1
  121. package/src/{install → configure/artifacts}/target-types.ts +2 -2
  122. package/src/{install → configure/artifacts}/targets/app.ts +5 -5
  123. package/src/{install → configure/artifacts}/targets/chatgpt-mcp.ts +2 -2
  124. package/src/{install → configure/artifacts}/targets/claude-code-mcp.ts +2 -2
  125. package/src/{install → configure/artifacts}/targets/claude-desktop-mcp.ts +2 -2
  126. package/src/{install → configure/artifacts}/targets/claude-skill.ts +2 -2
  127. package/src/{install → configure/artifacts}/targets/codex-mcp.ts +2 -2
  128. package/src/{install → configure/artifacts}/targets/codex-skill.ts +2 -2
  129. package/src/{install → configure/artifacts}/targets/configure.ts +5 -5
  130. package/src/{install → configure/artifacts}/targets/cursor-mcp.ts +2 -2
  131. package/src/{install → configure/artifacts}/targets/cursor-skill.ts +2 -2
  132. package/src/{install → configure/artifacts}/targets/index.ts +1 -1
  133. package/src/{install → configure/artifacts}/targets/openclaw-mcp.ts +2 -2
  134. package/src/{install → configure/artifacts}/targets/openclaw-skill.ts +3 -3
  135. package/src/{install → configure/artifacts}/targets/opencode-mcp.ts +5 -5
  136. package/src/{install → configure/artifacts}/targets/opencode-skill.ts +3 -3
  137. package/src/{install → configure/artifacts}/targets.test.ts +1 -1
  138. package/src/{install → configure/artifacts}/uninstall.ts +1 -1
  139. package/src/configure/configure.test.ts +11 -11
  140. package/src/configure/index.ts +14 -14
  141. package/src/configure/prompt.ts +2 -2
  142. package/src/{context.ts → core/context.ts} +26 -20
  143. package/src/core/json-leaf.test.ts +156 -0
  144. package/src/{leaf-inputs.test.ts → core/leaf-inputs.test.ts} +7 -7
  145. package/src/{leaf-inputs.ts → core/leaf-inputs.ts} +76 -16
  146. package/src/{parse.test.ts → core/parse.test.ts} +97 -109
  147. package/src/{parse.ts → core/parse.ts} +173 -25
  148. package/src/{schema.ts → core/schema.ts} +25 -13
  149. package/src/{types.ts → core/types.ts} +238 -35
  150. package/src/{validate.ts → core/validate.ts} +51 -29
  151. package/src/docs/builtin.ts +8 -19
  152. package/src/docs/{api-guide.test.ts → cli-guide.test.ts} +21 -21
  153. package/src/docs/{api-guide.ts → cli-guide.ts} +45 -16
  154. package/src/docs/docs.test.ts +76 -41
  155. package/src/docs/http-guide.ts +37 -34
  156. package/src/docs/mcp-guide.ts +12 -14
  157. package/src/docs/mcp-resources.test.ts +2 -3
  158. package/src/docs/mcp-resources.ts +6 -11
  159. package/src/docs/resolve.ts +22 -30
  160. package/src/docs/save.ts +3 -3
  161. package/src/exports/cli.ts +47 -0
  162. package/src/exports/headless.ts +13 -0
  163. package/src/exports/http.ts +6 -0
  164. package/src/exports/mcp.ts +6 -0
  165. package/src/{headless.test.ts → headless/routing.test.ts} +3 -3
  166. package/src/{headless.ts → headless/routing.ts} +3 -3
  167. package/src/headless/tool-call.ts +114 -46
  168. package/src/help.test.ts +152 -0
  169. package/src/help.ts +54 -18
  170. package/src/hooks/builtin.ts +20 -0
  171. package/src/hooks/run.ts +142 -0
  172. package/src/http/openapi.ts +182 -0
  173. package/src/http/readiness.ts +78 -0
  174. package/src/{api → http}/result.ts +16 -5
  175. package/src/http/routes.ts +329 -0
  176. package/src/http/server.ts +225 -0
  177. package/src/index.ts +38 -25
  178. package/src/log/ecs.test.ts +43 -0
  179. package/src/log/ecs.ts +59 -0
  180. package/src/log/emitter.ts +166 -0
  181. package/src/mcp/bundle.ts +2 -2
  182. package/src/mcp/claude.test.ts +1 -1
  183. package/src/mcp/claude.ts +4 -4
  184. package/src/{hidden-mcpb.test.ts → mcp/hidden-mcpb.test.ts} +10 -9
  185. package/src/mcp/result.ts +2 -2
  186. package/src/mcp/server.ts +54 -6
  187. package/src/mcp/tools.ts +18 -20
  188. package/src/{capabilities.ts → runtime/capabilities.ts} +11 -11
  189. package/src/{cli-errors.ts → runtime/cli-errors.ts} +4 -4
  190. package/src/{cli.ts → runtime/cli.ts} +160 -50
  191. package/src/runtime/exposure.ts +102 -0
  192. package/src/{invoke.test.ts → runtime/invoke.test.ts} +31 -7
  193. package/src/server/context.ts +25 -0
  194. package/src/server/overrides.ts +112 -0
  195. package/src/skill/generate.ts +8 -8
  196. package/src/skill/hint.ts +1 -1
  197. package/src/skill/install.ts +2 -2
  198. package/src/skill/naming.ts +1 -1
  199. package/src/{test-fixtures.ts → test/fixtures.ts} +3 -2
  200. package/src/{config.integration.test.ts → test/integration/config.test.ts} +8 -8
  201. package/src/{api.integration.test.ts → test/integration/http.test.ts} +170 -67
  202. package/src/{mcp.integration.test.ts → test/integration/mcp.test.ts} +11 -57
  203. package/docs/api-server.md +0 -141
  204. package/examples/full-example/docs/api.md +0 -511
  205. package/examples/full-example/src/commands/status/__generated__/outputSchema.json +0 -28
  206. package/examples/full-example/src/config/__generated__/configSchema.json +0 -40
  207. package/examples/full-example/src/config/__generated__/index.ts +0 -5
  208. package/examples/full-example/src/config/types.ts +0 -24
  209. package/src/api/openapi.ts +0 -117
  210. package/src/api/server.ts +0 -120
  211. package/src/builtins/api.ts +0 -38
  212. package/src/hidden.ts +0 -30
  213. package/src/install/plan.ts +0 -53
  214. /package/src/{install → configure/artifacts}/detect-installed.ts +0 -0
  215. /package/src/{install → configure/artifacts}/gh-release-update.test.ts +0 -0
  216. /package/src/{install → configure/artifacts}/mcp-codex.test.ts +0 -0
  217. /package/src/{install → configure/artifacts}/mcp-codex.ts +0 -0
  218. /package/src/{install → configure/artifacts}/mcp-openclaw.test.ts +0 -0
  219. /package/src/{install → configure/artifacts}/mcp-openclaw.ts +0 -0
  220. /package/src/{install → configure/artifacts}/normalize-uninstall.ts +0 -0
  221. /package/src/{install → configure/artifacts}/normalize.ts +0 -0
  222. /package/src/{install → configure/artifacts}/opts.ts +0 -0
  223. /package/src/{install → configure/artifacts}/shell.ts +0 -0
  224. /package/src/{formats.test.ts → core/formats.test.ts} +0 -0
  225. /package/src/{formats.ts → core/formats.ts} +0 -0
  226. /package/src/{respond.ts → core/respond.ts} +0 -0
  227. /package/src/{types.test.ts → core/types.test.ts} +0 -0
  228. /package/src/{api → http}/schema-deref.test.ts +0 -0
  229. /package/src/{api → http}/schema-deref.ts +0 -0
@@ -4,12 +4,13 @@ It is the shared declarative model that parsing, validation, help, and completio
4
4
  read from, so the package has one source of truth.
5
5
  */
6
6
 
7
+ import type { AnyAppConfigSnapshot } from "~/config/context.ts";
7
8
  import type { CliContext } from "./context.ts";
8
9
 
9
10
  /**
10
11
  * How a leaf handler was dispatched.
11
12
  */
12
- export type CliInvocation = "cli" | "mcp" | "api";
13
+ export type CliInvocation = "cli" | "mcp" | "http";
13
14
 
14
15
  /**
15
16
  * Option kinds: presence (boolean flag), string (free-form text), number (strict double), enum (fixed choices), or json (parsed JSON object/array).
@@ -61,14 +62,49 @@ export enum CliFallbackMode {
61
62
  UnknownOnly = "unknownOnly",
62
63
  }
63
64
 
65
+ /**
66
+ * Per-surface CLI exposure (help, completions, cli-schema).
67
+ */
68
+ export interface CliCliExposureConfig {
69
+ /** When `false`, not callable via CLI (cascades to descendants). Default: true. */
70
+ enabled?: boolean;
71
+ /** Callable; omit from help, completions, and schema export. */
72
+ hidden?: boolean;
73
+ completions?: { enabled?: boolean; hidden?: boolean };
74
+ schema?: { enabled?: boolean; hidden?: boolean };
75
+ }
76
+
77
+ /** HTTP method for REST leaves. */
78
+ export type CliHttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
79
+
80
+ /**
81
+ * Per-node HTTP exposure and response defaults (routers: segment/enabled/hidden; leaves: full set).
82
+ */
83
+ export interface CliHttpExposureConfig {
84
+ /** When `false`, omit from HTTP route table. Default: exposed. */
85
+ enabled?: boolean;
86
+ /** Callable; omit from OpenAPI / route discovery. */
87
+ hidden?: boolean;
88
+ /** Override inferred HTTP verb. */
89
+ method?: CliHttpMethod;
90
+ /** URL path segment override (≠ `key`). */
91
+ segment?: string;
92
+ /** Default success HTTP status when handler omits `ctx.respond({ status })`. */
93
+ successStatus?: number;
94
+ /** Default success Content-Type (OpenAPI + response headers). */
95
+ successContentType?: string;
96
+ /** Default Content-Disposition for binary/downloads. */
97
+ contentDisposition?: string;
98
+ }
99
+
64
100
  /**
65
101
  * A named flag or value option (`--long`, `-short`), listed on command `options`.
66
102
  */
67
103
  export interface CliOption {
68
104
  /** Option name (e.g., "name", "verbose"). */
69
105
  name: string;
70
- /** When `true`, omit from help, schema, completions, and MCP tool inputSchema (still parseable). */
71
- hidden?: boolean;
106
+ /** Per-surface CLI exposure for this option. */
107
+ cli?: Pick<CliCliExposureConfig, "hidden">;
72
108
  /** Description shown in help. */
73
109
  description: string;
74
110
  /** Option kind: presence flag, string value, or number value. */
@@ -120,7 +156,7 @@ export interface CliPositional {
120
156
  argMax?: number;
121
157
  }
122
158
 
123
- /** Optional metadata for `mcp bundle` MCP Bundle output (program root `mcpServer.bundle` only). */
159
+ /** @experimental MCP bundle output options (program root `mcpServer.bundle` only). */
124
160
  export interface CliMcpBundleConfig {
125
161
  author?: {
126
162
  name: string;
@@ -136,10 +172,15 @@ export interface CliMcpBundleConfig {
136
172
  /**
137
173
  * Enables `myapp mcp` and MCP stdio server metadata (program root only).
138
174
  * Must include `enabled: true`; omit `mcpServer` entirely to disable MCP.
175
+ * @experimental
139
176
  */
140
177
  export interface CliMcpServerConfig {
141
178
  /** When `true`, enables the `mcp` built-in and MCP stdio server. */
142
179
  enabled: boolean;
180
+ /** MCP error response defaults. */
181
+ errors?: CliMcpServerErrorsConfig;
182
+ /** Observe-only hooks for JSON-RPC messages. */
183
+ hooks?: CliMcpWireHooks;
143
184
  /** When `true`, `mcp bundle` writes `dist/<key>.mcpb` for Claude Desktop. Default false. */
144
185
  mcpd?: boolean;
145
186
  /** When `true`, `mcp bundle` also writes `dist/claude-plugin/<name>.zip`. Default false. */
@@ -161,23 +202,69 @@ export interface CliMcpServerConfig {
161
202
  bundle?: CliMcpBundleConfig;
162
203
  }
163
204
 
205
+ /** JSON Schema for structured error responses (OpenAPI + HTTP/MCP error bodies). */
206
+ export type CliJsonSchema = Record<string, unknown>;
207
+
208
+ /** Wire-level HTTP hooks (observe-only; all requests including health and 404s). */
209
+ export interface CliHttpWireHooks {
210
+ onRequest?: (ctx: CliHttpWireContext) => void | Promise<void>;
211
+ onResponse?: (ctx: CliHttpWireContext & { status: number; durationMs: number }) => void | Promise<void>;
212
+ onError?: (ctx: CliHttpWireContext & { failureKind: InvokeFailureKind; error: unknown }) => void | Promise<void>;
213
+ }
214
+
215
+ /** Per-request HTTP wire context for {@link CliHttpWireHooks}. */
216
+ export interface CliHttpWireContext {
217
+ request: Request;
218
+ requestId: string;
219
+ clientIp: string;
220
+ path: string;
221
+ method: string;
222
+ }
223
+
224
+ /** Wire-level MCP hooks on JSON-RPC messages (observe-only). */
225
+ export interface CliMcpWireHooks {
226
+ onRequest?: (ctx: CliMcpWireContext) => void | Promise<void>;
227
+ onResponse?: (ctx: CliMcpWireContext & { durationMs: number }) => void | Promise<void>;
228
+ onError?: (ctx: CliMcpWireContext & { failureKind: InvokeFailureKind; error: unknown }) => void | Promise<void>;
229
+ }
230
+
231
+ /** Per-message MCP wire context for {@link CliMcpWireHooks}. */
232
+ export interface CliMcpWireContext {
233
+ rpcMethod: string;
234
+ requestId: string;
235
+ toolName?: string;
236
+ }
237
+
164
238
  /**
165
- * Enables `myapp api` and the HTTP tool server (program root only).
166
- * Must include `enabled: true`; omit `apiServer` entirely to disable HTTP.
239
+ * Enables `myapp http` and the HTTP tool server (program root only).
240
+ * Must include `enabled: true`; omit `httpServer` entirely to disable HTTP.
167
241
  */
168
- export interface CliApiServerConfig {
169
- /** When `true`, enables the `api` built-in and HTTP tool server. */
242
+ export interface CliHttpServerConfig {
243
+ /** When `true`, enables the `http` built-in and HTTP tool server. */
170
244
  enabled: boolean;
171
245
  /** Listen host (default: `127.0.0.1`). */
172
246
  host?: string;
173
247
  /** Listen port (default: `3000`). */
174
248
  port?: number;
249
+ /** Honor `X-Forwarded-For` for client IP in hooks and logs. */
250
+ trustProxy?: boolean;
251
+ /** HTTP error response defaults. */
252
+ errors?: { errorSchema?: CliJsonSchema; obscureUnexpected?: boolean };
253
+ /** Observe-only hooks for all HTTP requests. */
254
+ hooks?: CliHttpWireHooks;
255
+ }
256
+
257
+ /** MCP server error defaults. */
258
+ export interface CliMcpServerErrorsConfig {
259
+ errorSchema?: CliJsonSchema;
260
+ obscureUnexpected?: boolean;
175
261
  }
176
262
 
177
263
  /**
178
- * Declarative HTTP response hints for a leaf (used by OpenAPI and default headers).
264
+ * Declarative HTTP response hints passed to {@link apiSuccessResponse}.
265
+ * @internal Prefer `CliHttpExposureConfig` on the leaf.
179
266
  */
180
- export interface CliApiResponseConfig {
267
+ export interface CliHttpResponseConfig {
181
268
  /** Default success Content-Type (default: `application/json`). */
182
269
  contentType?: string;
183
270
  /** Optional Content-Disposition (e.g. `attachment; filename="invoice.pdf"`). */
@@ -219,15 +306,13 @@ export interface CliMcpResource {
219
306
  export interface CliMcpToolConfig {
220
307
  /** When `false`, omit from `tools/list` (default: exposed). */
221
308
  enabled?: boolean;
309
+ /** Callable; omit from `tools/list` and MCP tool schemas. */
310
+ hidden?: boolean;
222
311
  /**
223
312
  * Override the generated MCP tool description.
224
313
  * Default: auto-generated from command path and description.
225
314
  */
226
315
  description?: string;
227
- /**
228
- * @deprecated Set `outputSchema` on the leaf command instead.
229
- */
230
- outputSchema?: Record<string, unknown>;
231
316
  }
232
317
 
233
318
  /**
@@ -310,6 +395,7 @@ export interface CliCompletionConfig {
310
395
  enabled?: boolean;
311
396
  }
312
397
 
398
+ /** @experimental */
313
399
  export interface CliConfigureConfig {
314
400
  /** When `false`, hide/disable `configure` (default: enabled). */
315
401
  enabled?: boolean;
@@ -385,21 +471,16 @@ export interface CliDocsTopic {
385
471
  }
386
472
 
387
473
  /**
388
- * Enables `myapp docs` and bundled markdown topics (program root only).
389
- * Must include `enabled: true`; omit `docs` entirely to disable.
474
+ * Opt-out and optional topics for the `docs` built-in (program root only).
475
+ * Docs is enabled by default; set `enabled: false` to disable.
390
476
  */
391
477
  export interface CliDocsConfig {
392
- /** When `true`, enables the `docs` built-in command group. */
393
- enabled: boolean;
478
+ /** When `false`, hide/disable `docs` (default: enabled). */
479
+ enabled?: boolean;
394
480
  /** Router description for `myapp docs` (default: "Print bundled CLI documentation."). */
395
481
  description?: string;
396
- /**
397
- * Subcommand for bare `myapp docs` (maps to router `fallbackCommand`).
398
- * When omitted, uses the first key in `topics` (insertion order).
399
- */
400
- defaultTopic?: string;
401
- /** Topic key → bundled markdown. Reserved keys: `mcp`, `all` (supplied by the built-in). */
402
- topics: Record<string, CliDocsTopic>;
482
+ /** Optional consumer markdown topics. Reserved keys: `mcp`, `all` (supplied by the built-in). */
483
+ topics?: Record<string, CliDocsTopic>;
403
484
  }
404
485
 
405
486
  /**
@@ -408,8 +489,10 @@ export interface CliDocsConfig {
408
489
  export interface CliNodeBase {
409
490
  /** Program or command key (e.g., "myapp", "stat", "owner"). */
410
491
  key: string;
411
- /** When `true`, omit from help listings, schema, completions, and MCP tools (still invocable). */
412
- hidden?: boolean;
492
+ /** Per-surface CLI exposure. */
493
+ cli?: CliCliExposureConfig;
494
+ /** Per-surface HTTP exposure and response defaults. */
495
+ http?: CliHttpExposureConfig;
413
496
  /** Short description shown in help. */
414
497
  description: string;
415
498
  /** Additional notes shown in help (`{argsbarg:program}` → program key). */
@@ -418,23 +501,29 @@ export interface CliNodeBase {
418
501
  options?: CliOption[];
419
502
  }
420
503
 
504
+ /** Leaf input mode: `json` = pure JSON body (no CLI flags). */
505
+ export type CliLeafKind = "json";
506
+
421
507
  /**
422
508
  * A leaf command node with a handler and optional positionals.
423
509
  */
424
510
  export type CliLeaf = CliNodeBase & {
511
+ /**
512
+ * When `"json"`, the leaf accepts a single JSON document (CLI positional or piped stdin;
513
+ * MCP/HTTP tool args = body). Requires `inputSchema`; forbids `options` and `positionals`.
514
+ */
515
+ kind?: CliLeafKind;
425
516
  /** Handler function for leaf commands. */
426
517
  handler: CliHandler;
427
518
  /** Positional argument definitions. */
428
519
  positionals?: CliPositional[];
429
520
  /**
430
521
  * JSON Schema for structured stdout (e.g. with `--json` or MCP when the handler emits JSON).
431
- * Exported in `docs cli-schema`, `docs api`, and MCP `tools/list`; not validated at runtime yet.
522
+ * Exported in `docs cli-schema`, `docs cli`, and MCP `tools/list`; not validated at runtime yet.
432
523
  */
433
524
  outputSchema?: Record<string, unknown>;
434
525
  /** JSON Schema for MCP/HTTP tool arguments (flat object). */
435
526
  inputSchema?: Record<string, unknown>;
436
- /** Declarative HTTP response metadata (Content-Type, Content-Disposition). */
437
- apiResponse?: CliApiResponseConfig;
438
527
  /** Per-tool MCP exposure and metadata. */
439
528
  mcpTool?: CliMcpToolConfig;
440
529
  };
@@ -456,6 +545,109 @@ export type CliRouter = CliNodeBase & {
456
545
  */
457
546
  export type CliNode = CliLeaf | CliRouter;
458
547
 
548
+ /** Classified failure kind for invoke error pipeline and HTTP/MCP status mapping. */
549
+ export type InvokeFailureKind = "validation" | "help" | "unexpected" | "not_ready" | "missing_config" | "unknown_route";
550
+
551
+ /**
552
+ * Per-invocation context attached in hooks (e.g. DB handles, auth principals).
553
+ * Augment in app code: `declare module "argsbarg" { interface CliLocals { db: AppDb } }`.
554
+ */
555
+ export interface CliLocals {
556
+ /** Correlation id seeded before hooks run (HTTP/MCP wire id or generated UUID). */
557
+ requestId?: string;
558
+ }
559
+
560
+ /**
561
+ * Cross-request server state (HTTP/MCP runtime bag).
562
+ * Augment in app code: `declare module "argsbarg" { interface ServerState { db: AppDb } }`.
563
+ */
564
+ export interface ServerState {
565
+ /** Set when app config soft-validation fails at server start. */
566
+ configFileError?: string;
567
+ /** Short-TTL cache for readiness probe results. */
568
+ readinessCache?: {
569
+ at: number;
570
+ result: { ok: boolean; checks: Record<string, { ok: boolean; error?: string; missing?: string[] }> };
571
+ };
572
+ /** Last readiness probe result. */
573
+ readiness?: { ok: boolean; checks: Record<string, { ok: boolean; error?: string; missing?: string[] }> };
574
+ }
575
+
576
+ /** Cross-request mutable state created at HTTP/MCP server start. */
577
+ export interface ServerRuntime {
578
+ /** Mutable global bag (DB pool, degraded flags, readiness cache, etc.). */
579
+ state: ServerState;
580
+ program: CliProgram;
581
+ surface: "http" | "mcp";
582
+ }
583
+
584
+ /** Context for program-level invoke hooks (CLI, HTTP, MCP user commands). */
585
+ export interface InvokeHookContext {
586
+ invocation: CliInvocation;
587
+ path: string[];
588
+ pathParams: Record<string, string>;
589
+ opts: Record<string, string>;
590
+ /** Per-invocation bag; `beforeInvoke` may write. Framework seeds `requestId` before hooks run. */
591
+ locals: CliLocals;
592
+ runtime?: ServerRuntime;
593
+ appConfig: AnyAppConfigSnapshot;
594
+ http?: { request: Request; clientIp: string; requestId: string };
595
+ mcp?: { rpcMethod: string; toolName?: string; requestId: string };
596
+ }
597
+
598
+ /** Error hook context after failure classification. */
599
+ export interface ErrorHookContext extends InvokeHookContext {
600
+ failureKind: InvokeFailureKind;
601
+ error: unknown;
602
+ /** Default client-facing error before `formatError` override. */
603
+ clientError: ClientErrorOverride;
604
+ }
605
+
606
+ /** Client-facing error payload; `formatError` may return a partial override. */
607
+ export interface ClientErrorOverride {
608
+ message: string;
609
+ exitCode?: number;
610
+ }
611
+
612
+ /** Minimal invoke result passed to `afterInvoke` (see {@link Cli.invoke}). */
613
+ export interface CliInvokeHookResult {
614
+ kind: "ok" | "help" | "error";
615
+ exitCode: number;
616
+ failureKind?: InvokeFailureKind;
617
+ errorMsg?: string;
618
+ }
619
+
620
+ /** Program-level invoke and error hooks (skipped for builtins). */
621
+ export interface CliProgramHooks {
622
+ /** May mutate `locals`, `opts`, `args`; may throw. Skipped for builtins. */
623
+ beforeInvoke?: (ctx: InvokeHookContext) => void | Promise<void>;
624
+ afterInvoke?: (ctx: InvokeHookContext & { result: CliInvokeHookResult }) => void | Promise<void>;
625
+ /** Mutate client-facing error payload only. Runs before `onError`. */
626
+ formatError?: (ctx: ErrorHookContext) => ClientErrorOverride | undefined | Promise<ClientErrorOverride | undefined>;
627
+ /** Observe only — runs after `formatError`; may enrich `locals`. Never mutates client response. */
628
+ onError?: (ctx: ErrorHookContext) => void | Promise<void>;
629
+ }
630
+
631
+ /** Context for optional `program.readiness` (HTTP/MCP health only). */
632
+ export interface ReadinessContext {
633
+ program: CliProgram;
634
+ surface: "http" | "mcp";
635
+ appConfig: AnyAppConfigSnapshot;
636
+ runtime: ServerRuntime;
637
+ }
638
+
639
+ /** Framework logging defaults (ECS json or human text on stderr). */
640
+ export interface CliLogConfig {
641
+ /** `json` = ECS lines; `text` = human stderr lines. Default: `json`. */
642
+ format?: "json" | "text";
643
+ /** Tee stderr + append; relative paths resolve under the app config dir. */
644
+ file?: string;
645
+ /** Emit HTTP/MCP access logs. Default: true. */
646
+ access?: boolean;
647
+ /** Emit error events after the hook pipeline. Default: true. */
648
+ errors?: boolean;
649
+ }
650
+
459
651
  /**
460
652
  * Program root passed to {@link Cli}.
461
653
  * May be a leaf or router, plus optional program-level MCP and install config.
@@ -467,14 +659,20 @@ export type CliProgram = CliNode & {
467
659
  appConfig?: CliAppConfig;
468
660
  /** When set with `enabled: true`, enables the `mcp` built-in subcommand. */
469
661
  mcpServer?: CliMcpServerConfig;
470
- /** When set with `enabled: true`, enables the `api` built-in HTTP server. */
471
- apiServer?: CliApiServerConfig;
662
+ /** When set with `enabled: true`, enables the `http` built-in HTTP server. */
663
+ httpServer?: CliHttpServerConfig;
472
664
  /** Opt-out and defaults for `configure`. */
473
665
  configure?: CliConfigureConfig;
474
666
  /** Opt-out for shell completion generation (`completion bash|zsh|fish`). */
475
667
  completion?: CliCompletionConfig;
476
- /** When set with `enabled: true`, enables the `docs` built-in command group. */
668
+ /** Opt-out and optional topics for the `docs` built-in (default: enabled). */
477
669
  docs?: CliDocsConfig;
670
+ /** Invoke and error hooks for user commands on CLI, HTTP, and MCP. */
671
+ hooks?: CliProgramHooks;
672
+ /** Optional readiness probe for HTTP/MCP `GET /health/ready` only. */
673
+ readiness?: (ctx: ReadinessContext) => boolean | Promise<boolean>;
674
+ /** Framework logging (stderr + optional file). */
675
+ log?: CliLogConfig;
478
676
  };
479
677
 
480
678
  /** True when the node is a leaf (has a handler). */
@@ -482,14 +680,19 @@ export function isCliLeaf(node: CliNode): node is CliLeaf {
482
680
  return "handler" in node && typeof node.handler === "function";
483
681
  }
484
682
 
683
+ /** True when the leaf accepts a pure JSON body (no CLI flags). */
684
+ export function isJsonLeaf(leaf: CliLeaf): boolean {
685
+ return leaf.kind === "json";
686
+ }
687
+
485
688
  /** True when the node is a router (has subcommands). */
486
689
  export function isCliRouter(node: CliNode): node is CliRouter {
487
690
  return "commands" in node && Array.isArray(node.commands);
488
691
  }
489
692
 
490
- /** Resolves structured stdout schema from the leaf (prefers leaf field over legacy `mcpTool.outputSchema`). */
693
+ /** Resolves structured stdout schema from the leaf. */
491
694
  export function leafOutputSchema(leaf: CliLeaf): Record<string, unknown> | undefined {
492
- return leaf.outputSchema ?? leaf.mcpTool?.outputSchema;
695
+ return leaf.outputSchema;
493
696
  }
494
697
 
495
698
  /**
@@ -2,12 +2,12 @@
2
2
  This module validates CLI schemas before execution.
3
3
  */
4
4
 
5
- import { reservedCommandNames, resolveCapabilities } from "./capabilities.ts";
6
- import { reservedDocsTopicResourceUris } from "./docs/mcp-resources.ts";
7
- import { DOCS_BUILTIN_TOPIC_KEYS } from "./docs/resolve.ts";
5
+ import { AGENT_PAIRS, MCP_KEYS, mcpServerRequiredForArtifact } from "~/configure/artifacts/target-registry.ts";
6
+ import { reservedDocsTopicResourceUris } from "~/docs/mcp-resources.ts";
7
+ import { DOCS_BUILTIN_TOPIC_KEYS, docsEnabled } from "~/docs/resolve.ts";
8
+ import { resolveMcpSchemaUri } from "~/mcp/tools.ts";
9
+ import { reservedCommandNames, resolveCapabilities } from "~/runtime/capabilities.ts";
8
10
  import { validateFormatValue } from "./formats.ts";
9
- import { AGENT_PAIRS, MCP_KEYS, mcpServerRequiredForArtifact } from "./install/target-registry.ts";
10
- import { resolveMcpSchemaUri } from "./mcp/tools.ts";
11
11
  import {
12
12
  type CliLeaf,
13
13
  type CliNode,
@@ -19,24 +19,20 @@ import {
19
19
  type InstallTargetSpec,
20
20
  isCliLeaf,
21
21
  isCliRouter,
22
+ isJsonLeaf,
22
23
  } from "./types.ts";
23
24
 
24
25
  /** Validates `docs` configuration on the program root. */
25
26
  function validateDocsConfig(docs: import("./types.ts").CliDocsConfig): void {
26
- const keys = Object.keys(docs.topics);
27
- if (keys.length === 0) {
28
- throw new CliSchemaValidationError("docs.topics must be non-empty");
29
- }
27
+ const topics = docs.topics ?? {};
28
+ const keys = Object.keys(topics);
30
29
  for (const reserved of DOCS_BUILTIN_TOPIC_KEYS) {
31
- if (reserved in docs.topics) {
30
+ if (reserved in topics) {
32
31
  throw new CliSchemaValidationError(`docs.topics key '${reserved}' is reserved for the docs built-in`);
33
32
  }
34
33
  }
35
- if (docs.defaultTopic !== undefined && !(docs.defaultTopic in docs.topics)) {
36
- throw new CliSchemaValidationError(`docs.defaultTopic '${docs.defaultTopic}' is not a key in docs.topics`);
37
- }
38
34
  for (const key of keys) {
39
- const text = docs.topics[key]?.text;
35
+ const text = topics[key]?.text;
40
36
  if (text === undefined || text.length === 0) {
41
37
  throw new CliSchemaValidationError(`docs.topics['${key}'].text must be non-empty`);
42
38
  }
@@ -180,15 +176,11 @@ export function cliValidateProgram(program: CliProgram): void {
180
176
  throw new CliSchemaValidationError("mcpServer requires enabled: true; omit mcpServer to disable MCP");
181
177
  }
182
178
 
183
- if (program.apiServer !== undefined && program.apiServer.enabled !== true) {
184
- throw new CliSchemaValidationError("apiServer requires enabled: true; omit apiServer to disable HTTP API");
179
+ if (program.httpServer !== undefined && program.httpServer.enabled !== true) {
180
+ throw new CliSchemaValidationError("httpServer requires enabled: true; omit httpServer to disable HTTP API");
185
181
  }
186
182
 
187
- if (program.docs !== undefined && program.docs.enabled !== true) {
188
- throw new CliSchemaValidationError("docs requires enabled: true; omit docs to disable bundled documentation");
189
- }
190
-
191
- if (program.docs?.enabled === true) {
183
+ if (docsEnabled(program) && program.docs?.topics !== undefined) {
192
184
  validateDocsConfig(program.docs);
193
185
  }
194
186
 
@@ -214,14 +206,20 @@ export function cliValidateProgram(program: CliProgram): void {
214
206
  walkNode(program, program, true);
215
207
  }
216
208
 
209
+ const PARAM_ROUTER_KEY = /^:[a-zA-Z][a-zA-Z0-9_]*$/;
210
+
211
+ function isParamRouterKey(key: string): boolean {
212
+ return key.startsWith(":");
213
+ }
214
+
217
215
  function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
218
216
  if (!isRoot) {
219
217
  const rogue = node as CliProgram;
220
218
  if (rogue.mcpServer !== undefined) {
221
219
  throw new CliSchemaValidationError(`mcpServer is only supported on the program root (not on ${node.key})`);
222
220
  }
223
- if (rogue.apiServer !== undefined) {
224
- throw new CliSchemaValidationError(`apiServer is only supported on the program root (not on ${node.key})`);
221
+ if (rogue.httpServer !== undefined) {
222
+ throw new CliSchemaValidationError(`httpServer is only supported on the program root (not on ${node.key})`);
225
223
  }
226
224
  if (rogue.configure !== undefined) {
227
225
  throw new CliSchemaValidationError(`configure is only supported on the program root (not on ${node.key})`);
@@ -238,13 +236,22 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
238
236
  if (isRoot && node.mcpTool !== undefined) {
239
237
  throw new CliSchemaValidationError("mcpTool is only supported on leaf commands");
240
238
  }
241
- const outputSchema = node.outputSchema;
242
- const legacyOutputSchema = node.mcpTool?.outputSchema;
243
- if (outputSchema !== undefined && legacyOutputSchema !== undefined) {
244
- throw new CliSchemaValidationError("Set outputSchema on the leaf only, not under mcpTool");
239
+ if (isJsonLeaf(node)) {
240
+ if (node.inputSchema === undefined) {
241
+ throw new CliSchemaValidationError(`kind: "json" requires inputSchema on ${node.key}`);
242
+ }
243
+ if ((node.options ?? []).length > 0) {
244
+ throw new CliSchemaValidationError(`kind: "json" forbids options on ${node.key}`);
245
+ }
246
+ if ((node.positionals ?? []).length > 0) {
247
+ throw new CliSchemaValidationError(`kind: "json" forbids positionals on ${node.key}`);
248
+ }
245
249
  }
246
- const resolved = outputSchema ?? legacyOutputSchema;
247
- if (resolved !== undefined && (typeof resolved !== "object" || resolved === null || Array.isArray(resolved))) {
250
+ const outputSchema = node.outputSchema;
251
+ if (
252
+ outputSchema !== undefined &&
253
+ (typeof outputSchema !== "object" || outputSchema === null || Array.isArray(outputSchema))
254
+ ) {
248
255
  throw new CliSchemaValidationError("outputSchema must be a JSON Schema object (not null or an array)");
249
256
  }
250
257
  const inputSchema = node.inputSchema;
@@ -296,11 +303,26 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
296
303
 
297
304
  if (isCliRouter(node)) {
298
305
  const seenNames = new Set<string>();
306
+ let paramRouterCount = 0;
299
307
  for (const child of node.commands) {
300
308
  if (seenNames.has(child.key)) {
301
309
  throw new CliSchemaValidationError(`Duplicate command name: ${child.key}`);
302
310
  }
303
311
  seenNames.add(child.key);
312
+ if (isParamRouterKey(child.key)) {
313
+ if (!PARAM_ROUTER_KEY.test(child.key)) {
314
+ throw new CliSchemaValidationError(
315
+ `Param router key '${child.key}' must match :[a-zA-Z][a-zA-Z0-9_]* on '${node.key}'`,
316
+ );
317
+ }
318
+ if (!isCliRouter(child)) {
319
+ throw new CliSchemaValidationError(`Param router '${child.key}' must be a router with subcommands`);
320
+ }
321
+ paramRouterCount++;
322
+ }
323
+ }
324
+ if (paramRouterCount > 1) {
325
+ throw new CliSchemaValidationError(`At most one param router per level on '${node.key}'`);
304
326
  }
305
327
 
306
328
  if (node.fallbackMode !== undefined && node.fallbackCommand === undefined) {
@@ -1,16 +1,8 @@
1
- import { docsSkillTopicDescription } from "../builtins/configure-copy.ts";
2
- import { resolveCapabilities } from "../capabilities.ts";
3
- import {
4
- CliFallbackMode,
5
- type CliLeaf,
6
- type CliOption,
7
- CliOptionKind,
8
- type CliProgram,
9
- type CliRouter,
10
- } from "../types.ts";
1
+ import { docsSkillTopicDescription } from "~/builtins/configure-copy.ts";
2
+ import { type CliLeaf, type CliOption, CliOptionKind, type CliProgram, type CliRouter } from "~/core/types.ts";
3
+ import { resolveCapabilities } from "~/runtime/capabilities.ts";
11
4
  import {
12
5
  DOCS_ROUTER_DESCRIPTION,
13
- docsEffectiveDefaultTopic,
14
6
  docsEnabled,
15
7
  docsIncludesHttpTopic,
16
8
  docsIncludesMcpTopic,
@@ -18,6 +10,7 @@ import {
18
10
  docsTopicDescription,
19
11
  docsUserTopicKeys,
20
12
  printDocsTopic,
13
+ resolveDocsConfig,
21
14
  } from "./resolve.ts";
22
15
  import { saveDocsTopic } from "./save.ts";
23
16
 
@@ -54,14 +47,12 @@ function docsRouterNotes(): string {
54
47
 
55
48
  /** Built-in `docs` router with bundled topic subcommands. */
56
49
  export function cliBuiltinDocsGroup(program: CliProgram): CliRouter {
57
- const docs = program.docs;
58
- if (!docs) {
59
- throw new Error("docs not enabled");
60
- }
50
+ const docs = resolveDocsConfig(program);
51
+ const topics = docs.topics ?? {};
61
52
  const leaves: CliLeaf[] = [];
62
53
 
63
54
  for (const key of docsUserTopicKeys(docs)) {
64
- const topic = docs.topics[key];
55
+ const topic = topics[key];
65
56
  if (!topic) {
66
57
  throw new Error(`docs topic missing: ${key}`);
67
58
  }
@@ -82,7 +73,7 @@ export function cliBuiltinDocsGroup(program: CliProgram): CliRouter {
82
73
 
83
74
  leaves.push(
84
75
  docsLeaf(program, "cli-schema", "Print the full CLI command tree as JSON."),
85
- docsLeaf(program, "api", "Print the full command reference as markdown."),
76
+ docsLeaf(program, "cli", "Print the full command reference as markdown."),
86
77
  docsLeaf(program, "skill", docsSkillTopicDescription(program, resolveCapabilities(program))),
87
78
  );
88
79
 
@@ -91,8 +82,6 @@ export function cliBuiltinDocsGroup(program: CliProgram): CliRouter {
91
82
  description: docs.description ?? DOCS_ROUTER_DESCRIPTION,
92
83
  notes: docsRouterNotes(),
93
84
  options: [DOCS_SAVE_OPTION],
94
- fallbackCommand: docsEffectiveDefaultTopic(docs),
95
- fallbackMode: CliFallbackMode.MissingOnly,
96
85
  commands: leaves,
97
86
  };
98
87
  }