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
@@ -7,6 +7,8 @@ It keeps handler dispatch and help on one parser so the CLI behavior stays consi
7
7
  across every entry path.
8
8
  */
9
9
 
10
+ import { isCliCallable } from "~/runtime/exposure.ts";
11
+ import { fullStringIsDouble } from "~/utils.ts";
10
12
  import { formatValidationError, validateFormatValue } from "./formats.ts";
11
13
  import {
12
14
  CliFallbackMode,
@@ -14,10 +16,11 @@ import {
14
16
  type CliNode,
15
17
  type CliOption,
16
18
  CliOptionKind,
19
+ type CliRouter,
17
20
  isCliLeaf,
18
21
  isCliRouter,
22
+ isJsonLeaf,
19
23
  } from "./types.ts";
20
- import { fullStringIsDouble } from "./utils.ts";
21
24
 
22
25
  // ── Parse Result ──────────────────────────────────────────────────────────────
23
26
 
@@ -47,6 +50,8 @@ export interface ParseResult {
47
50
  helpExplicit: boolean;
48
51
  /** Path segments for scoped help (empty for root help). */
49
52
  helpPath: string[];
53
+ /** Path parameter values from `:param` router descent (e.g. `{ id: "qa2" }`). */
54
+ pathParams: Record<string, string>;
50
55
  /** User-facing error message when `kind === Error`. */
51
56
  errorMsg: string;
52
57
  /** Help path to render next to an error (for contextual help). */
@@ -68,6 +73,24 @@ function findChild(cmds: CliNode[], name: string): CliNode | undefined {
68
73
  return cmds.find((c) => c.key === name);
69
74
  }
70
75
 
76
+ function isParamRouterKey(key: string): boolean {
77
+ return key.startsWith(":");
78
+ }
79
+
80
+ /** Static (non-`:param`) child by key. */
81
+ function findStaticChild(cmds: CliNode[], name: string): CliNode | undefined {
82
+ const ch = cmds.find((c) => c.key === name);
83
+ if (!ch || isParamRouterKey(ch.key)) {
84
+ return undefined;
85
+ }
86
+ return ch;
87
+ }
88
+
89
+ /** The single `:param` router child at this level, if any. */
90
+ function findParamChild(cmds: CliNode[]): CliNode | undefined {
91
+ return cmds.find((c) => isParamRouterKey(c.key));
92
+ }
93
+
71
94
  /** Resolves a long-option definition by name (without leading `--`). */
72
95
  function findOptionByName(defs: CliOption[], name: string): CliOption | undefined {
73
96
  return defs.find((o) => o.name === name);
@@ -237,6 +260,50 @@ export function collectOptionDefs(root: CliNode, path: string[]): CliOption[] {
237
260
  return defs;
238
261
  }
239
262
 
263
+ /** Fills `args` for a json leaf from `startIdx` (0 or 1 JSON string positional). */
264
+ function finishJsonLeaf(
265
+ _node: CliLeaf,
266
+ startIdx: number,
267
+ argv: string[],
268
+ path: string[],
269
+ opts: Record<string, string>,
270
+ pathParams: Record<string, string>,
271
+ ): ParseResult {
272
+ let idx = startIdx;
273
+ const args: string[] = [];
274
+
275
+ if (idx < argv.length) {
276
+ const tok = argv[idx];
277
+ if (isHelpTok(tok)) {
278
+ return helpResult(path, true, pathParams);
279
+ }
280
+ if (tok === "--") {
281
+ return errorResult("Unexpected extra arguments", path, [], pathParams);
282
+ }
283
+ if (tok.startsWith("-")) {
284
+ return errorResult(`JSON commands do not accept options: ${tok}`, path, [], pathParams);
285
+ }
286
+ args.push(tok);
287
+ idx += 1;
288
+ }
289
+
290
+ if (idx < argv.length) {
291
+ return errorResult("Unexpected extra arguments", path, [], pathParams);
292
+ }
293
+
294
+ return {
295
+ kind: ParseKind.Ok,
296
+ path,
297
+ opts,
298
+ args,
299
+ pathParams,
300
+ helpExplicit: false,
301
+ helpPath: [],
302
+ errorMsg: "",
303
+ errorHelpPath: [],
304
+ };
305
+ }
306
+
240
307
  /** Fills `args` for a leaf from `startIdx` according to `node.positionals`. */
241
308
  function finishLeaf(
242
309
  node: CliLeaf,
@@ -246,6 +313,7 @@ function finishLeaf(
246
313
  opts: Record<string, string>,
247
314
  optionDefs: CliOption[],
248
315
  forcePositionalsIn: boolean,
316
+ pathParams: Record<string, string>,
249
317
  ): ParseResult {
250
318
  let idx = startIdx;
251
319
  const args: string[] = [];
@@ -256,7 +324,7 @@ function finishLeaf(
256
324
  if (argMax === 1) {
257
325
  if (argMin >= 1) {
258
326
  if (idx >= argv.length) {
259
- return errorResult(`Missing positional argument: ${p.name}`, path, []);
327
+ return errorResult(`Missing positional argument: ${p.name}`, path, [], pathParams);
260
328
  }
261
329
  args.push(argv[idx]);
262
330
  idx += 1;
@@ -284,14 +352,14 @@ function finishLeaf(
284
352
  }
285
353
 
286
354
  if (!forcePositionals && isHelpTok(tok)) {
287
- return helpResult(path, true);
355
+ return helpResult(path, true, pathParams);
288
356
  }
289
357
 
290
358
  if (!forcePositionals && tok.startsWith("-")) {
291
359
  // MUST be false — lenient mode swallows unknown flags as positionals silently
292
360
  const tailRep = consumeOptions(optionDefs, false, argv, idx, opts);
293
361
  if (tailRep.report.err) {
294
- return errorResult(tailRep.report.err, path, []);
362
+ return errorResult(tailRep.report.err, path, [], pathParams);
295
363
  }
296
364
  if (tailRep.report.sawDoubleDash) {
297
365
  forcePositionals = true;
@@ -300,7 +368,7 @@ function finishLeaf(
300
368
  idx = tailRep.nextIndex;
301
369
  continue;
302
370
  }
303
- return errorResult(`Unexpected option token: ${tok}`, path, []);
371
+ return errorResult(`Unexpected option token: ${tok}`, path, [], pathParams);
304
372
  }
305
373
 
306
374
  args.push(tok);
@@ -315,27 +383,27 @@ function finishLeaf(
315
383
  }
316
384
  }
317
385
  if (count < argMin) {
318
- return errorResult(`Expected at least ${argMin} argument(s) for ${p.name}, got ${count}`, path, []);
386
+ return errorResult(`Expected at least ${argMin} argument(s) for ${p.name}, got ${count}`, path, [], pathParams);
319
387
  }
320
388
  }
321
389
 
322
390
  if (idx < argv.length) {
323
391
  if (forcePositionals) {
324
- return errorResult("Unexpected extra arguments", path, []);
392
+ return errorResult("Unexpected extra arguments", path, [], pathParams);
325
393
  }
326
394
 
327
395
  if (isHelpTok(argv[idx])) {
328
- return helpResult(path, true);
396
+ return helpResult(path, true, pathParams);
329
397
  }
330
398
 
331
399
  const tailRep = consumeOptions(optionDefs, false, argv, idx, opts);
332
400
  if (tailRep.report.err) {
333
- return errorResult(tailRep.report.err, path, []);
401
+ return errorResult(tailRep.report.err, path, [], pathParams);
334
402
  }
335
403
  idx = tailRep.nextIndex;
336
404
 
337
405
  if (idx < argv.length) {
338
- return errorResult("Unexpected extra arguments", path, []);
406
+ return errorResult("Unexpected extra arguments", path, [], pathParams);
339
407
  }
340
408
  }
341
409
 
@@ -344,6 +412,7 @@ function finishLeaf(
344
412
  path,
345
413
  opts,
346
414
  args,
415
+ pathParams,
347
416
  helpExplicit: false,
348
417
  helpPath: [],
349
418
  errorMsg: "",
@@ -354,12 +423,18 @@ function finishLeaf(
354
423
  // ── Main Parser ───────────────────────────────────────────────────────────────
355
424
 
356
425
  /** Builds a user-error parse result; `path` defaults to `errorHelpPath`. */
357
- function errorResult(errorMsg: string, errorHelpPath: string[] = [], path: string[] = errorHelpPath): ParseResult {
426
+ function errorResult(
427
+ errorMsg: string,
428
+ errorHelpPath: string[] = [],
429
+ path: string[] = errorHelpPath,
430
+ pathParams: Record<string, string> = {},
431
+ ): ParseResult {
358
432
  return {
359
433
  kind: ParseKind.Error,
360
434
  path,
361
435
  opts: {},
362
436
  args: [],
437
+ pathParams,
363
438
  helpExplicit: false,
364
439
  helpPath: [],
365
440
  errorMsg,
@@ -368,12 +443,13 @@ function errorResult(errorMsg: string, errorHelpPath: string[] = [], path: strin
368
443
  }
369
444
 
370
445
  /** Builds a help-request result for the current routing path. */
371
- function helpResult(p: string[], explicit: boolean): ParseResult {
446
+ function helpResult(p: string[], explicit: boolean, pathParams: Record<string, string> = {}): ParseResult {
372
447
  return {
373
448
  kind: ParseKind.Help,
374
449
  path: [],
375
450
  opts: {},
376
451
  args: [],
452
+ pathParams,
377
453
  helpExplicit: explicit,
378
454
  helpPath: p,
379
455
  errorMsg: "",
@@ -381,13 +457,45 @@ function helpResult(p: string[], explicit: boolean): ParseResult {
381
457
  };
382
458
  }
383
459
 
460
+ type DescendResult = { ok: true; node: CliNode; cliEnabled: boolean } | { ok: false; error: ParseResult };
461
+
462
+ /** Descends into a static or `:param` child, updating path and pathParams. */
463
+ function descendChild(
464
+ parent: CliRouter,
465
+ tok: string,
466
+ path: string[],
467
+ pathParams: Record<string, string>,
468
+ cliEnabled: boolean,
469
+ ): DescendResult {
470
+ const staticChild = findStaticChild(parent.commands, tok);
471
+ if (staticChild) {
472
+ if (!isCliCallable(staticChild, cliEnabled)) {
473
+ return { ok: false, error: errorResult(`Unknown subcommand: ${tok}`, path, [], pathParams) };
474
+ }
475
+ path.push(tok);
476
+ return { ok: true, node: staticChild, cliEnabled: isCliCallable(staticChild, cliEnabled) };
477
+ }
478
+
479
+ const paramChild = findParamChild(parent.commands);
480
+ if (paramChild && isCliCallable(paramChild, cliEnabled)) {
481
+ const paramName = paramChild.key.slice(1);
482
+ path.push(paramChild.key);
483
+ pathParams[paramName] = tok;
484
+ return { ok: true, node: paramChild, cliEnabled: isCliCallable(paramChild, cliEnabled) };
485
+ }
486
+
487
+ return { ok: false, error: errorResult(`Unknown subcommand: ${tok}`, path, [], pathParams) };
488
+ }
489
+
384
490
  /**
385
491
  * Parses `argv` against the program root, routing into subcommands and filling `opts` / `args`.
386
492
  */
387
493
  export function parse(root: CliNode, argv: string[]): ParseResult {
388
494
  let i = 0;
389
495
  const path: string[] = [];
496
+ const pathParams: Record<string, string> = {};
390
497
  const opts: Record<string, string> = {};
498
+ let cliEnabled = true;
391
499
 
392
500
  const rootLenient =
393
501
  isCliRouter(root) &&
@@ -412,7 +520,10 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
412
520
  let node: CliNode | undefined;
413
521
 
414
522
  if (isCliLeaf(root)) {
415
- return finishLeaf(root, i, argv, path, opts, root.options ?? [], forcePositionals);
523
+ if (isJsonLeaf(root)) {
524
+ return finishJsonLeaf(root, i, argv, path, opts, pathParams);
525
+ }
526
+ return finishLeaf(root, i, argv, path, opts, root.options ?? [], forcePositionals, pathParams);
416
527
  }
417
528
 
418
529
  if (i >= argv.length) {
@@ -431,12 +542,41 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
431
542
  }
432
543
  } else {
433
544
  const peek = argv[i];
434
- const childPick = !forcePositionals ? findChild(root.commands, peek) : undefined;
545
+ const childPick = !forcePositionals ? findStaticChild(root.commands, peek) : undefined;
435
546
 
436
547
  if (childPick !== undefined) {
548
+ if (!isCliCallable(childPick, cliEnabled)) {
549
+ return errorResult(`Unknown command: ${peek}`, path, [], pathParams);
550
+ }
437
551
  cmdName = peek;
438
552
  i += 1;
439
553
  node = childPick;
554
+ cliEnabled = isCliCallable(childPick, cliEnabled);
555
+ } else if (!forcePositionals && isCliRouter(root)) {
556
+ const paramChild = findParamChild(root.commands);
557
+ if (paramChild && isCliCallable(paramChild, cliEnabled)) {
558
+ cmdName = paramChild.key;
559
+ pathParams[paramChild.key.slice(1)] = peek;
560
+ i += 1;
561
+ node = paramChild;
562
+ cliEnabled = isCliCallable(paramChild, cliEnabled);
563
+ } else {
564
+ const fallbackCommand = root.fallbackCommand;
565
+ const canRouteUnknown =
566
+ fallbackCommand !== undefined &&
567
+ ((root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOrUnknown ||
568
+ (root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.UnknownOnly);
569
+
570
+ if (canRouteUnknown) {
571
+ cmdName = fallbackCommand;
572
+ node = findChild(root.commands, cmdName);
573
+ if (!node) {
574
+ return errorResult(`Unknown command: ${cmdName}`, path, [], pathParams);
575
+ }
576
+ } else {
577
+ return errorResult(`Unknown command: ${peek}`, path, [], pathParams);
578
+ }
579
+ }
440
580
  } else {
441
581
  const fallbackCommand = root.fallbackCommand;
442
582
  const canRouteUnknown =
@@ -473,6 +613,10 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
473
613
 
474
614
  // Walk the command tree
475
615
  while (true) {
616
+ if (isCliLeaf(current) && isJsonLeaf(current)) {
617
+ return finishJsonLeaf(current, i, argv, path, opts, pathParams);
618
+ }
619
+
476
620
  if (!forcePositionals) {
477
621
  const orep = consumeOptions(collectOptionDefs(root, path), false, argv, i, opts);
478
622
  if (orep.report.err) {
@@ -485,7 +629,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
485
629
  }
486
630
 
487
631
  if (i < argv.length && !forcePositionals && isHelpTok(argv[i])) {
488
- return helpResult(path, true);
632
+ return helpResult(path, true, pathParams);
489
633
  }
490
634
 
491
635
  if (i >= argv.length) {
@@ -497,28 +641,29 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
497
641
  if (fbNode) {
498
642
  path.push(fb);
499
643
  current = fbNode;
644
+ cliEnabled = isCliCallable(fbNode, cliEnabled);
500
645
  continue;
501
646
  }
502
647
  }
503
- return helpResult(path, false);
648
+ return helpResult(path, false, pathParams);
504
649
  }
505
650
  if (!isCliLeaf(current)) {
506
- return helpResult(path, false);
651
+ return helpResult(path, false, pathParams);
507
652
  }
508
- return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
653
+ return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals, pathParams);
509
654
  }
510
655
 
511
656
  const tok = argv[i];
512
657
  if (!forcePositionals && tok.startsWith("-")) {
513
- return errorResult(`Unexpected option token: ${tok}`, path);
658
+ return errorResult(`Unexpected option token: ${tok}`, path, [], pathParams);
514
659
  }
515
660
 
516
661
  if (!forcePositionals && isCliRouter(current)) {
517
- const childOpt = findChild(current.commands, tok);
518
- if (childOpt !== undefined) {
662
+ const descended = descendChild(current, tok, path, pathParams, cliEnabled);
663
+ if (descended.ok) {
519
664
  i += 1;
520
- path.push(tok);
521
- current = childOpt;
665
+ current = descended.node;
666
+ cliEnabled = descended.cliEnabled;
522
667
  continue;
523
668
  }
524
669
  }
@@ -534,6 +679,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
534
679
  if (fbNode) {
535
680
  path.push(fb);
536
681
  current = fbNode;
682
+ cliEnabled = isCliCallable(fbNode, cliEnabled);
537
683
  continue;
538
684
  }
539
685
  }
@@ -541,13 +687,15 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
541
687
  return errorResult(
542
688
  forcePositionals ? `Expected subcommand but got positional: ${tok}` : `Unknown subcommand: ${tok}`,
543
689
  path,
690
+ [],
691
+ pathParams,
544
692
  );
545
693
  }
546
694
 
547
695
  if (!isCliLeaf(current)) {
548
- return helpResult(path, false);
696
+ return helpResult(path, false, pathParams);
549
697
  }
550
- return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
698
+ return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals, pathParams);
551
699
  }
552
700
  }
553
701
 
@@ -2,15 +2,15 @@
2
2
  This module serializes the CLI schema tree to JSON for machine-readable introspection.
3
3
  */
4
4
 
5
- import { type CliSchemaExport, exportPresentationBuiltins } from "./builtins/export.ts";
6
- import { cliResolveNotes } from "./help.ts";
7
- import { visibleOptions } from "./hidden.ts";
5
+ import { type CliSchemaExport, exportPresentationBuiltins } from "~/builtins/export.ts";
6
+ import { cliResolveNotes } from "~/help.ts";
7
+ import { isCliSchemaHidden, visibleOptions } from "~/runtime/exposure.ts";
8
8
  import { type CliNode, type CliProgram, isCliLeaf, isCliRouter, leafOutputSchema } from "./types.ts";
9
9
 
10
- const RESERVED = new Set(["api", "completion", "configure", "docs", "mcp", "version"]);
10
+ const RESERVED = new Set(["http", "completion", "configure", "docs", "mcp", "version"]);
11
11
 
12
12
  function exportCommand(cmd: CliNode, root: CliProgram): CliSchemaExport | null {
13
- if (cmd.hidden) {
13
+ if (isCliSchemaHidden(cmd)) {
14
14
  return null;
15
15
  }
16
16
 
@@ -35,6 +35,8 @@ function exportCommand(cmd: CliNode, root: CliProgram): CliSchemaExport | null {
35
35
  const outputSchema = leafOutputSchema(cmd);
36
36
  if (outputSchema !== undefined) {
37
37
  out.outputSchema = outputSchema;
38
+ } else if (cmd.http?.successContentType !== undefined) {
39
+ out.outputContentType = cmd.http.successContentType;
38
40
  }
39
41
  out.commands = exportPresentationBuiltins(root);
40
42
  return out;
@@ -67,17 +69,27 @@ function resolveSchemaNotes(node: CliSchemaExport, appKey: string): CliSchemaExp
67
69
  return out;
68
70
  }
69
71
 
72
+ /** JSON-safe command tree export (handlers omitted). */
73
+ export interface CliSchemaRootExport extends CliSchemaExport {
74
+ /** Program-level error JSON Schema when configured on `httpServer.errors` or `mcpServer.errors`. */
75
+ errorSchema?: Record<string, unknown>;
76
+ }
77
+
70
78
  /** Returns the JSON-safe command tree (handlers omitted). */
71
- export function cliSchemaExport(root: CliProgram): CliSchemaExport {
79
+ export function cliSchemaExport(root: CliProgram): CliSchemaRootExport {
72
80
  const exported = exportCommand(root, root);
73
- if (!exported) {
74
- return {
75
- key: root.key,
76
- description: root.description,
77
- commands: exportPresentationBuiltins(root),
78
- };
81
+ const errorSchema = root.httpServer?.errors?.errorSchema ?? root.mcpServer?.errors?.errorSchema;
82
+ const base: CliSchemaRootExport = !exported
83
+ ? {
84
+ key: root.key,
85
+ description: root.description,
86
+ commands: exportPresentationBuiltins(root),
87
+ }
88
+ : resolveSchemaNotes(exported, root.key);
89
+ if (errorSchema !== undefined) {
90
+ base.errorSchema = errorSchema;
79
91
  }
80
- return resolveSchemaNotes(exported, root.key);
92
+ return base;
81
93
  }
82
94
 
83
95
  export function cliSchemaJson(root: CliProgram): string {