toolcraft 0.0.231 → 0.0.233

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 (180) hide show
  1. package/composition.json +3 -3
  2. package/dist/applied-default.d.ts +3 -0
  3. package/dist/applied-default.js +16 -0
  4. package/dist/cli.js +508 -204
  5. package/dist/composition.json +3 -3
  6. package/dist/discriminator.d.ts +9 -0
  7. package/dist/discriminator.js +32 -0
  8. package/dist/error-report.d.ts +2 -18
  9. package/dist/error-report.js +12 -14
  10. package/dist/http.js +8 -1
  11. package/dist/human-in-loop/gate.js +6 -0
  12. package/dist/index.compile-check.js +22 -5
  13. package/dist/index.d.ts +12 -5
  14. package/dist/index.js +50 -0
  15. package/dist/json-schema-converter.d.ts +14 -6
  16. package/dist/json-schema-converter.js +150 -20
  17. package/dist/mcp-proxy-metadata.d.ts +2 -0
  18. package/dist/mcp-proxy-metadata.js +13 -0
  19. package/dist/mcp-proxy.d.ts +1 -0
  20. package/dist/mcp-proxy.js +65 -47
  21. package/dist/mcp-result.d.ts +3 -0
  22. package/dist/mcp-result.js +10 -0
  23. package/dist/mcp.js +211 -153
  24. package/dist/redaction.js +26 -17
  25. package/dist/renderer.js +40 -29
  26. package/dist/schema-member-names.d.ts +2 -0
  27. package/dist/schema-member-names.js +39 -0
  28. package/dist/schema-scope.d.ts +1 -0
  29. package/dist/schema-scope.js +29 -16
  30. package/dist/schema.d.ts +1 -1
  31. package/dist/sdk.d.ts +2 -1
  32. package/dist/sdk.js +164 -50
  33. package/dist/stream.js +24 -23
  34. package/dist/testing/diagnostic-value.d.ts +1 -0
  35. package/dist/testing/diagnostic-value.js +57 -0
  36. package/dist/testing/fakes.js +2 -1
  37. package/dist/testing/harness.js +1 -0
  38. package/dist/testing/memory-fs.js +42 -1
  39. package/dist/testing/parity.js +50 -6
  40. package/dist/union-validation.d.ts +5 -0
  41. package/dist/union-validation.js +36 -0
  42. package/node_modules/@poe-code/process-runner/dist/types.d.ts +2 -0
  43. package/node_modules/mcp-oauth-server/dist/index.js +47 -6
  44. package/node_modules/tiny-http-mcp-server/dist/cli.js +14 -2
  45. package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
  46. package/node_modules/tiny-http-mcp-server/dist/http-server.d.ts +7 -5
  47. package/node_modules/tiny-http-mcp-server/dist/http-server.js +3 -11
  48. package/node_modules/tiny-http-mcp-server/dist/http-transport.d.ts +6 -1
  49. package/node_modules/tiny-http-mcp-server/dist/http-transport.js +196 -15
  50. package/node_modules/tiny-http-mcp-server/dist/index.d.ts +2 -2
  51. package/node_modules/tiny-http-mcp-server/dist/modern-headers.d.ts +3 -0
  52. package/node_modules/tiny-http-mcp-server/dist/modern-headers.js +35 -0
  53. package/node_modules/tiny-http-mcp-server/dist/parse-body.d.ts +1 -0
  54. package/node_modules/tiny-http-mcp-server/dist/parse-body.js +9 -5
  55. package/node_modules/tiny-http-mcp-server/dist/test-support.js +17 -0
  56. package/node_modules/tiny-http-mcp-server/dist/testing.d.ts +1 -1
  57. package/node_modules/tiny-http-mcp-server/dist/testing.js +3 -2
  58. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +21 -4
  59. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +1 -1
  60. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +14 -6
  61. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.d.ts +1 -0
  62. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.js +8 -0
  63. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.d.ts +1 -0
  64. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.js +50 -0
  65. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +2 -0
  66. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +2 -0
  67. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +21 -3
  68. package/node_modules/tiny-http-mcp-server/package.json +1 -1
  69. package/node_modules/tiny-mcp-client/README.md +1 -0
  70. package/node_modules/tiny-mcp-client/dist/index.d.ts +178 -101
  71. package/node_modules/tiny-mcp-client/dist/index.js +4696 -291
  72. package/node_modules/tiny-mcp-client/package.json +2 -1
  73. package/node_modules/tiny-stdio-mcp-server/dist/base64.d.ts +1 -0
  74. package/node_modules/tiny-stdio-mcp-server/dist/base64.js +5 -0
  75. package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
  76. package/node_modules/tiny-stdio-mcp-server/dist/content/audio.d.ts +3 -0
  77. package/node_modules/tiny-stdio-mcp-server/dist/content/convert.d.ts +5 -1
  78. package/node_modules/tiny-stdio-mcp-server/dist/content/convert.js +41 -6
  79. package/node_modules/tiny-stdio-mcp-server/dist/content/file.d.ts +5 -0
  80. package/node_modules/tiny-stdio-mcp-server/dist/content/image.d.ts +3 -0
  81. package/node_modules/tiny-stdio-mcp-server/dist/headers.d.ts +10 -0
  82. package/node_modules/tiny-stdio-mcp-server/dist/headers.js +180 -0
  83. package/node_modules/tiny-stdio-mcp-server/dist/index.d.ts +5 -5
  84. package/node_modules/tiny-stdio-mcp-server/dist/index.js +1 -1
  85. package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.d.ts +220 -0
  86. package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.js +1995 -0
  87. package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.d.ts +1 -0
  88. package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.js +38 -25
  89. package/node_modules/tiny-stdio-mcp-server/dist/metadata.d.ts +2 -0
  90. package/node_modules/tiny-stdio-mcp-server/dist/metadata.js +37 -0
  91. package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.d.ts +4 -0
  92. package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.js +53 -0
  93. package/node_modules/tiny-stdio-mcp-server/dist/protocol.d.ts +17 -0
  94. package/node_modules/tiny-stdio-mcp-server/dist/protocol.js +228 -0
  95. package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.d.ts +1 -0
  96. package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.js +18 -0
  97. package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.d.ts +18 -0
  98. package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.js +34 -0
  99. package/node_modules/tiny-stdio-mcp-server/dist/schema.d.ts +4 -1
  100. package/node_modules/tiny-stdio-mcp-server/dist/server.d.ts +9 -4
  101. package/node_modules/tiny-stdio-mcp-server/dist/server.js +334 -135
  102. package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.d.ts +3 -0
  103. package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.js +50 -28
  104. package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.d.ts +11 -0
  105. package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.js +128 -0
  106. package/node_modules/tiny-stdio-mcp-server/dist/types.d.ts +58 -18
  107. package/node_modules/tiny-stdio-mcp-server/dist/types.js +2 -1
  108. package/node_modules/tiny-stdio-mcp-server/dist/uri.d.ts +1 -0
  109. package/node_modules/tiny-stdio-mcp-server/dist/uri.js +41 -0
  110. package/node_modules/tiny-stdio-mcp-server/package.json +8 -0
  111. package/node_modules/toolcraft-design/dist/command-registry.d.ts +11 -0
  112. package/node_modules/toolcraft-design/dist/command-registry.js +24 -0
  113. package/node_modules/toolcraft-design/dist/dashboard/ansi.d.ts +2 -0
  114. package/node_modules/toolcraft-design/dist/dashboard/ansi.js +81 -27
  115. package/node_modules/toolcraft-design/dist/dashboard/buffer.js +39 -25
  116. package/node_modules/toolcraft-design/dist/dashboard/components/border.js +6 -5
  117. package/node_modules/toolcraft-design/dist/dashboard/components/footer.js +26 -21
  118. package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.d.ts +1 -1
  119. package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.js +12 -6
  120. package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.d.ts +2 -1
  121. package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.js +51 -10
  122. package/node_modules/toolcraft-design/dist/dashboard/dashboard.d.ts +4 -0
  123. package/node_modules/toolcraft-design/dist/dashboard/dashboard.js +77 -4
  124. package/node_modules/toolcraft-design/dist/dashboard/index.d.ts +2 -0
  125. package/node_modules/toolcraft-design/dist/dashboard/index.js +2 -0
  126. package/node_modules/toolcraft-design/dist/dashboard/keymap.js +26 -6
  127. package/node_modules/toolcraft-design/dist/dashboard/layout.d.ts +1 -0
  128. package/node_modules/toolcraft-design/dist/dashboard/layout.js +19 -3
  129. package/node_modules/toolcraft-design/dist/dashboard/line-buffer.d.ts +9 -0
  130. package/node_modules/toolcraft-design/dist/dashboard/line-buffer.js +63 -0
  131. package/node_modules/toolcraft-design/dist/dashboard/output-preview.d.ts +9 -0
  132. package/node_modules/toolcraft-design/dist/dashboard/output-preview.js +70 -0
  133. package/node_modules/toolcraft-design/dist/dashboard/snapshot.js +3 -1
  134. package/node_modules/toolcraft-design/dist/dashboard/store.js +9 -3
  135. package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.d.ts +6 -0
  136. package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.js +108 -0
  137. package/node_modules/toolcraft-design/dist/dashboard/terminal-width.js +3 -1
  138. package/node_modules/toolcraft-design/dist/dashboard/terminal.js +11 -1
  139. package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.d.ts +1 -0
  140. package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.js +126 -0
  141. package/node_modules/toolcraft-design/dist/dashboard/types.d.ts +3 -1
  142. package/node_modules/toolcraft-design/dist/event-groups.d.ts +19 -0
  143. package/node_modules/toolcraft-design/dist/event-groups.js +49 -0
  144. package/node_modules/toolcraft-design/dist/index.d.ts +6 -1
  145. package/node_modules/toolcraft-design/dist/index.js +5 -1
  146. package/node_modules/toolcraft-design/dist/inline-notice.d.ts +14 -0
  147. package/node_modules/toolcraft-design/dist/inline-notice.js +28 -0
  148. package/node_modules/toolcraft-design/dist/metric.d.ts +8 -0
  149. package/node_modules/toolcraft-design/dist/metric.js +21 -0
  150. package/node_modules/toolcraft-design/dist/overlay-manager.d.ts +6 -0
  151. package/node_modules/toolcraft-design/dist/overlay-manager.js +10 -0
  152. package/node_modules/toolcraft-design/dist/primitives.d.ts +12 -0
  153. package/node_modules/toolcraft-design/dist/primitives.js +7 -0
  154. package/node_modules/toolcraft-design/dist/progress-group.d.ts +7 -0
  155. package/node_modules/toolcraft-design/dist/progress-group.js +10 -0
  156. package/node_modules/toolcraft-design/dist/render-performance.d.ts +33 -0
  157. package/node_modules/toolcraft-design/dist/render-performance.js +71 -0
  158. package/node_modules/toolcraft-design/dist/task-tree.d.ts +22 -0
  159. package/node_modules/toolcraft-design/dist/task-tree.js +79 -0
  160. package/node_modules/toolcraft-design/dist/viewport.d.ts +18 -0
  161. package/node_modules/toolcraft-design/dist/viewport.js +56 -0
  162. package/node_modules/toolcraft-schema/dist/clone-default.d.ts +1 -0
  163. package/node_modules/toolcraft-schema/dist/clone-default.js +26 -0
  164. package/node_modules/toolcraft-schema/dist/index.compile-check.js +1 -0
  165. package/node_modules/toolcraft-schema/dist/index.d.ts +18 -9
  166. package/node_modules/toolcraft-schema/dist/index.js +46 -8
  167. package/node_modules/toolcraft-schema/dist/json-schema/compiler.d.ts +1 -0
  168. package/node_modules/toolcraft-schema/dist/json-schema/compiler.js +6 -0
  169. package/node_modules/toolcraft-schema/dist/json-schema/evaluate.js +6 -3
  170. package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.d.ts +1 -0
  171. package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.js +69 -0
  172. package/node_modules/toolcraft-schema/dist/json-schema/types.d.ts +1 -0
  173. package/node_modules/toolcraft-schema/dist/json.d.ts +4 -1
  174. package/node_modules/toolcraft-schema/dist/json.js +57 -1
  175. package/node_modules/toolcraft-schema/dist/native-json-schema.d.ts +10 -0
  176. package/node_modules/toolcraft-schema/dist/native-json-schema.js +11 -0
  177. package/node_modules/toolcraft-schema/dist/validate.d.ts +5 -1
  178. package/node_modules/toolcraft-schema/dist/validate.js +61 -47
  179. package/node_modules/toolcraft-schema/package.json +1 -1
  180. package/package.json +6 -3
package/dist/cli.js CHANGED
@@ -2,8 +2,8 @@ import "./node-require-shim.js";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { Command as CommanderCommand, CommanderError, InvalidArgumentError, Option } from "commander";
5
- import { validate as validateSchema } from "toolcraft-schema";
6
- import { cancel, configureTheme, confirm, createLogger, formatCommandList, formatOptionList, getTheme, helpFormatterPlain, isCancel, note, promptText, renderHelpTokens, renderTable, resetOutputFormatCache, select, text } from "toolcraft-design";
5
+ import { cloneDefaultValue, nativeJsonSchema, unicodeLength, validate as validateSchema } from "toolcraft-schema";
6
+ import { configureTheme, confirm, createLogger, formatCommandList, formatOptionList, getTheme, helpFormatterPlain, isCancel, note, promptText, renderHelpTokens, renderTable, select, text, withOutputFormat } from "toolcraft-design";
7
7
  import { ApprovalDeclinedError, ToolcraftBugError, UserError, assertCommandRequirements, getCommandSourcePath, hasMcpProxyConfig, isUserError, resolveCommandSecrets } from "./index.js";
8
8
  import { hasOwnErrorCode } from "./error-codes.js";
9
9
  import { writeErrorReport } from "./error-report.js";
@@ -203,10 +203,20 @@ function collectFields(schema, casing, globalLongOptionFlags, path = [], inherit
203
203
  optional: runtimeOptional,
204
204
  hasDefault: false,
205
205
  defaultValue: undefined,
206
- requiredWhenActive
206
+ requiredWhenActive,
207
+ variantId: variantContext?.id,
208
+ variantBranchId: variantContext?.branchId
207
209
  };
208
210
  collected.fields.push(controlField);
209
211
  const branches = [];
212
+ collected.variants.push({
213
+ id: variantId,
214
+ controlDisplayPath: controlField.displayPath,
215
+ controlFieldId: controlField.id,
216
+ optional: variantContext === undefined ? runtimeOptional : !requiredWhenActive,
217
+ parent: variantContext,
218
+ branches
219
+ });
210
220
  for (const [branchId, branchSchema] of Object.entries(childSchema.branches)) {
211
221
  const branch = collectFields(branchSchema, casing, globalLongOptionFlags, nextPath, true, {
212
222
  id: variantId,
@@ -227,20 +237,21 @@ function collectFields(schema, casing, globalLongOptionFlags, path = [], inherit
227
237
  .map((field) => field.id)
228
238
  });
229
239
  }
230
- collected.variants.push({
231
- id: variantId,
232
- controlDisplayPath: controlField.displayPath,
233
- controlFieldId: controlField.id,
234
- optional: runtimeOptional,
235
- branches
236
- });
237
240
  continue;
238
241
  }
239
242
  if (childSchema.kind === "union") {
240
243
  const variantId = `${toDisplayPath(nextPath)}:union`;
241
244
  const controlPath = toUnionKindControlPath(nextPath);
242
245
  const controlDisplayPath = toUnionKindDisplayPath(nextPath);
243
- const branchIds = childSchema.branches.map((branch) => getRequiredBranchFingerprint(branch, casing));
246
+ const seenFingerprints = new Set();
247
+ const branchIds = childSchema.branches.map((branch, index) => {
248
+ const fingerprint = getRequiredBranchFingerprint(branch, casing);
249
+ const branchId = seenFingerprints.has(fingerprint)
250
+ ? `${fingerprint} (branch ${index + 1})`
251
+ : fingerprint;
252
+ seenFingerprints.add(fingerprint);
253
+ return branchId;
254
+ });
244
255
  const controlField = {
245
256
  id: controlDisplayPath,
246
257
  path: controlPath,
@@ -256,10 +267,20 @@ function collectFields(schema, casing, globalLongOptionFlags, path = [], inherit
256
267
  hasDefault: false,
257
268
  defaultValue: undefined,
258
269
  requiredWhenActive,
259
- synthetic: true
270
+ synthetic: true,
271
+ variantId: variantContext?.id,
272
+ variantBranchId: variantContext?.branchId
260
273
  };
261
274
  collected.fields.push(controlField);
262
275
  const branches = [];
276
+ collected.variants.push({
277
+ id: variantId,
278
+ controlDisplayPath,
279
+ controlFieldId: controlField.id,
280
+ optional: variantContext === undefined ? runtimeOptional : !requiredWhenActive,
281
+ parent: variantContext,
282
+ branches
283
+ });
263
284
  childSchema.branches.forEach((branchSchema, index) => {
264
285
  const branchId = branchIds[index] ?? "";
265
286
  const branch = collectFields(branchSchema, casing, globalLongOptionFlags, nextPath, true, {
@@ -281,13 +302,6 @@ function collectFields(schema, casing, globalLongOptionFlags, path = [], inherit
281
302
  .map((field) => field.id)
282
303
  });
283
304
  });
284
- collected.variants.push({
285
- id: variantId,
286
- controlDisplayPath,
287
- controlFieldId: controlField.id,
288
- optional: runtimeOptional,
289
- branches
290
- });
291
305
  continue;
292
306
  }
293
307
  if (childSchema.kind === "record") {
@@ -418,23 +432,24 @@ function parseBooleanText(value, label) {
418
432
  if (normalized === "false") {
419
433
  return false;
420
434
  }
421
- throw new InvalidArgumentError(`Invalid value for "${label}". Expected true or false, got ${describeReceived(value)}.`);
435
+ throw new UserError(`Invalid value for "${label}". Expected true or false, got ${describeReceived(value)}.`);
422
436
  }
423
437
  function parseEnumValue(value, values, label) {
424
438
  const match = values.find((candidate) => String(candidate) === value);
425
439
  if (match === undefined) {
426
440
  const suggestions = suggest(value, values.map((candidate) => String(candidate)));
427
441
  const suggestionLine = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?\n` : " ";
428
- throw new InvalidArgumentError(`Invalid value for "${label}".${suggestionLine}Expected one of: ${values.map((candidate) => String(candidate)).join(", ")}, got ${describeReceived(value)}.`);
442
+ throw new UserError(`Invalid value for "${label}".${suggestionLine}Expected one of: ${values.map((candidate) => String(candidate)).join(", ")}, got ${describeReceived(value)}.`);
429
443
  }
430
444
  return match;
431
445
  }
432
446
  function validateStringPattern(value, schema, label) {
433
- if (schema.minLength !== undefined && value.length < schema.minLength) {
434
- throw new UserError(`Invalid value for "${label}". Expected a string with length at least ${schema.minLength}, got string with length ${value.length}.`);
447
+ const length = unicodeLength(value);
448
+ if (schema.minLength !== undefined && length < schema.minLength) {
449
+ throw new UserError(`Invalid value for "${label}". Expected a string with length at least ${schema.minLength}, got string with length ${length}.`);
435
450
  }
436
- if (schema.maxLength !== undefined && value.length > schema.maxLength) {
437
- throw new UserError(`Invalid value for "${label}". Expected a string with length at most ${schema.maxLength}, got string with length ${value.length}.`);
451
+ if (schema.maxLength !== undefined && length > schema.maxLength) {
452
+ throw new UserError(`Invalid value for "${label}". Expected a string with length at most ${schema.maxLength}, got string with length ${length}.`);
438
453
  }
439
454
  if (schema.pattern !== undefined && !matchesStringPattern(value, schema.pattern)) {
440
455
  throw new UserError(`Invalid value for "${label}": "${value}" does not match pattern "${schema.pattern}".`);
@@ -449,7 +464,7 @@ function parseJsonText(value, label) {
449
464
  return JSON.parse(value);
450
465
  }
451
466
  catch (error) {
452
- throw new InvalidArgumentError(`Invalid value for "${label}". Expected valid JSON, got ${describeReceived(value)} (parser: ${getErrorMessage(error)}).`);
467
+ throw new UserError(`Invalid value for "${label}". Expected valid JSON, got ${describeReceived(value)} (parser: ${getErrorMessage(error)}).`);
453
468
  }
454
469
  }
455
470
  function describeReceived(value) {
@@ -573,8 +588,8 @@ function parseScalarValue(value, schema, label) {
573
588
  return validateStringPattern(value, schema, label);
574
589
  case "number": {
575
590
  const parsed = Number(value);
576
- if (!isValidNumberSchemaValue(parsed, schema)) {
577
- throw new InvalidArgumentError(`Invalid value for "${label}". Expected ${getExpectedNumberDescription(schema)}, got ${describeReceived(value)}.`);
591
+ if (value.trim().length === 0 || !isValidNumberSchemaValue(parsed, schema)) {
592
+ throw new UserError(`Invalid value for "${label}". Expected ${getExpectedNumberDescription(schema)}, got ${describeReceived(value)}.`);
578
593
  }
579
594
  return parsed;
580
595
  }
@@ -616,22 +631,66 @@ function parseArrayValue(value, schema, label) {
616
631
  }
617
632
  return splitArrayInput(value).map((item) => parseScalarValue(item, itemSchema, label));
618
633
  }
619
- function isNegativeNumericArrayToken(token, schema) {
634
+ function isNegativeNumericToken(token) {
620
635
  if (!token.startsWith("-") || token.startsWith("--")) {
621
636
  return false;
622
637
  }
623
- const itemSchema = unwrapOptional(schema.item);
624
- if (itemSchema.kind !== "number") {
625
- return false;
626
- }
627
638
  const items = splitArrayInput(token);
628
- return (items.length > 0 && items.every((item) => isValidNumberSchemaValue(Number(item), itemSchema)));
639
+ return (items.length > 0 && items.every((item) => Number.isFinite(Number(item))));
629
640
  }
630
641
  function isNextArrayOptionToken(token, schema) {
631
642
  if (token === "-" && unwrapOptional(schema.item).kind === "string") {
632
643
  return false;
633
644
  }
634
- return token.startsWith("-") && !isNegativeNumericArrayToken(token, schema);
645
+ return token.startsWith("-") &&
646
+ !(unwrapOptional(schema.item).kind === "number" && isNegativeNumericToken(token));
647
+ }
648
+ function normalizeNumericArrayOptions(argv, options, numericArrayOptions) {
649
+ if (numericArrayOptions.size === 0) {
650
+ return argv;
651
+ }
652
+ const normalized = [];
653
+ let activeFlag;
654
+ for (let index = 0; index < argv.length; index += 1) {
655
+ const token = argv[index];
656
+ if (token === "--") {
657
+ normalized.push(...argv.slice(index));
658
+ break;
659
+ }
660
+ const option = options.find((candidate) => candidate.short === token || candidate.long === token);
661
+ const attachedOption = token.startsWith("-") && !token.startsWith("--") && token.length > 2
662
+ ? options.find((candidate) => candidate.short === token.slice(0, 2))
663
+ : undefined;
664
+ if (activeFlag !== undefined && option === undefined && attachedOption === undefined) {
665
+ if (token.startsWith("-") && !token.startsWith("--")) {
666
+ if (isNegativeNumericToken(token)) {
667
+ normalized.push(activeFlag);
668
+ }
669
+ else if (token !== "-") {
670
+ activeFlag = undefined;
671
+ }
672
+ }
673
+ else if (token.startsWith("--")) {
674
+ activeFlag = undefined;
675
+ }
676
+ }
677
+ else {
678
+ activeFlag = undefined;
679
+ }
680
+ normalized.push(token);
681
+ if (option !== undefined) {
682
+ const nextToken = argv[index + 1];
683
+ if (nextToken !== undefined && (option.required ||
684
+ (option.optional && !(nextToken.length > 1 && nextToken.startsWith("-"))))) {
685
+ normalized.push(nextToken);
686
+ index += 1;
687
+ }
688
+ if (numericArrayOptions.has(option)) {
689
+ activeFlag = token;
690
+ }
691
+ }
692
+ }
693
+ return normalized;
635
694
  }
636
695
  function validateArrayBounds(value, schema, label) {
637
696
  if (schema.minItems !== undefined && value.length < schema.minItems) {
@@ -642,31 +701,37 @@ function validateArrayBounds(value, schema, label) {
642
701
  }
643
702
  }
644
703
  function createOption(field, globalLongOptionFlags) {
645
- const flags = formatOptionFlags(field, globalLongOptionFlags);
646
704
  const collidesWithGlobalFlag = globalLongOptionFlags.has(field.optionFlag);
647
- if (field.schema.kind === "boolean") {
648
- if (collidesWithGlobalFlag) {
649
- return [createCommanderOption(flags, field.description, field)];
650
- }
651
- const mainOption = createCommanderOption(`${flags} [value]`, field.description, field);
652
- mainOption.preset(true);
653
- // Commander v14 passes the preset value through argParser too, so guard with typeof check
654
- mainOption.argParser((value) => (typeof value === "boolean" ? value : value));
655
- return [
656
- mainOption,
657
- createCommanderOption(`--no-${field.optionFlag.slice(2)}`, field.description, field)
705
+ const flagGroups = collidesWithGlobalFlag
706
+ ? [formatOptionFlags(field, globalLongOptionFlags)]
707
+ : [
708
+ field.shortFlag === undefined ? field.optionFlag : `-${field.shortFlag}, ${field.optionFlag}`,
709
+ ...field.longAliases
658
710
  ];
659
- }
660
- if (field.schema.kind === "array") {
661
- return [
662
- createCommanderOption(`${flags} <value...>`, field.description, field).argParser((value, previous = []) => [...previous, value])
663
- ];
664
- }
665
- if (field.schema.kind === "json") {
666
- return [createCommanderOption(`${flags} <json>`, field.description, field)];
667
- }
668
- const option = createCommanderOption(`${flags} <value>`, field.description, field);
669
- return [option];
711
+ return flagGroups.flatMap((flags, index) => {
712
+ if (field.schema.kind === "boolean") {
713
+ if (collidesWithGlobalFlag) {
714
+ return [createCommanderOption(flags, field.description, field)];
715
+ }
716
+ const mainOption = createCommanderOption(`${flags} [value]`, field.description, field);
717
+ mainOption.preset(true);
718
+ // Commander v14 passes the preset value through argParser too, so guard with typeof check
719
+ mainOption.argParser((value) => (typeof value === "boolean" ? value : value));
720
+ return index === 0
721
+ ? [mainOption, createCommanderOption(`--no-${field.optionFlag.slice(2)}`, field.description, field)]
722
+ : [mainOption];
723
+ }
724
+ if (field.schema.kind === "array") {
725
+ return [
726
+ createCommanderOption(`${flags} <value...>`, field.description, field).argParser((value, previous = []) => [...previous, value])
727
+ ];
728
+ }
729
+ if (field.schema.kind === "json") {
730
+ return [createCommanderOption(`${flags} <json>`, field.description, field)];
731
+ }
732
+ const option = createCommanderOption(`${flags} <value>`, field.description, field);
733
+ return [option];
734
+ });
670
735
  }
671
736
  function resolveCLIControls(controls) {
672
737
  const outputFormats = typeof controls?.output === "object" ? (controls.output.formats ?? {}) : {};
@@ -752,11 +817,101 @@ function createCommanderOption(flags, description, field) {
752
817
  }
753
818
  return option;
754
819
  }
755
- function hasHelpFlag(argv) {
756
- return argv.some((token) => HELP_FLAGS.has(token));
757
- }
758
- function normalizeVerboseAlias(argv) {
759
- return argv.map((token) => (token === "-v" ? "--verbose" : token));
820
+ function prepareCliArguments(program, argv, fieldLoaders, casing, controls) {
821
+ const normalized = argv.slice(0, 2);
822
+ const path = [];
823
+ let current = program;
824
+ let helpPath;
825
+ let output;
826
+ for (let index = 2; index < argv.length; index += 1) {
827
+ let token = argv[index];
828
+ if (token === "--") {
829
+ normalized.push(...argv.slice(index));
830
+ break;
831
+ }
832
+ if (HELP_FLAGS.has(token)) {
833
+ helpPath ??= [...path];
834
+ normalized.push(token);
835
+ continue;
836
+ }
837
+ if (controls.verbose && token === "-v") {
838
+ token = "--verbose";
839
+ }
840
+ const dynamicFields = fieldLoaders.get(current)?.() ?? [];
841
+ const equalsIndex = token.startsWith("--") ? token.indexOf("=") : -1;
842
+ const flag = equalsIndex < 0 ? token : token.slice(0, equalsIndex);
843
+ let option = current.options.find((candidate) => candidate.long === flag || candidate.short === flag);
844
+ let attached = equalsIndex >= 0;
845
+ if (option === undefined && token.startsWith("-") && !token.startsWith("--") && token.length > 2) {
846
+ for (let offset = 1; offset < token.length; offset += 1) {
847
+ const candidate = current.options.find((entry) => entry.short === `-${token[offset]}`);
848
+ if (candidate === undefined) {
849
+ break;
850
+ }
851
+ option = candidate;
852
+ if (candidate.required || candidate.optional) {
853
+ attached = offset < token.length - 1;
854
+ break;
855
+ }
856
+ }
857
+ }
858
+ if (option !== undefined) {
859
+ normalized.push(token);
860
+ const next = argv[index + 1];
861
+ if (flag === "--output" && controls.output) {
862
+ output = attached ? token.slice(equalsIndex + 1) : next;
863
+ }
864
+ if (!attached && next !== undefined && (option.required ||
865
+ (option.optional && !(next.length > 1 && next.startsWith("-"))))) {
866
+ normalized.push(next);
867
+ index += 1;
868
+ }
869
+ continue;
870
+ }
871
+ const child = current.commands.find((command) => command.name() === token || command.aliases().includes(token));
872
+ if (child !== undefined) {
873
+ current = child;
874
+ path.push(token);
875
+ normalized.push(token);
876
+ continue;
877
+ }
878
+ const defaultName = getDefaultCommanderCommandName(current);
879
+ const defaultCommand = current.commands.find((command) => command.name() === defaultName);
880
+ if (defaultCommand !== undefined) {
881
+ current = defaultCommand;
882
+ index -= 1;
883
+ continue;
884
+ }
885
+ if (token.startsWith("--") && equalsIndex < 0) {
886
+ const normalizedFlag = token.startsWith("--no-") ? token.slice(5) : token.slice(2);
887
+ let dynamic;
888
+ try {
889
+ dynamic = resolveDynamicOption(dynamicFields, normalizedFlag, casing);
890
+ }
891
+ catch (error) {
892
+ if (!isUserError(error)) {
893
+ throw error;
894
+ }
895
+ }
896
+ const next = argv[index + 1];
897
+ if (dynamic !== undefined && next !== undefined &&
898
+ dynamic.leaf.schema.kind !== "boolean" && dynamic.leaf.schema.kind !== "array") {
899
+ normalized.push(`${token}=${next}`);
900
+ index += 1;
901
+ continue;
902
+ }
903
+ }
904
+ if (!token.startsWith("-") && current.commands.length > 0) {
905
+ path.push(token);
906
+ }
907
+ normalized.push(token);
908
+ }
909
+ return {
910
+ argv: normalized,
911
+ ...(helpPath === undefined ? {} : {
912
+ helpArgv: [...argv.slice(0, 2), ...helpPath, ...(output === undefined ? [] : ["--output", output])]
913
+ })
914
+ };
760
915
  }
761
916
  function resolveHelpOutput(argv) {
762
917
  for (let index = 0; index < argv.length; index += 1) {
@@ -1538,12 +1693,20 @@ function formatJsonHelpOption(field, globalLongOptionFlags) {
1538
1693
  name: field.displayPath,
1539
1694
  flags: formatHelpFieldFlags(field, globalLongOptionFlags).split(", "),
1540
1695
  type: formatJsonHelpSchemaType(field.schema),
1696
+ ...(field.schema.kind === "enum" ? { choices: formatCLIEnumChoices(field.schema) } : {}),
1541
1697
  ...(field.description === undefined ? {} : { description: field.description }),
1542
1698
  required: field.requiredWhenActive,
1543
1699
  ...(field.hasDefault ? { default: field.defaultValue } : {}),
1544
1700
  ...(field.positionalIndex === undefined ? {} : { positional: true })
1545
1701
  };
1546
1702
  }
1703
+ function formatCLIEnumChoices(schema) {
1704
+ const choices = schema.values.map((value) => String(value));
1705
+ if (schema.nullable === true && !choices.includes("null")) {
1706
+ choices.push("null");
1707
+ }
1708
+ return choices;
1709
+ }
1547
1710
  function formatJsonHelpSchemaType(schema) {
1548
1711
  if (schema.kind === "enum") {
1549
1712
  return "enum";
@@ -1591,7 +1754,7 @@ async function renderGeneratedHelp(root, argv, options) {
1591
1754
  }, rootUsageName));
1592
1755
  return;
1593
1756
  }
1594
- await withOutputFormat(output, async () => {
1757
+ await withOutputFormat(toDesignSystemOutput(output), async () => {
1595
1758
  const rendered = target.node.kind === "group"
1596
1759
  ? renderGroupHelp(target.node, target.breadcrumb, "cli", casing, {
1597
1760
  controls,
@@ -1606,18 +1769,17 @@ async function renderGeneratedHelp(root, argv, options) {
1606
1769
  process.stdout.write(rendered);
1607
1770
  });
1608
1771
  }
1609
- function createNodeCommand(node, casing, globalLongOptionFlags, execute, presetsEnabled, controls, pathSegments = []) {
1772
+ function createNodeCommand(node, casing, globalLongOptionFlags, execute, presetsEnabled, controls, fieldLoaders, pathSegments = []) {
1610
1773
  const nextPathSegments = [...pathSegments, node.name];
1611
1774
  if (node.kind === "command") {
1612
1775
  if (!node.scope.includes("cli")) {
1613
1776
  return null;
1614
1777
  }
1615
1778
  const command = new CommanderCommand(node.name);
1779
+ let unknownArgv = [];
1780
+ let loadedFields;
1616
1781
  Reflect.set(command, "_toolcraftHidden", node.hidden);
1617
1782
  Reflect.set(command, "_toolcraftOriginalName", node.name);
1618
- const collected = collectFields(node.params, casing, globalLongOptionFlags);
1619
- const fields = assignPositionals(collected.fields, node.positional);
1620
- validateUniqueOptionFlags(fields, globalLongOptionFlags);
1621
1783
  if (node.description !== undefined) {
1622
1784
  command.description(node.description);
1623
1785
  }
@@ -1625,36 +1787,63 @@ function createNodeCommand(node, casing, globalLongOptionFlags, execute, presets
1625
1787
  command.addHelpCommand(false);
1626
1788
  addGlobalOptions(command, presetsEnabled, controls);
1627
1789
  command.allowExcessArguments(true);
1628
- if (collected.dynamicFields.length > 0) {
1629
- command.allowUnknownOption(true);
1630
- }
1631
- for (const field of fields) {
1632
- if (field.positionalIndex !== undefined) {
1633
- // Positionals are declared optional so resolveParams stays the single owner of argument
1634
- // validation: Commander would otherwise report a missing positional in its own words
1635
- // before prompts, defaults, and presets have had a chance to supply the value.
1636
- command.argument(field.variadicPosition === true ? `[${field.displayPath}...]` : `[${field.displayPath}]`);
1637
- continue;
1790
+ fieldLoaders.set(command, () => {
1791
+ if (loadedFields !== undefined) {
1792
+ return loadedFields;
1638
1793
  }
1639
- for (const option of createOption(field, globalLongOptionFlags)) {
1640
- command.addOption(option);
1641
- }
1642
- }
1643
- command.action(async (...args) => {
1644
- const actionCommand = args[args.length - 1];
1645
- const positionalValues = args.slice(0, -2);
1646
- await execute({
1647
- command: node,
1648
- commandPath: nextPathSegments.join("."),
1649
- casing,
1650
- dynamicFields: collected.dynamicFields,
1651
- fields,
1652
- positionalValues,
1653
- presetsEnabled,
1654
- rawArgv: actionCommand.args,
1655
- actionCommand,
1656
- variants: collected.variants
1794
+ const collected = collectFields(node.params, casing, globalLongOptionFlags);
1795
+ const fields = assignPositionals(collected.fields, node.positional);
1796
+ validateUniqueOptionFlags(fields, globalLongOptionFlags);
1797
+ if (collected.dynamicFields.length > 0) {
1798
+ command.allowUnknownOption(true);
1799
+ }
1800
+ const numericArrayOptions = new Set();
1801
+ for (const field of fields) {
1802
+ if (field.positionalIndex !== undefined) {
1803
+ // Positionals are declared optional so resolveParams stays the single owner of argument
1804
+ // validation: Commander would otherwise report a missing positional in its own words
1805
+ // before prompts, defaults, and presets have had a chance to supply the value.
1806
+ command.argument(field.variadicPosition === true ? `[${field.displayPath}...]` : `[${field.displayPath}]`);
1807
+ continue;
1808
+ }
1809
+ for (const option of createOption(field, globalLongOptionFlags)) {
1810
+ command.addOption(option);
1811
+ if (field.schema.kind === "array" && unwrapOptional(field.schema.item).kind === "number") {
1812
+ numericArrayOptions.add(option);
1813
+ }
1814
+ }
1815
+ }
1816
+ const parseOptions = command.parseOptions.bind(command);
1817
+ command.parseOptions = (argv) => {
1818
+ const parsed = parseOptions(normalizeNumericArrayOptions(argv, command.options, numericArrayOptions));
1819
+ const firstUnknownOption = parsed.unknown.findIndex((token) => token.length > 1 && token.startsWith("-") && !isNegativeNumericToken(token));
1820
+ const operandCount = firstUnknownOption < 0 ? parsed.unknown.length : firstUnknownOption;
1821
+ parsed.operands.push(...parsed.unknown.splice(0, operandCount));
1822
+ if (parsed.unknown[0] === "--") {
1823
+ parsed.operands.push(...parsed.unknown.slice(1));
1824
+ parsed.unknown.length = 0;
1825
+ }
1826
+ unknownArgv = parsed.unknown;
1827
+ return parsed;
1828
+ };
1829
+ command.action(async (...args) => {
1830
+ const actionCommand = args[args.length - 1];
1831
+ const positionalValues = actionCommand.args.slice(0, actionCommand.args.length - unknownArgv.length);
1832
+ await execute({
1833
+ command: node,
1834
+ commandPath: nextPathSegments.join("."),
1835
+ casing,
1836
+ dynamicFields: collected.dynamicFields,
1837
+ fields,
1838
+ positionalValues,
1839
+ presetsEnabled,
1840
+ rawArgv: unknownArgv,
1841
+ actionCommand,
1842
+ variants: collected.variants
1843
+ });
1657
1844
  });
1845
+ loadedFields = collected.dynamicFields;
1846
+ return loadedFields;
1658
1847
  });
1659
1848
  return command;
1660
1849
  }
@@ -1665,7 +1854,7 @@ function createNodeCommand(node, casing, globalLongOptionFlags, execute, presets
1665
1854
  .filter((child) => !isNodeVisibleInScope(child, "cli"))
1666
1855
  .flatMap((child) => getNodeCommandNames(child));
1667
1856
  const visibleChildren = node.children
1668
- .map((child) => createNodeCommand(child, casing, globalLongOptionFlags, execute, presetsEnabled, controls, nextPathSegments))
1857
+ .map((child) => createNodeCommand(child, casing, globalLongOptionFlags, execute, presetsEnabled, controls, fieldLoaders, nextPathSegments))
1669
1858
  .filter((child) => child !== null);
1670
1859
  const group = new CommanderCommand(node.name);
1671
1860
  Reflect.set(group, "_toolcraftReservedChildNames", reservedChildNames);
@@ -1773,7 +1962,8 @@ function createGlobalSnapshotOptions(presetsEnabled, versionEnabled, controls) {
1773
1962
  type: "enum",
1774
1963
  required: false,
1775
1964
  hidden: true,
1776
- description: "Print stack traces for unexpected errors."
1965
+ description: "Print stack traces for unexpected errors.",
1966
+ choices: ["trim", "raw"]
1777
1967
  });
1778
1968
  }
1779
1969
  if (controls.logLevel) {
@@ -1783,7 +1973,8 @@ function createGlobalSnapshotOptions(presetsEnabled, versionEnabled, controls) {
1783
1973
  type: "enum",
1784
1974
  required: false,
1785
1975
  hidden: true,
1786
- description: "Set runtime diagnostic log level."
1976
+ description: "Set runtime diagnostic log level.",
1977
+ choices: [...LOG_LEVELS]
1787
1978
  });
1788
1979
  }
1789
1980
  if (controls.verbose) {
@@ -1849,6 +2040,7 @@ function createFieldSnapshotOption(field, globalLongOptionFlags) {
1849
2040
  name: field.displayPath,
1850
2041
  flags: formatHelpFieldFlags(field, globalLongOptionFlags).split(", "),
1851
2042
  type: formatJsonHelpSchemaType(field.schema),
2043
+ ...(field.schema.kind === "enum" ? { choices: formatCLIEnumChoices(field.schema) } : {}),
1852
2044
  required: field.requiredWhenActive,
1853
2045
  hidden: false,
1854
2046
  ...(field.description === undefined ? {} : { description: field.description }),
@@ -1999,7 +2191,6 @@ function withPromptStreams(options, streams) {
1999
2191
  };
2000
2192
  }
2001
2193
  function throwPromptCancellation() {
2002
- cancel("Operation cancelled.");
2003
2194
  throw new UserError("Operation cancelled.");
2004
2195
  }
2005
2196
  async function promptForField(field, streams = {}) {
@@ -2044,7 +2235,7 @@ async function promptForField(field, streams = {}) {
2044
2235
  throw new UserError(`Missing required parameter "${field.displayPath}".`);
2045
2236
  }
2046
2237
  if (entered.trim().length === 0 && field.hasDefault) {
2047
- return field.defaultValue;
2238
+ return cloneDefaultValue(field.defaultValue);
2048
2239
  }
2049
2240
  if (field.schema.kind === "array") {
2050
2241
  return parseArrayValue(entered, field.schema, field.displayPath);
@@ -2112,23 +2303,6 @@ function toDesignSystemOutput(output) {
2112
2303
  }
2113
2304
  return "terminal";
2114
2305
  }
2115
- async function withOutputFormat(output, fn) {
2116
- const previous = process.env.OUTPUT_FORMAT;
2117
- process.env.OUTPUT_FORMAT = toDesignSystemOutput(output);
2118
- resetOutputFormatCache();
2119
- try {
2120
- return await fn();
2121
- }
2122
- finally {
2123
- if (previous === undefined) {
2124
- delete process.env.OUTPUT_FORMAT;
2125
- }
2126
- else {
2127
- process.env.OUTPUT_FORMAT = previous;
2128
- }
2129
- resetOutputFormatCache();
2130
- }
2131
- }
2132
2306
  function isPlainObject(value) {
2133
2307
  return typeof value === "object" && value !== null && !Array.isArray(value);
2134
2308
  }
@@ -2159,20 +2333,22 @@ function validatePresetScalarValue(value, schema, fieldPath, presetPath) {
2159
2333
  return null;
2160
2334
  }
2161
2335
  switch (schema.kind) {
2162
- case "string":
2336
+ case "string": {
2163
2337
  if (typeof value !== "string") {
2164
2338
  break;
2165
2339
  }
2166
- if (schema.minLength !== undefined && value.length < schema.minLength) {
2167
- throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}". Expected a string with length at least ${schema.minLength}, got string with length ${value.length}.`);
2340
+ const length = unicodeLength(value);
2341
+ if (schema.minLength !== undefined && length < schema.minLength) {
2342
+ throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}". Expected a string with length at least ${schema.minLength}, got string with length ${length}.`);
2168
2343
  }
2169
- if (schema.maxLength !== undefined && value.length > schema.maxLength) {
2170
- throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}". Expected a string with length at most ${schema.maxLength}, got string with length ${value.length}.`);
2344
+ if (schema.maxLength !== undefined && length > schema.maxLength) {
2345
+ throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}". Expected a string with length at most ${schema.maxLength}, got string with length ${length}.`);
2171
2346
  }
2172
2347
  if (schema.pattern !== undefined && !matchesStringPattern(value, schema.pattern)) {
2173
2348
  throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}": "${value}" does not match pattern "${schema.pattern}".`);
2174
2349
  }
2175
2350
  return value;
2351
+ }
2176
2352
  case "number":
2177
2353
  if (!isValidNumberSchemaValue(value, schema)) {
2178
2354
  break;
@@ -2200,6 +2376,9 @@ function validatePresetFieldValue(value, field, presetPath) {
2200
2376
  if (field.schema.kind !== "array") {
2201
2377
  return validatePresetScalarValue(value, field.schema, field.displayPath, presetPath);
2202
2378
  }
2379
+ if (value === null && field.schema.nullable === true) {
2380
+ return null;
2381
+ }
2203
2382
  const itemSchema = unwrapOptional(field.schema.item);
2204
2383
  if (itemSchema.kind === "array" || itemSchema.kind === "object") {
2205
2384
  throw new UserError(`Array parameter "${field.displayPath}" must use scalar items.`);
@@ -2215,7 +2394,7 @@ function validatePresetFieldValue(value, field, presetPath) {
2215
2394
  }
2216
2395
  return value.map((item) => validatePresetScalarValue(item, itemSchema, field.displayPath, presetPath));
2217
2396
  }
2218
- async function loadPresetValues(fields, presetPath) {
2397
+ async function loadPresetValues(fields, dynamicFields, presetPath) {
2219
2398
  let rawPreset;
2220
2399
  try {
2221
2400
  rawPreset = await readFile(presetPath, {
@@ -2242,7 +2421,10 @@ async function loadPresetValues(fields, presetPath) {
2242
2421
  throw new UserError(`Preset file "${presetPath}" must contain a JSON object.`);
2243
2422
  }
2244
2423
  const fieldByPath = new Map(fields.map((field) => [field.displayPath, field]));
2424
+ const dynamicFieldByPath = new Map(dynamicFields.map((field) => [field.displayPath, field]));
2425
+ const allFields = [...fields, ...dynamicFields];
2245
2426
  const presetValues = {};
2427
+ const dynamicValues = new Map();
2246
2428
  function visitObject(current, path) {
2247
2429
  for (const [key, value] of Object.entries(current)) {
2248
2430
  const nextPath = [...path, key];
@@ -2252,7 +2434,18 @@ async function loadPresetValues(fields, presetPath) {
2252
2434
  presetValues[field.optionAttribute] = validatePresetFieldValue(value, field, presetPath);
2253
2435
  continue;
2254
2436
  }
2255
- if (!hasNestedField(fields, nextPath)) {
2437
+ const dynamicField = dynamicFieldByPath.get(displayPath);
2438
+ if (dynamicField !== undefined) {
2439
+ const validation = validateSchema(dynamicField.schema, value, { defaults: "all" });
2440
+ if (!validation.ok) {
2441
+ const issue = validation.issues[0];
2442
+ const issuePath = [displayPath, ...issue.path].join(".");
2443
+ throw new UserError(`Preset file "${presetPath}" has an invalid value for "${issuePath}". ${issue.message}`);
2444
+ }
2445
+ dynamicValues.set(dynamicField.id, validation.value);
2446
+ continue;
2447
+ }
2448
+ if (!hasNestedField(allFields, nextPath)) {
2256
2449
  throw new UserError(`Preset file "${presetPath}" contains unknown parameter "${displayPath}".`);
2257
2450
  }
2258
2451
  if (!isPlainObject(value)) {
@@ -2262,7 +2455,7 @@ async function loadPresetValues(fields, presetPath) {
2262
2455
  }
2263
2456
  }
2264
2457
  visitObject(parsedPreset, []);
2265
- return presetValues;
2458
+ return { fields: presetValues, dynamic: dynamicValues };
2266
2459
  }
2267
2460
  function isNumericFixtureSelector(value) {
2268
2461
  if (value.length === 0) {
@@ -2576,15 +2769,16 @@ function isHumanInLoopPending(result) {
2576
2769
  typeof result.message === "string" &&
2577
2770
  typeof result.enqueuedAt === "string");
2578
2771
  }
2579
- function renderHumanInLoopPending(pending, rootUsageName) {
2580
- process.stdout.write(`✓ Queued for human approval (id: ${pending.approvalId})\n` +
2772
+ function renderHumanInLoopPending(pending, rootUsageName, outputEmitter) {
2773
+ const message = `✓ Queued for human approval (id: ${pending.approvalId})\n` +
2581
2774
  ` Message: ${pending.message}\n` +
2582
- ` Track: ${rootUsageName} approvals show --approval-id ${pending.approvalId}\n`);
2583
- }
2584
- function renderApprovalDeclined(error) {
2585
- const logger = createLogger();
2586
- logger.error(error.message);
2587
- process.exitCode = 1;
2775
+ ` Track: ${rootUsageName} approvals show --approval-id ${pending.approvalId}`;
2776
+ if (outputEmitter === undefined) {
2777
+ process.stdout.write(`${message}\n`);
2778
+ }
2779
+ else {
2780
+ outputEmitter(message);
2781
+ }
2588
2782
  }
2589
2783
  function renderCliErrorPattern(pattern, outputEmitter) {
2590
2784
  const logger = createLogger(outputEmitter);
@@ -2633,7 +2827,7 @@ function renderCliErrorPattern(pattern, outputEmitter) {
2633
2827
  process.exitCode = 1;
2634
2828
  }
2635
2829
  function getNestedValue(target, path) {
2636
- return path.reduce((current, segment) => current !== null && typeof current === "object"
2830
+ return path.reduce((current, segment) => current !== null && typeof current === "object" && Object.prototype.hasOwnProperty.call(current, segment)
2637
2831
  ? current[segment]
2638
2832
  : undefined, target);
2639
2833
  }
@@ -2800,6 +2994,10 @@ function resolveDynamicLeaf(schema, rawSegments, casing, outputPath = [], displa
2800
2994
  : [qualifyDisplayPath(displayPathPrefix, toDisplayPath(displayPath))])}`);
2801
2995
  }
2802
2996
  }
2997
+ function formatFieldValidationIssue(issue, displayPath) {
2998
+ const path = [displayPath, ...issue.path].filter((part) => part !== "").join(".") || "parameters";
2999
+ return { path, message: `Invalid value for "${path}". ${issue.message}` };
3000
+ }
2803
3001
  function finalizeDynamicValue(schema, value, displayPath, errors) {
2804
3002
  const unwrappedSchema = unwrapOptional(schema);
2805
3003
  if (value === undefined) {
@@ -2813,8 +3011,14 @@ function finalizeDynamicValue(schema, value, displayPath, errors) {
2813
3011
  case "number":
2814
3012
  case "boolean":
2815
3013
  case "enum":
2816
- case "json":
2817
3014
  return value;
3015
+ case "json": {
3016
+ const validation = validateSchema(unwrappedSchema, value);
3017
+ if (!validation.ok) {
3018
+ errors.push(...validation.issues.map((issue) => formatFieldValidationIssue(issue, displayPath)));
3019
+ }
3020
+ return value;
3021
+ }
2818
3022
  case "array": {
2819
3023
  const itemSchema = unwrapOptional(unwrappedSchema.item);
2820
3024
  if (itemSchema.kind !== "object") {
@@ -2862,7 +3066,7 @@ function finalizeDynamicValue(schema, value, displayPath, errors) {
2862
3066
  const childDisplayPath = displayPath.length === 0 ? key : `${displayPath}.${key}`;
2863
3067
  if (childValue === undefined) {
2864
3068
  if (childSchema.default !== undefined) {
2865
- result[key] = childSchema.default;
3069
+ result[key] = cloneDefaultValue(childSchema.default);
2866
3070
  continue;
2867
3071
  }
2868
3072
  if (rawChildSchema.kind === "optional") {
@@ -2903,7 +3107,7 @@ function formatCliSchemaKind(kind) {
2903
3107
  return kind === "oneOf" ? "oneof" : kind;
2904
3108
  }
2905
3109
  function formatUnsupportedDynamicSchemaMessage(kind, displayPath) {
2906
- return `Unsupported parameter type "${kind}" for "${displayPath}". Supported types: string, number, integer, boolean, array, object, enum, oneof.`;
3110
+ return `Unsupported CLI argument shape for "${displayPath}" (type "${kind}").`;
2907
3111
  }
2908
3112
  function qualifyDisplayPath(prefix, displayPath) {
2909
3113
  if (prefix.length === 0) {
@@ -2914,13 +3118,38 @@ function qualifyDisplayPath(prefix, displayPath) {
2914
3118
  }
2915
3119
  return `${prefix}.${displayPath}`;
2916
3120
  }
3121
+ function resolveDynamicOption(dynamicFields, flagName, casing) {
3122
+ const flagPath = flagName.split(".");
3123
+ const match = [...dynamicFields]
3124
+ .sort((left, right) => right.optionPath.length - left.optionPath.length)
3125
+ .find((field) => {
3126
+ const optionPath = field.optionPath.map((segment) => formatSegment(segment, casing));
3127
+ return flagPath.length > optionPath.length &&
3128
+ optionPath.every((segment, index) => flagPath[index] === segment);
3129
+ });
3130
+ if (match === undefined) {
3131
+ return undefined;
3132
+ }
3133
+ return {
3134
+ match,
3135
+ leaf: resolveDynamicLeaf(match.schema, flagPath.slice(match.optionPath.length), casing, [], [], match.displayPath)
3136
+ };
3137
+ }
2917
3138
  function parseDynamicValues(dynamicFields, rawArgv, casing, errors) {
2918
3139
  const rawValues = new Map();
2919
3140
  const providedFieldIds = new Set();
2920
- const sortedFields = [...dynamicFields].sort((left, right) => right.optionPath.length - left.optionPath.length);
3141
+ const positionals = [];
2921
3142
  for (let index = 0; index < rawArgv.length; index += 1) {
2922
3143
  const token = rawArgv[index] ?? "";
3144
+ if (token === "--") {
3145
+ positionals.push(...rawArgv.slice(index + 1));
3146
+ break;
3147
+ }
2923
3148
  if (!token.startsWith("--")) {
3149
+ if (token.length > 1 && token.startsWith("-") && !isNegativeNumericToken(token)) {
3150
+ throw new UserError(`Unknown option "${token}".`);
3151
+ }
3152
+ positionals.push(token);
2924
3153
  continue;
2925
3154
  }
2926
3155
  const negated = token.startsWith("--no-");
@@ -2928,18 +3157,11 @@ function parseDynamicValues(dynamicFields, rawArgv, casing, errors) {
2928
3157
  const equalsIndex = normalized.indexOf("=");
2929
3158
  const flagName = equalsIndex >= 0 ? normalized.slice(2, equalsIndex) : normalized.slice(2);
2930
3159
  const inlineValue = equalsIndex >= 0 ? normalized.slice(equalsIndex + 1) : undefined;
2931
- const flagPath = flagName.split(".");
2932
- const match = sortedFields.find((field) => {
2933
- const optionPath = field.optionPath.map((segment) => formatSegment(segment, casing));
2934
- return (flagPath.length > optionPath.length &&
2935
- optionPath.every((segment, segmentIndex) => flagPath[segmentIndex] === segment));
2936
- });
2937
- if (match === undefined) {
3160
+ const dynamic = resolveDynamicOption(dynamicFields, flagName, casing);
3161
+ if (dynamic === undefined) {
2938
3162
  throw new UserError(`Unknown parameter "${flagName}". ${formatAvailableList(dynamicFields.map((field) => field.optionPathDisplay))}`);
2939
3163
  }
2940
- const optionPath = match.optionPath.map((segment) => formatSegment(segment, casing));
2941
- const remainder = flagPath.slice(optionPath.length);
2942
- const leaf = resolveDynamicLeaf(match.schema, remainder, casing, [], [], match.displayPath);
3164
+ const { match, leaf } = dynamic;
2943
3165
  const rawStore = rawValues.get(match.id) ?? {};
2944
3166
  const label = `${match.displayPath}.${leaf.displayPath}`.replace(/^\./u, "");
2945
3167
  const parsed = negated && leaf.schema.kind === "boolean"
@@ -2955,6 +3177,7 @@ function parseDynamicValues(dynamicFields, rawArgv, casing, errors) {
2955
3177
  }
2956
3178
  return {
2957
3179
  providedFieldIds,
3180
+ positionals,
2958
3181
  values: new Map(dynamicFields
2959
3182
  .filter((field) => rawValues.has(field.id))
2960
3183
  .map((field) => [
@@ -2963,9 +3186,10 @@ function parseDynamicValues(dynamicFields, rawArgv, casing, errors) {
2963
3186
  ]))
2964
3187
  };
2965
3188
  }
2966
- async function enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, providedDynamicFieldIds, providedFieldIds, shouldPrompt, errors) {
3189
+ async function enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, providedDynamicFieldIds, providedFieldIds, shouldPrompt, errors, promptStreams) {
2967
3190
  const fieldById = new Map(fields.map((field) => [field.id, field]));
2968
3191
  const dynamicFieldById = new Map(dynamicFields.map((field) => [field.id, field]));
3192
+ const activeBranches = new Map();
2969
3193
  const getAvailableBranchParameters = (branch) => [
2970
3194
  ...branch.fieldIds
2971
3195
  .map((fieldId) => fieldById.get(fieldId))
@@ -2977,12 +3201,16 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
2977
3201
  .map((field) => field.optionPathDisplay)
2978
3202
  ];
2979
3203
  for (const variant of variants) {
3204
+ if (variant.parent !== undefined && activeBranches.get(variant.parent.id) !== variant.parent.branchId) {
3205
+ continue;
3206
+ }
2980
3207
  let selectedBranchId = resolvedFieldValues.get(variant.controlFieldId);
2981
3208
  if (selectedBranchId === undefined && shouldPrompt) {
2982
3209
  const controlField = fieldById.get(variant.controlFieldId);
2983
3210
  if (controlField !== undefined) {
2984
- selectedBranchId = await promptForField(controlField);
3211
+ selectedBranchId = await promptForField(controlField, promptStreams);
2985
3212
  resolvedFieldValues.set(controlField.id, selectedBranchId);
3213
+ providedFieldIds.add(controlField.id);
2986
3214
  if (!controlField.synthetic) {
2987
3215
  setNestedValue(params, controlField.path, selectedBranchId);
2988
3216
  }
@@ -3037,16 +3265,33 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
3037
3265
  if (hasInvalidBranchParameter) {
3038
3266
  continue;
3039
3267
  }
3268
+ activeBranches.set(variant.id, selectedBranch.branchId);
3269
+ for (const fieldId of selectedBranch.fieldIds) {
3270
+ const field = fieldById.get(fieldId);
3271
+ if (field?.variantId !== variant.id || !field.hasDefault || getNestedValue(params, field.path) !== undefined) {
3272
+ continue;
3273
+ }
3274
+ const value = cloneDefaultValue(field.defaultValue);
3275
+ resolvedFieldValues.set(field.id, value);
3276
+ setNestedValue(params, field.path, value);
3277
+ }
3278
+ for (const fieldId of selectedBranch.dynamicFieldIds) {
3279
+ const field = dynamicFieldById.get(fieldId);
3280
+ if (field?.variantId !== variant.id || !field.hasDefault || getNestedValue(params, field.path) !== undefined) {
3281
+ continue;
3282
+ }
3283
+ setNestedValue(params, field.path, cloneDefaultValue(field.defaultValue));
3284
+ }
3040
3285
  for (const fieldId of selectedBranch.requiredFieldIds) {
3041
3286
  const field = fieldById.get(fieldId);
3042
- if (field === undefined || field.synthetic) {
3287
+ if (field?.variantId !== variant.id || field.synthetic) {
3043
3288
  continue;
3044
3289
  }
3045
3290
  if (getNestedValue(params, field.path) !== undefined) {
3046
3291
  continue;
3047
3292
  }
3048
3293
  if (shouldPrompt) {
3049
- const promptedValue = await promptForField(field);
3294
+ const promptedValue = await promptForField(field, promptStreams);
3050
3295
  resolvedFieldValues.set(field.id, promptedValue);
3051
3296
  setNestedValue(params, field.path, promptedValue);
3052
3297
  providedFieldIds.add(field.id);
@@ -3059,7 +3304,7 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
3059
3304
  }
3060
3305
  for (const fieldId of selectedBranch.requiredDynamicFieldIds) {
3061
3306
  const field = dynamicFieldById.get(fieldId);
3062
- if (field === undefined) {
3307
+ if (field?.variantId !== variant.id) {
3063
3308
  continue;
3064
3309
  }
3065
3310
  if (getNestedValue(params, field.path) !== undefined) {
@@ -3074,18 +3319,27 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
3074
3319
  }
3075
3320
  async function resolveParams(fields, dynamicFields, variants, positionalValues, optionValues, rawArgv, casing, presetPath, shouldPrompt, missingParameterContext, promptStreams) {
3076
3321
  const params = {};
3322
+ const errors = [];
3323
+ const dynamicResults = parseDynamicValues(dynamicFields, rawArgv, casing, errors);
3324
+ const positionalTokens = [...positionalValues, ...dynamicResults.positionals];
3325
+ const positionalFields = fields.filter((field) => field.positionalIndex !== undefined);
3326
+ if (!positionalFields.some((field) => field.variadicPosition === true) &&
3327
+ positionalTokens.length > positionalFields.length) {
3328
+ throw new UserError(`Unexpected arguments: ${positionalTokens.slice(positionalFields.length).map((token) => JSON.stringify(token)).join(", ")}.`);
3329
+ }
3077
3330
  const presetValues = typeof presetPath === "string" && presetPath.length > 0
3078
- ? await loadPresetValues(fields, presetPath)
3079
- : {};
3331
+ ? await loadPresetValues(fields, dynamicFields, presetPath)
3332
+ : { fields: {}, dynamic: new Map() };
3080
3333
  const providedFieldIds = new Set();
3081
3334
  const resolvedFieldValues = new Map();
3082
- const errors = [];
3083
3335
  for (const field of fields) {
3084
3336
  let value;
3085
3337
  let resolvedMissing = false;
3086
3338
  let source;
3087
3339
  if (field.positionalIndex !== undefined) {
3088
- const positionalValue = positionalValues[field.positionalIndex];
3340
+ const positionalValue = field.variadicPosition === true
3341
+ ? positionalTokens.slice(field.positionalIndex)
3342
+ : positionalTokens[field.positionalIndex];
3089
3343
  if (field.schema.kind === "array") {
3090
3344
  if (Array.isArray(positionalValue) && positionalValue.length > 0) {
3091
3345
  const itemSchema = unwrapOptional(field.schema.item);
@@ -3096,7 +3350,7 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
3096
3350
  source = "positional";
3097
3351
  }
3098
3352
  }
3099
- else if (typeof positionalValue === "string" && positionalValue.length > 0) {
3353
+ else if (typeof positionalValue === "string") {
3100
3354
  value = parseFieldInputValue(positionalValue, field.schema, field.displayPath);
3101
3355
  source = "positional";
3102
3356
  }
@@ -3122,8 +3376,8 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
3122
3376
  source = "option";
3123
3377
  }
3124
3378
  if (value === undefined &&
3125
- Object.prototype.hasOwnProperty.call(presetValues, field.optionAttribute)) {
3126
- value = presetValues[field.optionAttribute];
3379
+ Object.prototype.hasOwnProperty.call(presetValues.fields, field.optionAttribute)) {
3380
+ value = presetValues.fields[field.optionAttribute];
3127
3381
  source = "preset";
3128
3382
  }
3129
3383
  if (source === "option") {
@@ -3178,8 +3432,8 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
3178
3432
  value = await promptForField(field, promptStreams);
3179
3433
  source = "prompt";
3180
3434
  }
3181
- if (value === undefined && field.hasDefault) {
3182
- value = field.defaultValue;
3435
+ if (value === undefined && field.hasDefault && field.variantId === undefined) {
3436
+ value = cloneDefaultValue(field.defaultValue);
3183
3437
  source = "default";
3184
3438
  }
3185
3439
  if (value === undefined) {
@@ -3192,6 +3446,14 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
3192
3446
  });
3193
3447
  continue;
3194
3448
  }
3449
+ if (field.schema.kind === "json") {
3450
+ const validation = validateSchema(field.schema, value);
3451
+ if (!validation.ok) {
3452
+ errors.push(...validation.issues.map((issue) => formatFieldValidationIssue(issue, field.displayPath)));
3453
+ continue;
3454
+ }
3455
+ value = validation.value;
3456
+ }
3195
3457
  resolvedFieldValues.set(field.id, value);
3196
3458
  if (source !== undefined && source !== "default") {
3197
3459
  providedFieldIds.add(field.id);
@@ -3200,16 +3462,15 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
3200
3462
  setNestedValue(params, field.path, value);
3201
3463
  }
3202
3464
  }
3203
- const dynamicResults = dynamicFields.length > 0
3204
- ? parseDynamicValues(dynamicFields, rawArgv, casing, errors)
3205
- : {
3206
- providedFieldIds: new Set(),
3207
- values: new Map()
3208
- };
3209
3465
  for (const field of dynamicFields) {
3210
3466
  let value = dynamicResults.values.get(field.id);
3211
- if (value === undefined && field.hasDefault) {
3212
- value = field.defaultValue;
3467
+ if (presetValues.dynamic.has(field.id)) {
3468
+ if (value === undefined)
3469
+ value = presetValues.dynamic.get(field.id);
3470
+ dynamicResults.providedFieldIds.add(field.id);
3471
+ }
3472
+ if (value === undefined && field.hasDefault && field.variantId === undefined) {
3473
+ value = cloneDefaultValue(field.defaultValue);
3213
3474
  }
3214
3475
  if (value === undefined) {
3215
3476
  if (field.optional || field.variantId !== undefined) {
@@ -3223,17 +3484,24 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
3223
3484
  }
3224
3485
  setNestedValue(params, field.path, value);
3225
3486
  }
3226
- await enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, dynamicResults.providedFieldIds, providedFieldIds, shouldPrompt, errors);
3487
+ await enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, dynamicResults.providedFieldIds, providedFieldIds, shouldPrompt, errors, promptStreams);
3227
3488
  throwValidationErrors(errors);
3228
3489
  return params;
3229
3490
  }
3230
3491
  function getResolvedFlags(command) {
3231
- const flags = command.optsWithGlobals();
3492
+ const flags = {};
3493
+ const commands = [];
3494
+ for (let current = command; current !== null; current = current.parent) {
3495
+ commands.unshift(current);
3496
+ }
3497
+ for (const current of commands) {
3498
+ Object.assign(flags, current.opts());
3499
+ }
3232
3500
  return flags;
3233
3501
  }
3234
3502
  async function executeCommand(state, rootUsageName, services, requirementOptions, runtimeFetch, humanInLoop, runtimeEnv, runtimeFs, outputEmitter, outputFormats, promptStreams, diagnosticsOptions, onErrorReportContext) {
3235
3503
  const logger = createLogger(outputEmitter);
3236
- const optionValues = state.actionCommand.optsWithGlobals();
3504
+ const optionValues = getResolvedFlags(state.actionCommand);
3237
3505
  const resolvedFlags = optionValues;
3238
3506
  const output = resolveOutput(resolvedFlags);
3239
3507
  const primitives = {
@@ -3280,9 +3548,15 @@ async function executeCommand(state, rootUsageName, services, requirementOptions
3280
3548
  let runtimeSecrets;
3281
3549
  let resolvedParams;
3282
3550
  try {
3283
- await withOutputFormat(output, async () => {
3551
+ await withOutputFormat(toDesignSystemOutput(output), async () => {
3284
3552
  await assertCommandRequirements(state.command, preflightContext, runtime.requirementOptions);
3285
3553
  const params = await resolveParams(state.fields, state.dynamicFields, state.variants, state.positionalValues, optionValues, state.rawArgv, state.casing, state.presetsEnabled ? resolvedFlags.preset : undefined, shouldPrompt, missingParameterContext, promptStreams);
3554
+ const paramsSchema = state.command.params;
3555
+ if (paramsSchema !== undefined && paramsSchema[nativeJsonSchema] !== undefined) {
3556
+ const validation = validateSchema(paramsSchema, params);
3557
+ if (!validation.ok)
3558
+ throwValidationErrors(validation.issues.map((issue) => formatFieldValidationIssue(issue, "")));
3559
+ }
3286
3560
  resolvedParams = params;
3287
3561
  runtimeSecrets = runtime.secrets;
3288
3562
  const context = {
@@ -3314,7 +3588,7 @@ async function executeCommand(state, rootUsageName, services, requirementOptions
3314
3588
  }
3315
3589
  });
3316
3590
  const interrupt = () => {
3317
- void stream.cancel(new UserError("Operation cancelled."));
3591
+ void stream.cancel(new UserError("Operation cancelled.")).catch(() => undefined);
3318
3592
  };
3319
3593
  process.once("SIGINT", interrupt);
3320
3594
  try {
@@ -3343,8 +3617,7 @@ async function executeCommand(state, rootUsageName, services, requirementOptions
3343
3617
  }
3344
3618
  if (state.command.confirm &&
3345
3619
  !state.command.humanInLoop &&
3346
- !resolvedFlags.yes &&
3347
- process.stdin.isTTY) {
3620
+ shouldPrompt) {
3348
3621
  for (const field of state.fields) {
3349
3622
  const value = field.path.reduce((current, segment) => current && typeof current === "object"
3350
3623
  ? current[segment]
@@ -3355,10 +3628,10 @@ async function executeCommand(state, rootUsageName, services, requirementOptions
3355
3628
  }
3356
3629
  const proceed = await confirm({
3357
3630
  message: "Proceed?",
3358
- initialValue: true
3631
+ initialValue: true,
3632
+ ...promptStreams
3359
3633
  });
3360
3634
  if (isCancel(proceed)) {
3361
- cancel("Operation cancelled.");
3362
3635
  throw new UserError("Operation cancelled.");
3363
3636
  }
3364
3637
  if (proceed !== true) {
@@ -3371,11 +3644,12 @@ async function executeCommand(state, rootUsageName, services, requirementOptions
3371
3644
  if (output === "rich" && runtime.isFixture) {
3372
3645
  writeRichHeader(`${state.command.name} (fixture)`);
3373
3646
  }
3374
- if (isHumanInLoopPending(result)) {
3375
- renderHumanInLoopPending(result, rootUsageName);
3647
+ const pendingApproval = isHumanInLoopPending(result);
3648
+ if (pendingApproval && output === "rich") {
3649
+ renderHumanInLoopPending(result, rootUsageName, outputEmitter);
3376
3650
  return;
3377
3651
  }
3378
- const renderStatus = renderCLIResult(state.command, state.commandPath, result, output, primitives, outputFormats, outputEmitter === undefined
3652
+ const renderStatus = renderCLIResult(pendingApproval ? { ...state.command, render: undefined } : state.command, state.commandPath, result, output, primitives, outputFormats, outputEmitter === undefined
3379
3653
  ? undefined
3380
3654
  : (chunk) => outputEmitter(chunk.endsWith("\n") ? chunk.slice(0, -1) : chunk), outputEmitter);
3381
3655
  if (renderStatus.mcpError) {
@@ -3610,7 +3884,7 @@ function renderHttpError(error, options) {
3610
3884
  }
3611
3885
  async function handleRunError(error, options) {
3612
3886
  const logger = createLogger(options.outputEmitter);
3613
- await withOutputFormat(options.output, async () => {
3887
+ await withOutputFormat(toDesignSystemOutput(options.output), async () => {
3614
3888
  if (isUserError(error)) {
3615
3889
  renderCliErrorPattern(options.userErrorPattern === "definition"
3616
3890
  ? {
@@ -3910,26 +4184,30 @@ function getDefaultCommanderCommandName(command) {
3910
4184
  ? candidate._defaultCommandName
3911
4185
  : undefined;
3912
4186
  }
3913
- function configureCommanderSuggestionOutput(command) {
4187
+ function configureCommanderSuggestionOutput(command, version) {
3914
4188
  command.exitOverride();
4189
+ command.enablePositionalOptions();
4190
+ command.helpOption(false);
4191
+ if (version !== undefined && !command.options.some((option) => option.long === "--version")) {
4192
+ command.version(version, "--version");
4193
+ }
3915
4194
  command.configureOutput({
3916
4195
  // Every Commander error throws through exitOverride and is rendered once by handleRunError
3917
4196
  // in the design-system error pattern; writing it here as well double-reports one mistake.
3918
4197
  outputError: () => { }
3919
4198
  });
3920
- command.commands.forEach((child) => configureCommanderSuggestionOutput(child));
4199
+ command.commands.forEach((child) => configureCommanderSuggestionOutput(child, version));
3921
4200
  }
3922
4201
  export async function runCLI(roots, options = {}) {
3923
4202
  enableSourceMaps();
3924
4203
  const controls = resolveCLIControls(options.controls);
3925
- const argv = controls.verbose
3926
- ? normalizeVerboseAlias([...(options.argv ?? process.argv)])
3927
- : [...(options.argv ?? process.argv)];
4204
+ let argv = [...(options.argv ?? process.argv)];
3928
4205
  const rootUsageName = options.rootUsageName ?? inferProgramName(argv);
3929
4206
  let lastActionCommand;
3930
4207
  let resolvedCommandPath = "";
3931
4208
  let program;
3932
4209
  let version;
4210
+ let proxyCleanup;
3933
4211
  let userErrorPattern = "definition";
3934
4212
  let errorReportContext;
3935
4213
  try {
@@ -3937,7 +4215,9 @@ export async function runCLI(roots, options = {}) {
3937
4215
  const root = mergeApprovalsRoot(normalizedRoot, options);
3938
4216
  assertHumanInLoopWired(root, options.humanInLoop);
3939
4217
  if (hasMcpProxyConfig(root)) {
3940
- await (await importOptionalModule(optionalModulePaths.mcpProxy)).resolveMcpProxies(root, { projectRoot: options.projectRoot });
4218
+ const proxyRuntime = await importOptionalModule(optionalModulePaths.mcpProxy);
4219
+ await proxyRuntime.resolveMcpProxies(root, { projectRoot: options.projectRoot });
4220
+ proxyCleanup = { dispose: proxyRuntime.disposeMcpProxies, root };
3941
4221
  }
3942
4222
  const casing = options.casing ?? "kebab";
3943
4223
  const services = (options.services ?? {});
@@ -3954,11 +4234,6 @@ export async function runCLI(roots, options = {}) {
3954
4234
  env: options.env
3955
4235
  };
3956
4236
  validateServices(services);
3957
- if (hasHelpFlag(argv)) {
3958
- userErrorPattern = "usage";
3959
- await renderGeneratedHelp(root, argv, { ...options, version });
3960
- return;
3961
- }
3962
4237
  if (argv.length <= 2 && root.default?.scope.includes("cli") !== true) {
3963
4238
  userErrorPattern = "usage";
3964
4239
  await renderGeneratedHelp(root, argv, { ...options, version });
@@ -3971,6 +4246,7 @@ export async function runCLI(roots, options = {}) {
3971
4246
  program.addHelpCommand(false);
3972
4247
  const presetsEnabled = options.presets === true;
3973
4248
  const globalLongOptionFlags = getGlobalLongOptionFlags(presetsEnabled, version !== undefined, controls);
4249
+ const fieldLoaders = new Map();
3974
4250
  addGlobalOptions(program, presetsEnabled, controls);
3975
4251
  if (version !== undefined) {
3976
4252
  program.version(version, "--version");
@@ -3996,7 +4272,7 @@ export async function runCLI(roots, options = {}) {
3996
4272
  .filter((candidate) => isNodeVisibleInScope(candidate, "cli"))
3997
4273
  .map((candidate) => candidate.name));
3998
4274
  for (const child of root.children) {
3999
- const command = createNodeCommand(child, casing, globalLongOptionFlags, execute, presetsEnabled, controls);
4275
+ const command = createNodeCommand(child, casing, globalLongOptionFlags, execute, presetsEnabled, controls, fieldLoaders);
4000
4276
  if (command === null) {
4001
4277
  continue;
4002
4278
  }
@@ -4005,25 +4281,42 @@ export async function runCLI(roots, options = {}) {
4005
4281
  (command.name() === root.default.name || command.aliases().includes(root.default.name));
4006
4282
  addCommanderChild(program, command, isDefaultChild, rootChildNames);
4007
4283
  }
4008
- configureCommanderSuggestionOutput(program);
4284
+ configureCommanderSuggestionOutput(program, version);
4285
+ const prepared = prepareCliArguments(program, argv, fieldLoaders, casing, controls);
4286
+ argv = prepared.argv;
4287
+ if (prepared.helpArgv !== undefined) {
4288
+ userErrorPattern = "usage";
4289
+ await renderGeneratedHelp(root, prepared.helpArgv, { ...options, version });
4290
+ return;
4291
+ }
4292
+ for (const loadFields of fieldLoaders.values()) {
4293
+ loadFields();
4294
+ }
4009
4295
  const unknownCommand = findUnknownCommanderCommand(program, argv);
4010
4296
  if (unknownCommand !== undefined) {
4011
- createLogger().error(appendUsagePointer(formatUnknownCommandMessage(unknownCommand.input, unknownCommand.currentCommand), {
4012
- rootUsageName,
4013
- commandPath: unknownCommand.commandPath
4014
- }));
4015
- process.exitCode = 1;
4297
+ await withOutputFormat(toDesignSystemOutput(resolveOutputFromArgv(argv, controls.outputFormats)), async () => {
4298
+ renderCliErrorPattern({
4299
+ kind: "usage",
4300
+ message: formatUnknownCommandMessage(unknownCommand.input, unknownCommand.currentCommand),
4301
+ rootUsageName,
4302
+ commandPath: unknownCommand.commandPath
4303
+ }, options.outputEmitter);
4304
+ });
4016
4305
  return;
4017
4306
  }
4018
4307
  userErrorPattern = "usage";
4019
4308
  await program.parseAsync(argv);
4020
4309
  }
4021
4310
  catch (error) {
4311
+ const resolvedFlags = lastActionCommand ? getResolvedFlags(lastActionCommand) : undefined;
4022
4312
  if (error instanceof ApprovalDeclinedError) {
4023
- renderApprovalDeclined(error);
4313
+ await withOutputFormat(toDesignSystemOutput(resolvedFlags !== undefined
4314
+ ? resolveOutput(resolvedFlags)
4315
+ : resolveOutputFromArgv(argv, controls.outputFormats)), async () => {
4316
+ renderCliErrorPattern({ kind: "runtime-user", message: error.message }, options.outputEmitter);
4317
+ });
4024
4318
  return;
4025
4319
  }
4026
- const resolvedFlags = lastActionCommand ? getResolvedFlags(lastActionCommand) : undefined;
4027
4320
  const report = await writeErrorReport({
4028
4321
  argv,
4029
4322
  command: errorReportContext?.command,
@@ -4059,4 +4352,15 @@ export async function runCLI(roots, options = {}) {
4059
4352
  userErrorPattern: errorReportContext?.params === undefined ? userErrorPattern : "runtime-user"
4060
4353
  });
4061
4354
  }
4355
+ finally {
4356
+ if (proxyCleanup !== undefined) {
4357
+ try {
4358
+ await proxyCleanup.dispose(proxyCleanup.root);
4359
+ }
4360
+ catch (error) {
4361
+ process.exitCode = 1;
4362
+ process.stderr.write(`Failed to close MCP proxy connections: ${error instanceof Error ? error.message : String(error)}\n`);
4363
+ }
4364
+ }
4365
+ }
4062
4366
  }