toolcraft 0.0.17 → 0.0.19

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 (109) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.js +833 -124
  3. package/dist/error-report.d.ts +39 -0
  4. package/dist/error-report.js +330 -0
  5. package/dist/human-in-loop/approval-tasks.js +11 -8
  6. package/dist/human-in-loop/approvals-commands.js +21 -20
  7. package/dist/human-in-loop/default-provider.js +5 -3
  8. package/dist/human-in-loop/runner.js +45 -4
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js +55 -35
  11. package/dist/json-schema-converter.d.ts +1 -0
  12. package/dist/json-schema-converter.js +102 -52
  13. package/dist/mcp-proxy.d.ts +1 -0
  14. package/dist/mcp-proxy.js +13 -6
  15. package/dist/mcp.d.ts +2 -0
  16. package/dist/mcp.js +131 -55
  17. package/dist/sdk.d.ts +4 -2
  18. package/dist/sdk.js +132 -48
  19. package/dist/source-snippet.d.ts +8 -0
  20. package/dist/source-snippet.js +42 -0
  21. package/dist/stack-trim.d.ts +4 -0
  22. package/dist/stack-trim.js +70 -0
  23. package/dist/suggest.d.ts +4 -0
  24. package/dist/suggest.js +46 -0
  25. package/dist/user-error.d.ts +3 -0
  26. package/dist/user-error.js +7 -1
  27. package/dist/validation-errors.d.ts +5 -0
  28. package/dist/validation-errors.js +18 -0
  29. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +3 -3
  30. package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.d.ts +3 -3
  31. package/node_modules/@poe-code/config-mutations/dist/template/render.d.ts +0 -1
  32. package/node_modules/@poe-code/config-mutations/dist/template/render.js +2 -22
  33. package/node_modules/@poe-code/config-mutations/package.json +1 -4
  34. package/node_modules/@poe-code/design-system/dist/acp/components.js +15 -13
  35. package/node_modules/@poe-code/design-system/dist/components/color.d.ts +31 -0
  36. package/node_modules/@poe-code/design-system/dist/components/color.js +101 -0
  37. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.d.ts +1 -0
  38. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.js +1 -1
  39. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +4 -0
  40. package/node_modules/@poe-code/design-system/dist/components/index.js +2 -0
  41. package/node_modules/@poe-code/design-system/dist/components/logger.js +2 -2
  42. package/node_modules/@poe-code/design-system/dist/components/symbols.js +3 -3
  43. package/node_modules/@poe-code/design-system/dist/components/table.js +191 -40
  44. package/node_modules/@poe-code/design-system/dist/components/template.d.ts +6 -0
  45. package/node_modules/@poe-code/design-system/dist/components/template.js +271 -0
  46. package/node_modules/@poe-code/design-system/dist/components/text.d.ts +1 -0
  47. package/node_modules/@poe-code/design-system/dist/components/text.js +11 -3
  48. package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +20 -13
  49. package/node_modules/@poe-code/design-system/dist/dashboard/keymap.d.ts +5 -0
  50. package/node_modules/@poe-code/design-system/dist/dashboard/keymap.js +146 -12
  51. package/node_modules/@poe-code/design-system/dist/dashboard/terminal.js +31 -0
  52. package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
  53. package/node_modules/@poe-code/design-system/dist/explorer/actions.d.ts +16 -0
  54. package/node_modules/@poe-code/design-system/dist/explorer/actions.js +39 -0
  55. package/node_modules/@poe-code/design-system/dist/explorer/demo.d.ts +13 -0
  56. package/node_modules/@poe-code/design-system/dist/explorer/demo.js +297 -0
  57. package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +61 -0
  58. package/node_modules/@poe-code/design-system/dist/explorer/events.js +1 -0
  59. package/node_modules/@poe-code/design-system/dist/explorer/filter.d.ts +10 -0
  60. package/node_modules/@poe-code/design-system/dist/explorer/filter.js +95 -0
  61. package/node_modules/@poe-code/design-system/dist/explorer/index.d.ts +8 -0
  62. package/node_modules/@poe-code/design-system/dist/explorer/index.js +8 -0
  63. package/node_modules/@poe-code/design-system/dist/explorer/jobs.d.ts +7 -0
  64. package/node_modules/@poe-code/design-system/dist/explorer/jobs.js +59 -0
  65. package/node_modules/@poe-code/design-system/dist/explorer/keymap.d.ts +21 -0
  66. package/node_modules/@poe-code/design-system/dist/explorer/keymap.js +363 -0
  67. package/node_modules/@poe-code/design-system/dist/explorer/layout.d.ts +20 -0
  68. package/node_modules/@poe-code/design-system/dist/explorer/layout.js +73 -0
  69. package/node_modules/@poe-code/design-system/dist/explorer/reducer.d.ts +9 -0
  70. package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +704 -0
  71. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.d.ts +4 -0
  72. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +96 -0
  73. package/node_modules/@poe-code/design-system/dist/explorer/render/footer.d.ts +4 -0
  74. package/node_modules/@poe-code/design-system/dist/explorer/render/footer.js +49 -0
  75. package/node_modules/@poe-code/design-system/dist/explorer/render/header.d.ts +4 -0
  76. package/node_modules/@poe-code/design-system/dist/explorer/render/header.js +56 -0
  77. package/node_modules/@poe-code/design-system/dist/explorer/render/index.d.ts +8 -0
  78. package/node_modules/@poe-code/design-system/dist/explorer/render/index.js +61 -0
  79. package/node_modules/@poe-code/design-system/dist/explorer/render/list.d.ts +4 -0
  80. package/node_modules/@poe-code/design-system/dist/explorer/render/list.js +106 -0
  81. package/node_modules/@poe-code/design-system/dist/explorer/render/modal.d.ts +3 -0
  82. package/node_modules/@poe-code/design-system/dist/explorer/render/modal.js +91 -0
  83. package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.d.ts +8 -0
  84. package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.js +156 -0
  85. package/node_modules/@poe-code/design-system/dist/explorer/runtime.d.ts +2 -0
  86. package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +282 -0
  87. package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.d.ts +50 -0
  88. package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.js +101 -0
  89. package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +130 -0
  90. package/node_modules/@poe-code/design-system/dist/explorer/state.js +87 -0
  91. package/node_modules/@poe-code/design-system/dist/explorer/theme.d.ts +27 -0
  92. package/node_modules/@poe-code/design-system/dist/explorer/theme.js +97 -0
  93. package/node_modules/@poe-code/design-system/dist/index.d.ts +7 -0
  94. package/node_modules/@poe-code/design-system/dist/index.js +5 -0
  95. package/node_modules/@poe-code/design-system/dist/internal/color-support.d.ts +9 -0
  96. package/node_modules/@poe-code/design-system/dist/internal/color-support.js +12 -0
  97. package/node_modules/@poe-code/design-system/dist/prompts/index.js +2 -2
  98. package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.js +2 -2
  99. package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -2
  100. package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +4 -4
  101. package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +5 -5
  102. package/node_modules/@poe-code/design-system/dist/prompts/primitives/outro.js +2 -2
  103. package/node_modules/@poe-code/design-system/dist/prompts/primitives/spinner.js +3 -3
  104. package/node_modules/@poe-code/design-system/dist/static/menu.js +5 -5
  105. package/node_modules/@poe-code/design-system/dist/static/spinner.js +8 -8
  106. package/node_modules/@poe-code/design-system/dist/tokens/colors.js +29 -29
  107. package/node_modules/@poe-code/design-system/dist/tokens/typography.js +6 -6
  108. package/node_modules/@poe-code/design-system/package.json +6 -3
  109. package/package.json +6 -5
package/dist/cli.js CHANGED
@@ -4,12 +4,27 @@ import { Command as CommanderCommand, CommanderError, InvalidArgumentError, Opti
4
4
  import { cancel, confirm, createLogger, formatCommandList, formatOptionList, getTheme, helpFormatterPlain, isCancel, note, promptText, renderTable, resetOutputFormatCache, select, text } from "@poe-code/design-system";
5
5
  import { ApprovalDeclinedError, UserError, assertCommandRequirements, getCommandSourcePath, resolveCommandSecrets } from "./index.js";
6
6
  import { mergeApprovalsGroup } from "./human-in-loop/approvals-commands.js";
7
+ import { writeErrorReport } from "./error-report.js";
7
8
  import { invokeWithHumanInLoop } from "./human-in-loop/index.js";
8
9
  import { resolveMcpProxies } from "./mcp-proxy.js";
9
10
  import { getExpectedNumberDescription, isValidNumberSchemaValue } from "./number-schema.js";
10
11
  import { findEntrypointPackageMetadata } from "./package-metadata.js";
11
12
  import { renderResult } from "./renderer.js";
12
- const RESERVED_SERVICE_NAMES = new Set(["params", "secrets", "fetch", "fs", "env", "progress"]);
13
+ import { renderSourceSnippet } from "./source-snippet.js";
14
+ import { enableSourceMaps, formatDebugStack } from "./stack-trim.js";
15
+ import { suggest } from "./suggest.js";
16
+ import { throwValidationErrors } from "./validation-errors.js";
17
+ const RESERVED_SERVICE_NAMES = new Set([
18
+ "params",
19
+ "secrets",
20
+ "fetch",
21
+ "fs",
22
+ "env",
23
+ "progress",
24
+ "runtimeOptions",
25
+ "root"
26
+ ]);
27
+ const RESERVED_SERVICE_NAMES_MESSAGE = "Available reserved names: params, secrets, fetch, fs, env, progress, runtimeOptions, root.";
13
28
  const NULL_OPTION_VALUE = Symbol("toolcraft.cli.null");
14
29
  function inferProgramName(argv) {
15
30
  const entrypoint = argv[1];
@@ -368,12 +383,14 @@ function parseBooleanText(value, label) {
368
383
  if (normalized === "false") {
369
384
  return false;
370
385
  }
371
- throw new InvalidArgumentError(`Invalid value for "${label}". Expected true or false.`);
386
+ throw new InvalidArgumentError(`Invalid value for "${label}". Expected true or false, got ${describeReceived(value)}.`);
372
387
  }
373
388
  function parseEnumValue(value, values, label) {
374
389
  const match = values.find((candidate) => String(candidate) === value);
375
390
  if (match === undefined) {
376
- throw new InvalidArgumentError(`Invalid value for "${label}". Expected one of: ${values.map((candidate) => String(candidate)).join(", ")}.`);
391
+ const suggestions = suggest(value, values.map((candidate) => String(candidate)));
392
+ const suggestionLine = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?\n` : " ";
393
+ throw new InvalidArgumentError(`Invalid value for "${label}".${suggestionLine}Expected one of: ${values.map((candidate) => String(candidate)).join(", ")}, got ${describeReceived(value)}.`);
377
394
  }
378
395
  return match;
379
396
  }
@@ -393,9 +410,114 @@ function parseJsonText(value, label) {
393
410
  try {
394
411
  return JSON.parse(value);
395
412
  }
396
- catch {
397
- throw new InvalidArgumentError(`Invalid value for "${label}". Expected valid JSON.`);
413
+ catch (error) {
414
+ throw new InvalidArgumentError(`Invalid value for "${label}". Expected valid JSON, got ${describeReceived(value)} (parser: ${getErrorMessage(error)}).`);
415
+ }
416
+ }
417
+ function describeReceived(value) {
418
+ if (value === null)
419
+ return "null";
420
+ if (value === undefined)
421
+ return "missing";
422
+ if (Array.isArray(value))
423
+ return `array(${value.length})`;
424
+ if (typeof value === "object")
425
+ return "object";
426
+ if (typeof value === "string") {
427
+ const s = value.length > 40 ? `${value.slice(0, 40)}…` : value;
428
+ return `${JSON.stringify(s)}`;
429
+ }
430
+ return JSON.stringify(value);
431
+ }
432
+ function getErrorMessage(error) {
433
+ return error instanceof Error ? error.message : String(error);
434
+ }
435
+ function formatJsonParseUserErrorMessage(label, filePath, source, error, options) {
436
+ const location = getJsonParseErrorLocation(error, source);
437
+ const message = getErrorMessage(error);
438
+ const positionText = location === null ? "" : ` at line ${location.line} column ${location.column}`;
439
+ const formattedPath = options.quotePath ? `"${filePath}"` : filePath;
440
+ const snippet = location === null
441
+ ? ""
442
+ : `\n${renderSourceSnippet({
443
+ source,
444
+ line: location.line,
445
+ column: location.column,
446
+ filePath
447
+ })}`;
448
+ return `${label} ${formattedPath} is not valid JSON: ${message}${positionText}.${snippet}`;
449
+ }
450
+ function getJsonParseErrorLocation(error, source) {
451
+ const causeLocation = getJsonParseCauseLocation(error);
452
+ if (causeLocation !== null) {
453
+ return causeLocation;
454
+ }
455
+ const directPosition = getNumericProperty(error, "position");
456
+ if (directPosition !== null) {
457
+ return getSourceOffsetLocation(source, directPosition);
458
+ }
459
+ const messagePosition = getJsonParseMessagePosition(getErrorMessage(error));
460
+ if (messagePosition !== null) {
461
+ return getSourceOffsetLocation(source, messagePosition);
462
+ }
463
+ return null;
464
+ }
465
+ function getJsonParseCauseLocation(error) {
466
+ if (typeof error !== "object" || error === null || !("cause" in error)) {
467
+ return null;
468
+ }
469
+ const cause = error.cause;
470
+ const line = getNumericProperty(cause, "line");
471
+ const column = getNumericProperty(cause, "column") ?? getNumericProperty(cause, "col");
472
+ if (line === null || column === null) {
473
+ return null;
474
+ }
475
+ return { line, column };
476
+ }
477
+ function getNumericProperty(value, key) {
478
+ if (typeof value !== "object" || value === null || !(key in value)) {
479
+ return null;
480
+ }
481
+ const propertyValue = value[key];
482
+ return typeof propertyValue === "number" && Number.isFinite(propertyValue)
483
+ ? propertyValue
484
+ : null;
485
+ }
486
+ function getJsonParseMessagePosition(message) {
487
+ const marker = " at position ";
488
+ const markerIndex = message.indexOf(marker);
489
+ if (markerIndex === -1) {
490
+ return null;
398
491
  }
492
+ const startIndex = markerIndex + marker.length;
493
+ let endIndex = startIndex;
494
+ while (endIndex < message.length && isAsciiDigit(message[endIndex] ?? "")) {
495
+ endIndex += 1;
496
+ }
497
+ if (endIndex === startIndex) {
498
+ return null;
499
+ }
500
+ return Number.parseInt(message.slice(startIndex, endIndex), 10);
501
+ }
502
+ function getSourceOffsetLocation(source, offset) {
503
+ let line = 1;
504
+ let column = 1;
505
+ const boundedOffset = Math.max(0, Math.floor(offset));
506
+ for (let index = 0; index < boundedOffset && index < source.length; index += 1) {
507
+ if (source[index] === "\n") {
508
+ line += 1;
509
+ column = 1;
510
+ continue;
511
+ }
512
+ column += 1;
513
+ }
514
+ return { line, column };
515
+ }
516
+ function isAsciiDigit(value) {
517
+ return value >= "0" && value <= "9";
518
+ }
519
+ function formatAvailableList(values) {
520
+ return `Available: ${[...values].sort().join(", ")}.`;
399
521
  }
400
522
  function normalizeCommanderOptionValue(value) {
401
523
  return value === NULL_OPTION_VALUE ? null : value;
@@ -410,7 +532,7 @@ function parseScalarValue(value, schema, label) {
410
532
  case "number": {
411
533
  const parsed = Number(value);
412
534
  if (!isValidNumberSchemaValue(parsed, schema)) {
413
- throw new InvalidArgumentError(`Invalid value for "${label}". Expected ${getExpectedNumberDescription(schema)}.`);
535
+ throw new InvalidArgumentError(`Invalid value for "${label}". Expected ${getExpectedNumberDescription(schema)}, got ${describeReceived(value)}.`);
414
536
  }
415
537
  return parsed;
416
538
  }
@@ -419,7 +541,7 @@ function parseScalarValue(value, schema, label) {
419
541
  case "enum":
420
542
  return parseEnumValue(value, schema.values, label);
421
543
  }
422
- throw new UserError("Unsupported CLI schema kind.");
544
+ throw new UserError(`Unsupported CLI schema kind. ${formatAvailableList(["boolean", "enum", "number", "string"])}`);
423
545
  }
424
546
  function splitArrayInput(value) {
425
547
  const items = [];
@@ -455,7 +577,6 @@ function parseArrayValue(value, schema, label) {
455
577
  function createOption(field, globalLongOptionFlags) {
456
578
  const flags = formatOptionFlags(field, globalLongOptionFlags);
457
579
  const collidesWithGlobalFlag = globalLongOptionFlags.has(field.optionFlag);
458
- const commanderValue = (value) => (value === null ? NULL_OPTION_VALUE : value);
459
580
  if (field.schema.kind === "boolean") {
460
581
  if (collidesWithGlobalFlag) {
461
582
  return [createCommanderOption(flags, field.description, field)];
@@ -463,9 +584,7 @@ function createOption(field, globalLongOptionFlags) {
463
584
  const mainOption = createCommanderOption(`${flags} [value]`, field.description, field);
464
585
  mainOption.preset(true);
465
586
  // Commander v14 passes the preset value through argParser too, so guard with typeof check
466
- mainOption.argParser((value) => typeof value === "boolean"
467
- ? value
468
- : commanderValue(parseBooleanText(value, field.displayPath)));
587
+ mainOption.argParser((value) => (typeof value === "boolean" ? value : value));
469
588
  return [
470
589
  mainOption,
471
590
  createCommanderOption(`--no-${field.optionFlag.slice(2)}`, field.description, field)
@@ -473,29 +592,16 @@ function createOption(field, globalLongOptionFlags) {
473
592
  }
474
593
  if (field.schema.kind === "array") {
475
594
  return [
476
- createCommanderOption(`${flags} <value...>`, field.description, field).argParser((value, previous = []) => {
477
- const parsed = parseArrayValue(value, field.schema, field.displayPath);
478
- if (parsed === null) {
479
- return NULL_OPTION_VALUE;
480
- }
481
- return [...(previous ?? []), ...parsed];
482
- })
595
+ createCommanderOption(`${flags} <value...>`, field.description, field).argParser((value, previous = []) => [...previous, value])
483
596
  ];
484
597
  }
485
598
  if (field.schema.kind === "json") {
486
- return [
487
- createCommanderOption(`${flags} <json>`, field.description, field).argParser((value) => commanderValue(parseJsonText(value, field.displayPath)))
488
- ];
599
+ return [createCommanderOption(`${flags} <json>`, field.description, field)];
489
600
  }
490
601
  const option = createCommanderOption(`${flags} <value>`, field.description, field);
491
- if (field.schema.kind === "enum" &&
492
- field.schema.values.every((value) => typeof value === "string")) {
493
- option.choices([...field.schema.values]);
494
- }
495
- option.argParser((value) => commanderValue(parseScalarValue(value, field.schema, field.displayPath)));
496
602
  return [option];
497
603
  }
498
- const ALWAYS_GLOBAL_LONG_OPTION_FLAGS = ["--yes", "--output", "--debug"];
604
+ const ALWAYS_GLOBAL_LONG_OPTION_FLAGS = ["--yes", "--output", "--debug", "--verbose"];
499
605
  function getGlobalLongOptionFlags(presetsEnabled) {
500
606
  return new Set(presetsEnabled
501
607
  ? ["--preset", ...ALWAYS_GLOBAL_LONG_OPTION_FLAGS]
@@ -850,9 +956,6 @@ function formatGlobalOptionRows(ctx) {
850
956
  }, {
851
957
  flags: "--output <format>",
852
958
  description: "Output format: rich, md, json."
853
- }, {
854
- flags: "--debug",
855
- description: "Print stack traces for unexpected errors."
856
959
  });
857
960
  if (ctx.showVersion) {
858
961
  rows.push({
@@ -871,6 +974,9 @@ function collectSchemaGlobalFieldRows(group, scope, casing, globalLongOptionFlag
871
974
  if (field.global !== true) {
872
975
  continue;
873
976
  }
977
+ if (globalLongOptionFlags.has(field.optionFlag)) {
978
+ continue;
979
+ }
874
980
  const dedupeKey = `${field.optionFlag}|${field.shortFlag ?? ""}`;
875
981
  if (seen.has(dedupeKey)) {
876
982
  continue;
@@ -1083,9 +1189,24 @@ function addGlobalOptions(command, presetsEnabled) {
1083
1189
  if (value === "markdown") {
1084
1190
  return "md";
1085
1191
  }
1086
- throw new InvalidArgumentError('Invalid value for "--output". Expected one of: rich, md, markdown, json.');
1192
+ throw new InvalidArgumentError(formatInvalidEnumMessage("--output", value, ["rich", "md", "markdown", "json"], {
1193
+ candidates: ["rich", "markdown", "json"],
1194
+ threshold: 3
1195
+ }));
1087
1196
  })
1088
- .option("--debug", "Print stack traces for unexpected errors.");
1197
+ .addOption(new Option("--debug [mode]", "Print stack traces for unexpected errors.")
1198
+ .preset("trim")
1199
+ .argParser(parseDebugStackMode))
1200
+ .option("--verbose", "Print detailed runtime diagnostics.");
1201
+ }
1202
+ function parseDebugStackMode(value) {
1203
+ if (value === true || value === "trim") {
1204
+ return "trim";
1205
+ }
1206
+ if (value === "raw") {
1207
+ return "raw";
1208
+ }
1209
+ throw new InvalidArgumentError(formatInvalidEnumMessage("--debug", String(value), ["raw"], { candidates: ["raw"] }));
1089
1210
  }
1090
1211
  function setNestedValue(target, path, value) {
1091
1212
  let cursor = target;
@@ -1283,7 +1404,7 @@ function validatePresetScalarValue(value, schema, fieldPath, presetPath) {
1283
1404
  break;
1284
1405
  }
1285
1406
  }
1286
- throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}". Expected ${describeExpectedPresetValue(schema)}.`);
1407
+ throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}". Expected ${describeExpectedPresetValue(schema)}, got ${describeReceived(value)}.`);
1287
1408
  }
1288
1409
  function validatePresetFieldValue(value, field, presetPath) {
1289
1410
  if (field.schema.kind === "json") {
@@ -1297,7 +1418,7 @@ function validatePresetFieldValue(value, field, presetPath) {
1297
1418
  throw new UserError(`Array parameter "${field.displayPath}" must use scalar items.`);
1298
1419
  }
1299
1420
  if (!Array.isArray(value)) {
1300
- throw new UserError(`Preset file "${presetPath}" has an invalid value for "${field.displayPath}". Expected an array.`);
1421
+ throw new UserError(`Preset file "${presetPath}" has an invalid value for "${field.displayPath}". Expected an array, got ${describeReceived(value)}.`);
1301
1422
  }
1302
1423
  return value.map((item) => validatePresetScalarValue(item, itemSchema, field.displayPath, presetPath));
1303
1424
  }
@@ -1319,8 +1440,10 @@ async function loadPresetValues(fields, presetPath) {
1319
1440
  try {
1320
1441
  parsedPreset = JSON.parse(rawPreset);
1321
1442
  }
1322
- catch {
1323
- throw new UserError(`Preset file "${presetPath}" is not valid JSON.`);
1443
+ catch (error) {
1444
+ throw new UserError(formatJsonParseUserErrorMessage("Preset file", presetPath, rawPreset, error, {
1445
+ quotePath: true
1446
+ }), { cause: error });
1324
1447
  }
1325
1448
  if (!isPlainObject(parsedPreset)) {
1326
1449
  throw new UserError(`Preset file "${presetPath}" must contain a JSON object.`);
@@ -1340,7 +1463,7 @@ async function loadPresetValues(fields, presetPath) {
1340
1463
  throw new UserError(`Preset file "${presetPath}" contains unknown parameter "${displayPath}".`);
1341
1464
  }
1342
1465
  if (!isPlainObject(value)) {
1343
- throw new UserError(`Preset file "${presetPath}" has an invalid value for "${displayPath}". Expected an object.`);
1466
+ throw new UserError(`Preset file "${presetPath}" has an invalid value for "${displayPath}". Expected an object, got ${describeReceived(value)}.`);
1344
1467
  }
1345
1468
  visitObject(value, nextPath);
1346
1469
  }
@@ -1548,7 +1671,7 @@ function resolveFixturePath(commandPath) {
1548
1671
  const parsed = path.parse(commandPath);
1549
1672
  return path.join(parsed.dir, `${parsed.name}.fixture.json`);
1550
1673
  }
1551
- function selectFixtureScenario(scenarios, selector) {
1674
+ function selectFixtureScenario(scenarios, selector, fixturePath) {
1552
1675
  if (isNumericFixtureSelector(selector)) {
1553
1676
  const index = Number(selector) - 1;
1554
1677
  const scenario = scenarios[index];
@@ -1559,7 +1682,13 @@ function selectFixtureScenario(scenarios, selector) {
1559
1682
  }
1560
1683
  const scenario = scenarios.find((entry) => entry.name === selector);
1561
1684
  if (scenario === undefined) {
1562
- throw new UserError(`Fixture scenario "${selector}" was not found.`);
1685
+ const names = scenarios
1686
+ .map((entry) => entry.name)
1687
+ .filter((name) => typeof name === "string" && name.length > 0);
1688
+ const available = names.length === 0
1689
+ ? `No fixtures are declared in ${fixturePath}.`
1690
+ : formatAvailableList(names);
1691
+ throw new UserError(`Fixture scenario "${selector}" was not found. ${available}`);
1563
1692
  }
1564
1693
  return scenario;
1565
1694
  }
@@ -1582,13 +1711,15 @@ async function loadFixtureScenario(command, selector) {
1582
1711
  try {
1583
1712
  parsed = JSON.parse(rawFixture);
1584
1713
  }
1585
- catch {
1586
- throw new UserError(`Fixture file ${fixturePath} is not valid JSON.`);
1714
+ catch (error) {
1715
+ throw new UserError(formatJsonParseUserErrorMessage("Fixture file", fixturePath, rawFixture, error, {
1716
+ quotePath: false
1717
+ }), { cause: error });
1587
1718
  }
1588
1719
  if (!Array.isArray(parsed)) {
1589
1720
  throw new UserError(`Fixture file ${fixturePath} must contain a JSON array of scenarios.`);
1590
1721
  }
1591
- return selectFixtureScenario(parsed, selector);
1722
+ return selectFixtureScenario(parsed, selector, fixturePath);
1592
1723
  }
1593
1724
  function resolveFixtureSecrets(command) {
1594
1725
  return Object.fromEntries(Object.keys(command.secrets).map((name) => [name, "fixture-secret"]));
@@ -1662,7 +1793,7 @@ function renderApprovalDeclined(error) {
1662
1793
  function validateServices(services) {
1663
1794
  for (const name of Object.keys(services)) {
1664
1795
  if (RESERVED_SERVICE_NAMES.has(name)) {
1665
- throw new Error(`Service name "${name}" is reserved. Choose a different name.`);
1796
+ throw new Error(`Service name "${name}" is reserved. Choose a different name. ${RESERVED_SERVICE_NAMES_MESSAGE}`);
1666
1797
  }
1667
1798
  }
1668
1799
  }
@@ -1683,6 +1814,38 @@ function parseFieldInputValue(value, schema, label) {
1683
1814
  }
1684
1815
  return parseScalarValue(value, schema, label);
1685
1816
  }
1817
+ function parseOptionFieldValue(field, value, errors) {
1818
+ try {
1819
+ if (value === null) {
1820
+ return { ok: true, value };
1821
+ }
1822
+ if (field.schema.kind === "array" && Array.isArray(value)) {
1823
+ const parsedValues = [];
1824
+ for (const item of value) {
1825
+ const parsed = parseArrayValue(String(item), field.schema, field.displayPath);
1826
+ if (parsed === null) {
1827
+ return { ok: true, value: null };
1828
+ }
1829
+ parsedValues.push(...parsed);
1830
+ }
1831
+ return { ok: true, value: parsedValues };
1832
+ }
1833
+ if (typeof value !== "string") {
1834
+ return { ok: true, value };
1835
+ }
1836
+ return { ok: true, value: parseFieldInputValue(value, field.schema, field.displayPath) };
1837
+ }
1838
+ catch (error) {
1839
+ if (error instanceof UserError || error instanceof InvalidArgumentError) {
1840
+ errors.push({
1841
+ path: field.displayPath,
1842
+ message: error.message
1843
+ });
1844
+ return { ok: false };
1845
+ }
1846
+ throw error;
1847
+ }
1848
+ }
1686
1849
  function consumeFieldValue(args, index, schema, label, inlineValue) {
1687
1850
  if (schema.kind === "boolean") {
1688
1851
  if (inlineValue !== undefined) {
@@ -1746,8 +1909,9 @@ function consumeFieldValue(args, index, schema, label, inlineValue) {
1746
1909
  value: parseFieldInputValue(next, schema, label)
1747
1910
  };
1748
1911
  }
1749
- function resolveDynamicLeaf(schema, rawSegments, casing, outputPath = [], displayPath = []) {
1912
+ function resolveDynamicLeaf(schema, rawSegments, casing, outputPath = [], displayPath = [], displayPathPrefix = "") {
1750
1913
  const unwrappedSchema = unwrapOptional(schema);
1914
+ const kind = formatCliSchemaKind(unwrappedSchema.kind);
1751
1915
  if (rawSegments.length === 0) {
1752
1916
  if (unwrappedSchema.kind === "json" ||
1753
1917
  unwrappedSchema.kind === "array" ||
@@ -1761,7 +1925,7 @@ function resolveDynamicLeaf(schema, rawSegments, casing, outputPath = [], displa
1761
1925
  schema: unwrappedSchema
1762
1926
  };
1763
1927
  }
1764
- throw new UserError(`Unsupported dynamic CLI schema kind "${unwrappedSchema.kind}".`);
1928
+ throw new UserError(formatUnsupportedDynamicSchemaMessage(kind, qualifyDisplayPath(displayPathPrefix, toDisplayPath(displayPath))));
1765
1929
  }
1766
1930
  switch (unwrappedSchema.kind) {
1767
1931
  case "object": {
@@ -1770,30 +1934,32 @@ function resolveDynamicLeaf(schema, rawSegments, casing, outputPath = [], displa
1770
1934
  if (formatSegment(key, casing) !== head) {
1771
1935
  continue;
1772
1936
  }
1773
- return resolveDynamicLeaf(childSchema, rest, casing, [...outputPath, key], [...displayPath, key]);
1937
+ return resolveDynamicLeaf(childSchema, rest, casing, [...outputPath, key], [...displayPath, key], displayPathPrefix);
1774
1938
  }
1775
- throw new UserError(`Unknown parameter "${[...displayPath, head].join(".")}".`);
1939
+ throw new UserError(`Unknown parameter "${qualifyDisplayPath(displayPathPrefix, [...displayPath, head].join("."))}". ${formatAvailableList(Object.keys(unwrappedSchema.shape).map((key) => qualifyDisplayPath(displayPathPrefix, toDisplayPath([...displayPath, formatSegment(key, casing)]))))}`);
1776
1940
  }
1777
1941
  case "record": {
1778
1942
  const [head, ...rest] = rawSegments;
1779
- return resolveDynamicLeaf(unwrappedSchema.value, rest, casing, [...outputPath, head ?? ""], [...displayPath, head ?? ""]);
1943
+ return resolveDynamicLeaf(unwrappedSchema.value, rest, casing, [...outputPath, head ?? ""], [...displayPath, head ?? ""], displayPathPrefix);
1780
1944
  }
1781
1945
  case "array": {
1782
1946
  const itemSchema = unwrapOptional(unwrappedSchema.item);
1783
1947
  if (itemSchema.kind !== "object") {
1784
- throw new UserError(`Array parameter "${toDisplayPath(displayPath)}" must use object items.`);
1948
+ throw new UserError(`Array parameter "${qualifyDisplayPath(displayPathPrefix, toDisplayPath(displayPath))}" must use object items.`);
1785
1949
  }
1786
1950
  const [head, ...rest] = rawSegments;
1787
1951
  if (head === undefined || !isNumericFixtureSelector(head)) {
1788
- throw new UserError(`Array parameter "${toDisplayPath(displayPath)}" must use numeric indices.`);
1952
+ throw new UserError(`Array parameter "${qualifyDisplayPath(displayPathPrefix, toDisplayPath(displayPath))}" must use numeric indices.`);
1789
1953
  }
1790
- return resolveDynamicLeaf(itemSchema, rest, casing, [...outputPath, head], [...displayPath, head]);
1954
+ return resolveDynamicLeaf(itemSchema, rest, casing, [...outputPath, head], [...displayPath, head], displayPathPrefix);
1791
1955
  }
1792
1956
  default:
1793
- throw new UserError(`Unknown parameter "${[...displayPath, ...rawSegments].join(".")}".`);
1957
+ throw new UserError(`Unknown parameter "${qualifyDisplayPath(displayPathPrefix, [...displayPath, ...rawSegments].join("."))}". ${formatAvailableList(displayPath.length === 0
1958
+ ? []
1959
+ : [qualifyDisplayPath(displayPathPrefix, toDisplayPath(displayPath))])}`);
1794
1960
  }
1795
1961
  }
1796
- function finalizeDynamicValue(schema, value, displayPath) {
1962
+ function finalizeDynamicValue(schema, value, displayPath, errors) {
1797
1963
  const unwrappedSchema = unwrapOptional(schema);
1798
1964
  if (value === undefined) {
1799
1965
  return undefined;
@@ -1814,23 +1980,39 @@ function finalizeDynamicValue(schema, value, displayPath) {
1814
1980
  return value;
1815
1981
  }
1816
1982
  if (!isPlainObject(value)) {
1817
- throw new UserError(`Invalid value for "${displayPath}". Expected indexed object entries.`);
1983
+ errors.push({
1984
+ path: displayPath,
1985
+ message: `Invalid value for "${displayPath}". Expected indexed object entries, got ${describeReceived(value)}.`
1986
+ });
1987
+ return value;
1818
1988
  }
1819
1989
  const entries = Object.entries(value);
1820
1990
  const indices = entries.map(([key]) => Number(key)).sort((left, right) => left - right);
1821
1991
  if (indices.some((index) => !Number.isInteger(index) || index < 0)) {
1822
- throw new UserError(`Array parameter "${displayPath}" must use numeric indices.`);
1992
+ errors.push({
1993
+ path: displayPath,
1994
+ message: `Array parameter "${displayPath}" must use numeric indices.`
1995
+ });
1996
+ return value;
1823
1997
  }
1824
1998
  for (let index = 0; index < indices.length; index += 1) {
1825
1999
  if (indices[index] !== index) {
1826
- throw new UserError(`Array parameter "${displayPath}" must use contiguous indices starting at 0.`);
2000
+ errors.push({
2001
+ path: displayPath,
2002
+ message: `Array parameter "${displayPath}" must use contiguous indices starting at 0.`
2003
+ });
2004
+ return value;
1827
2005
  }
1828
2006
  }
1829
- return indices.map((index) => finalizeDynamicValue(unwrappedSchema.item, value[String(index)], `${displayPath}.${index}`));
2007
+ return indices.map((index) => finalizeDynamicValue(unwrappedSchema.item, value[String(index)], `${displayPath}.${index}`, errors));
1830
2008
  }
1831
2009
  case "object": {
1832
2010
  if (!isPlainObject(value)) {
1833
- throw new UserError(`Invalid value for "${displayPath}". Expected an object.`);
2011
+ errors.push({
2012
+ path: displayPath,
2013
+ message: `Invalid value for "${displayPath}". Expected an object, got ${describeReceived(value)}.`
2014
+ });
2015
+ return value;
1834
2016
  }
1835
2017
  const result = {};
1836
2018
  for (const [key, rawChildSchema] of Object.entries(unwrappedSchema.shape)) {
@@ -1845,26 +2027,53 @@ function finalizeDynamicValue(schema, value, displayPath) {
1845
2027
  if (rawChildSchema.kind === "optional") {
1846
2028
  continue;
1847
2029
  }
1848
- throw new UserError(`Missing required parameter "${childDisplayPath}".`);
2030
+ errors.push({
2031
+ path: childDisplayPath,
2032
+ message: `Missing required parameter "${childDisplayPath}".`
2033
+ });
2034
+ continue;
1849
2035
  }
1850
- result[key] = finalizeDynamicValue(rawChildSchema, childValue, childDisplayPath);
2036
+ result[key] = finalizeDynamicValue(rawChildSchema, childValue, childDisplayPath, errors);
1851
2037
  }
1852
2038
  return result;
1853
2039
  }
1854
2040
  case "record": {
1855
2041
  if (!isPlainObject(value)) {
1856
- throw new UserError(`Invalid value for "${displayPath}". Expected an object.`);
2042
+ errors.push({
2043
+ path: displayPath,
2044
+ message: `Invalid value for "${displayPath}". Expected an object, got ${describeReceived(value)}.`
2045
+ });
2046
+ return value;
1857
2047
  }
1858
2048
  return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [
1859
2049
  key,
1860
- finalizeDynamicValue(unwrappedSchema.value, entryValue, displayPath.length === 0 ? key : `${displayPath}.${key}`)
2050
+ finalizeDynamicValue(unwrappedSchema.value, entryValue, displayPath.length === 0 ? key : `${displayPath}.${key}`, errors)
1861
2051
  ]));
1862
2052
  }
1863
2053
  default:
1864
- throw new UserError(`Unsupported dynamic CLI schema kind "${unwrappedSchema.kind}".`);
2054
+ errors.push({
2055
+ path: displayPath,
2056
+ message: formatUnsupportedDynamicSchemaMessage(formatCliSchemaKind(unwrappedSchema.kind), displayPath)
2057
+ });
2058
+ return value;
2059
+ }
2060
+ }
2061
+ function formatCliSchemaKind(kind) {
2062
+ return kind === "oneOf" ? "oneof" : kind;
2063
+ }
2064
+ function formatUnsupportedDynamicSchemaMessage(kind, displayPath) {
2065
+ return `Unsupported parameter type "${kind}" for "${displayPath}". Supported types: string, number, integer, boolean, array, object, enum, oneof.`;
2066
+ }
2067
+ function qualifyDisplayPath(prefix, displayPath) {
2068
+ if (prefix.length === 0) {
2069
+ return displayPath;
2070
+ }
2071
+ if (displayPath.length === 0) {
2072
+ return prefix;
1865
2073
  }
2074
+ return `${prefix}.${displayPath}`;
1866
2075
  }
1867
- function parseDynamicValues(dynamicFields, rawArgv, casing) {
2076
+ function parseDynamicValues(dynamicFields, rawArgv, casing, errors) {
1868
2077
  const rawValues = new Map();
1869
2078
  const providedFieldIds = new Set();
1870
2079
  const sortedFields = [...dynamicFields].sort((left, right) => right.optionPath.length - left.optionPath.length);
@@ -1885,11 +2094,11 @@ function parseDynamicValues(dynamicFields, rawArgv, casing) {
1885
2094
  optionPath.every((segment, segmentIndex) => flagPath[segmentIndex] === segment));
1886
2095
  });
1887
2096
  if (match === undefined) {
1888
- throw new UserError(`Unknown parameter "${flagName}".`);
2097
+ throw new UserError(`Unknown parameter "${flagName}". ${formatAvailableList(dynamicFields.map((field) => field.optionPathDisplay))}`);
1889
2098
  }
1890
2099
  const optionPath = match.optionPath.map((segment) => formatSegment(segment, casing));
1891
2100
  const remainder = flagPath.slice(optionPath.length);
1892
- const leaf = resolveDynamicLeaf(match.schema, remainder, casing);
2101
+ const leaf = resolveDynamicLeaf(match.schema, remainder, casing, [], [], match.displayPath);
1893
2102
  const rawStore = rawValues.get(match.id) ?? {};
1894
2103
  const label = `${match.displayPath}.${leaf.displayPath}`.replace(/^\./u, "");
1895
2104
  const parsed = negated && leaf.schema.kind === "boolean"
@@ -1909,13 +2118,23 @@ function parseDynamicValues(dynamicFields, rawArgv, casing) {
1909
2118
  .filter((field) => rawValues.has(field.id))
1910
2119
  .map((field) => [
1911
2120
  field.id,
1912
- finalizeDynamicValue(field.schema.kind === "record" ? field.schema : field.schema, rawValues.get(field.id), field.displayPath)
2121
+ finalizeDynamicValue(field.schema.kind === "record" ? field.schema : field.schema, rawValues.get(field.id), field.displayPath, errors)
1913
2122
  ]))
1914
2123
  };
1915
2124
  }
1916
- async function enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, providedDynamicFieldIds, providedFieldIds, shouldPrompt) {
2125
+ async function enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, providedDynamicFieldIds, providedFieldIds, shouldPrompt, errors) {
1917
2126
  const fieldById = new Map(fields.map((field) => [field.id, field]));
1918
2127
  const dynamicFieldById = new Map(dynamicFields.map((field) => [field.id, field]));
2128
+ const getAvailableBranchParameters = (branch) => [
2129
+ ...branch.fieldIds
2130
+ .map((fieldId) => fieldById.get(fieldId))
2131
+ .filter((field) => field !== undefined && field.synthetic !== true)
2132
+ .map((field) => field.displayPath),
2133
+ ...branch.dynamicFieldIds
2134
+ .map((fieldId) => dynamicFieldById.get(fieldId))
2135
+ .filter((field) => field !== undefined)
2136
+ .map((field) => field.optionPathDisplay)
2137
+ ];
1919
2138
  for (const variant of variants) {
1920
2139
  let selectedBranchId = resolvedFieldValues.get(variant.controlFieldId);
1921
2140
  if (selectedBranchId === undefined && shouldPrompt) {
@@ -1932,12 +2151,21 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
1932
2151
  if (variant.optional) {
1933
2152
  continue;
1934
2153
  }
1935
- throw new UserError(`Missing required parameter "${variant.controlDisplayPath}".`);
2154
+ errors.push({
2155
+ path: variant.controlDisplayPath,
2156
+ message: `Missing required parameter "${variant.controlDisplayPath}".`
2157
+ });
2158
+ continue;
1936
2159
  }
1937
2160
  const selectedBranch = variant.branches.find((branch) => branch.branchId === selectedBranchId);
1938
2161
  if (selectedBranch === undefined) {
1939
- throw new UserError(`Invalid value for "${variant.controlDisplayPath}". Expected one of: ${variant.branches.map((branch) => branch.branchId).join(", ")}.`);
2162
+ errors.push({
2163
+ path: variant.controlDisplayPath,
2164
+ message: `Invalid value for "${variant.controlDisplayPath}". Expected one of: ${variant.branches.map((branch) => branch.branchId).join(", ")}, got ${describeReceived(selectedBranchId)}.`
2165
+ });
2166
+ continue;
1940
2167
  }
2168
+ let hasInvalidBranchParameter = false;
1941
2169
  for (const branch of variant.branches) {
1942
2170
  if (branch.branchId === selectedBranch.branchId) {
1943
2171
  continue;
@@ -1946,17 +2174,28 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
1946
2174
  if (invalidFieldId !== undefined) {
1947
2175
  const field = fieldById.get(invalidFieldId);
1948
2176
  if (field !== undefined) {
1949
- throw new UserError(`Parameter "${field.displayPath}" is not valid when "${variant.controlDisplayPath}" is "${selectedBranch.branchId}".`);
2177
+ errors.push({
2178
+ path: field.displayPath,
2179
+ message: `Unknown parameter "${field.displayPath}" for ${variant.controlDisplayPath}="${selectedBranch.branchId}". ${formatAvailableList(getAvailableBranchParameters(selectedBranch))}`
2180
+ });
2181
+ hasInvalidBranchParameter = true;
1950
2182
  }
1951
2183
  }
1952
2184
  const invalidDynamicFieldId = branch.dynamicFieldIds.find((fieldId) => providedDynamicFieldIds.has(fieldId));
1953
2185
  if (invalidDynamicFieldId !== undefined) {
1954
2186
  const field = dynamicFieldById.get(invalidDynamicFieldId);
1955
2187
  if (field !== undefined) {
1956
- throw new UserError(`Parameter "${field.displayPath}" is not valid when "${variant.controlDisplayPath}" is "${selectedBranch.branchId}".`);
2188
+ errors.push({
2189
+ path: field.displayPath,
2190
+ message: `Unknown parameter "${field.displayPath}" for ${variant.controlDisplayPath}="${selectedBranch.branchId}". ${formatAvailableList(getAvailableBranchParameters(selectedBranch))}`
2191
+ });
2192
+ hasInvalidBranchParameter = true;
1957
2193
  }
1958
2194
  }
1959
2195
  }
2196
+ if (hasInvalidBranchParameter) {
2197
+ continue;
2198
+ }
1960
2199
  for (const fieldId of selectedBranch.requiredFieldIds) {
1961
2200
  const field = fieldById.get(fieldId);
1962
2201
  if (field === undefined || field.synthetic) {
@@ -1972,7 +2211,10 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
1972
2211
  providedFieldIds.add(field.id);
1973
2212
  continue;
1974
2213
  }
1975
- throw new UserError(`Missing required parameter "${field.displayPath}".`);
2214
+ errors.push({
2215
+ path: field.displayPath,
2216
+ message: `Missing required parameter "${field.displayPath}" for ${variant.controlDisplayPath}="${selectedBranch.branchId}". ${formatAvailableList(getAvailableBranchParameters(selectedBranch))}`
2217
+ });
1976
2218
  }
1977
2219
  for (const fieldId of selectedBranch.requiredDynamicFieldIds) {
1978
2220
  const field = dynamicFieldById.get(fieldId);
@@ -1982,7 +2224,10 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
1982
2224
  if (getNestedValue(params, field.path) !== undefined) {
1983
2225
  continue;
1984
2226
  }
1985
- throw new UserError(`Missing required parameter "${field.displayPath}".`);
2227
+ errors.push({
2228
+ path: field.displayPath,
2229
+ message: `Missing required parameter "${field.displayPath}" for ${variant.controlDisplayPath}="${selectedBranch.branchId}". ${formatAvailableList(getAvailableBranchParameters(selectedBranch))}`
2230
+ });
1986
2231
  }
1987
2232
  }
1988
2233
  }
@@ -1993,6 +2238,7 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
1993
2238
  : {};
1994
2239
  const providedFieldIds = new Set();
1995
2240
  const resolvedFieldValues = new Map();
2241
+ const errors = [];
1996
2242
  for (const field of fields) {
1997
2243
  let value;
1998
2244
  let source;
@@ -2026,11 +2272,25 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
2026
2272
  value = normalizeCommanderOptionValue(optionValues[field.optionAttribute]);
2027
2273
  source = "option";
2028
2274
  }
2275
+ if (value === undefined &&
2276
+ field.optionFlag === "--verbose" &&
2277
+ Object.prototype.hasOwnProperty.call(optionValues, field.optionAttribute) &&
2278
+ hasFieldValue(optionValues[field.optionAttribute])) {
2279
+ value = normalizeCommanderOptionValue(optionValues[field.optionAttribute]);
2280
+ source = "option";
2281
+ }
2029
2282
  if (value === undefined &&
2030
2283
  Object.prototype.hasOwnProperty.call(presetValues, field.optionAttribute)) {
2031
2284
  value = presetValues[field.optionAttribute];
2032
2285
  source = "preset";
2033
2286
  }
2287
+ if (source === "option") {
2288
+ const parsed = parseOptionFieldValue(field, value, errors);
2289
+ if (!parsed.ok) {
2290
+ continue;
2291
+ }
2292
+ value = parsed.value;
2293
+ }
2034
2294
  if (value === undefined && shouldPrompt && !field.optional) {
2035
2295
  value = await promptForField(field);
2036
2296
  source = "prompt";
@@ -2043,7 +2303,11 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
2043
2303
  if (field.optional) {
2044
2304
  continue;
2045
2305
  }
2046
- throw new UserError(`Missing required parameter "${field.displayPath}".`);
2306
+ errors.push({
2307
+ path: field.displayPath,
2308
+ message: `Missing required parameter "${field.displayPath}".`
2309
+ });
2310
+ continue;
2047
2311
  }
2048
2312
  resolvedFieldValues.set(field.id, value);
2049
2313
  if (source !== undefined && source !== "default") {
@@ -2054,7 +2318,7 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
2054
2318
  }
2055
2319
  }
2056
2320
  const dynamicResults = dynamicFields.length > 0
2057
- ? parseDynamicValues(dynamicFields, rawArgv, casing)
2321
+ ? parseDynamicValues(dynamicFields, rawArgv, casing, errors)
2058
2322
  : {
2059
2323
  providedFieldIds: new Set(),
2060
2324
  values: new Map()
@@ -2068,18 +2332,23 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
2068
2332
  if (field.optional || field.variantId !== undefined) {
2069
2333
  continue;
2070
2334
  }
2071
- throw new UserError(`Missing required parameter "${field.displayPath}".`);
2335
+ errors.push({
2336
+ path: field.displayPath,
2337
+ message: `Missing required parameter "${field.displayPath}".`
2338
+ });
2339
+ continue;
2072
2340
  }
2073
2341
  setNestedValue(params, field.path, value);
2074
2342
  }
2075
- await enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, dynamicResults.providedFieldIds, providedFieldIds, shouldPrompt);
2343
+ await enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, dynamicResults.providedFieldIds, providedFieldIds, shouldPrompt, errors);
2344
+ throwValidationErrors(errors);
2076
2345
  return params;
2077
2346
  }
2078
2347
  function getResolvedFlags(command) {
2079
2348
  const flags = command.optsWithGlobals();
2080
2349
  return flags;
2081
2350
  }
2082
- async function executeCommand(state, services, requirementOptions, runtimeOptions) {
2351
+ async function executeCommand(state, services, requirementOptions, runtimeOptions, onErrorReportContext) {
2083
2352
  const logger = createLogger();
2084
2353
  const primitives = {
2085
2354
  logger,
@@ -2102,55 +2371,250 @@ async function executeCommand(state, services, requirementOptions, runtimeOption
2102
2371
  logger.info(message);
2103
2372
  }
2104
2373
  };
2105
- await withOutputFormat(output, async () => {
2106
- await assertCommandRequirements(state.command, preflightContext, runtime.requirementOptions);
2107
- const params = await resolveParams(state.fields, state.dynamicFields, state.variants, state.positionalValues, optionValues, state.rawArgv, state.casing, state.presetsEnabled ? resolvedFlags.preset : undefined, shouldPrompt);
2108
- const context = {
2109
- ...preflightContext,
2110
- params
2111
- };
2112
- if (state.command.confirm &&
2113
- !state.command.humanInLoop &&
2114
- !resolvedFlags.yes &&
2115
- process.stdin.isTTY) {
2116
- for (const field of state.fields) {
2117
- const value = field.path.reduce((current, segment) => current && typeof current === "object"
2118
- ? current[segment]
2119
- : undefined, params);
2120
- if (value !== undefined) {
2121
- logger.resolved(field.displayPath, formatResolvedValue(value));
2374
+ let runtimeSecrets;
2375
+ let resolvedParams;
2376
+ try {
2377
+ await withOutputFormat(output, async () => {
2378
+ await assertCommandRequirements(state.command, preflightContext, runtime.requirementOptions);
2379
+ const params = await resolveParams(state.fields, state.dynamicFields, state.variants, state.positionalValues, optionValues, state.rawArgv, state.casing, state.presetsEnabled ? resolvedFlags.preset : undefined, shouldPrompt);
2380
+ resolvedParams = params;
2381
+ runtimeSecrets = runtime.secrets;
2382
+ const context = {
2383
+ ...preflightContext,
2384
+ params
2385
+ };
2386
+ if (state.command.confirm &&
2387
+ !state.command.humanInLoop &&
2388
+ !resolvedFlags.yes &&
2389
+ process.stdin.isTTY) {
2390
+ for (const field of state.fields) {
2391
+ const value = field.path.reduce((current, segment) => current && typeof current === "object"
2392
+ ? current[segment]
2393
+ : undefined, params);
2394
+ if (value !== undefined) {
2395
+ logger.resolved(field.displayPath, formatResolvedValue(value));
2396
+ }
2397
+ }
2398
+ const proceed = await confirm({
2399
+ message: "Proceed?",
2400
+ initialValue: true
2401
+ });
2402
+ if (isCancel(proceed)) {
2403
+ cancel("Operation cancelled.");
2404
+ throw new UserError("Operation cancelled.");
2405
+ }
2406
+ if (proceed !== true) {
2407
+ throw new UserError("Operation cancelled.");
2122
2408
  }
2123
2409
  }
2124
- const proceed = await confirm({
2125
- message: "Proceed?",
2126
- initialValue: true
2127
- });
2128
- if (isCancel(proceed)) {
2129
- cancel("Operation cancelled.");
2130
- throw new UserError("Operation cancelled.");
2410
+ const result = await invokeWithHumanInLoop(state.command, context, runtimeOptions, state.commandPath);
2411
+ if (output === "rich" && runtime.isFixture) {
2412
+ writeRichHeader(`${state.command.name} (fixture)`);
2131
2413
  }
2132
- if (proceed !== true) {
2133
- throw new UserError("Operation cancelled.");
2414
+ if (isHumanInLoopPending(result)) {
2415
+ renderHumanInLoopPending(result);
2416
+ return;
2134
2417
  }
2418
+ const renderStatus = renderResult(state.command, result, output, primitives);
2419
+ if (renderStatus.mcpError) {
2420
+ process.exitCode = 1;
2421
+ }
2422
+ });
2423
+ }
2424
+ catch (error) {
2425
+ onErrorReportContext?.({
2426
+ command: state.command,
2427
+ commandPath: state.commandPath,
2428
+ params: resolvedParams,
2429
+ secrets: runtimeSecrets ?? runtime.secrets
2430
+ });
2431
+ throw error;
2432
+ }
2433
+ }
2434
+ function isStringRecord(value) {
2435
+ return isPlainObject(value) && Object.values(value).every((entry) => typeof entry === "string");
2436
+ }
2437
+ function isHttpErrorLike(error) {
2438
+ if (!isPlainObject(error)) {
2439
+ return false;
2440
+ }
2441
+ if (error.name !== "HttpError" || typeof error.message !== "string") {
2442
+ return false;
2443
+ }
2444
+ const request = error.request;
2445
+ const response = error.response;
2446
+ return (isPlainObject(request) &&
2447
+ typeof request.method === "string" &&
2448
+ typeof request.url === "string" &&
2449
+ isStringRecord(request.headers) &&
2450
+ isPlainObject(response) &&
2451
+ typeof response.status === "number" &&
2452
+ typeof response.statusText === "string" &&
2453
+ isStringRecord(response.headers) &&
2454
+ "body" in response);
2455
+ }
2456
+ function hasTypedOptionalField(value, field, type) {
2457
+ return !(field in value) || typeof value[field] === type;
2458
+ }
2459
+ function isNonEmptyString(value) {
2460
+ return typeof value === "string" && value.trim().length > 0;
2461
+ }
2462
+ function isProblemDetailsLike(body) {
2463
+ if (!isPlainObject(body)) {
2464
+ return false;
2465
+ }
2466
+ if (!hasTypedOptionalField(body, "type", "string")) {
2467
+ return false;
2468
+ }
2469
+ if (!hasTypedOptionalField(body, "title", "string")) {
2470
+ return false;
2471
+ }
2472
+ if (!hasTypedOptionalField(body, "status", "number")) {
2473
+ return false;
2474
+ }
2475
+ if (!hasTypedOptionalField(body, "detail", "string")) {
2476
+ return false;
2477
+ }
2478
+ if (!hasTypedOptionalField(body, "instance", "string")) {
2479
+ return false;
2480
+ }
2481
+ return isNonEmptyString(body.title) || isNonEmptyString(body.detail);
2482
+ }
2483
+ function isGraphQLErrorEnvelopeLike(body) {
2484
+ if (!isPlainObject(body) || !Array.isArray(body.errors) || body.errors.length === 0) {
2485
+ return false;
2486
+ }
2487
+ return body.errors.every((error) => {
2488
+ if (!isPlainObject(error) || typeof error.message !== "string") {
2489
+ return false;
2135
2490
  }
2136
- const result = await invokeWithHumanInLoop(state.command, context, runtimeOptions, state.commandPath);
2137
- if (output === "rich" && runtime.isFixture) {
2138
- writeRichHeader(`${state.command.name} (fixture)`);
2139
- }
2140
- if (isHumanInLoopPending(result)) {
2141
- renderHumanInLoopPending(result);
2142
- return;
2491
+ if ("path" in error) {
2492
+ const pathValue = error.path;
2493
+ if (!Array.isArray(pathValue) ||
2494
+ !pathValue.every((entry) => typeof entry === "string" || typeof entry === "number")) {
2495
+ return false;
2496
+ }
2143
2497
  }
2144
- const renderStatus = renderResult(state.command, result, output, primitives);
2145
- if (renderStatus.mcpError) {
2146
- process.exitCode = 1;
2498
+ if ("extensions" in error) {
2499
+ if (!isPlainObject(error.extensions)) {
2500
+ return false;
2501
+ }
2502
+ if ("code" in error.extensions && typeof error.extensions.code !== "string") {
2503
+ return false;
2504
+ }
2147
2505
  }
2506
+ return true;
2148
2507
  });
2149
2508
  }
2150
- function handleRunError(error, debug) {
2509
+ function styleHttpErrorLine(value, style) {
2510
+ return process.stdout.isTTY === false ? value : style(value);
2511
+ }
2512
+ function formatHttpErrorStatus(value) {
2513
+ return styleHttpErrorLine(value, text.error);
2514
+ }
2515
+ function formatProblemDetailsBody(body) {
2516
+ const lines = [];
2517
+ if (isNonEmptyString(body.title)) {
2518
+ lines.push(`Problem: ${body.title}`);
2519
+ }
2520
+ if (isNonEmptyString(body.detail)) {
2521
+ lines.push(`Detail: ${body.detail}`);
2522
+ }
2523
+ if (body.type !== undefined) {
2524
+ lines.push(`Type: ${body.type}`);
2525
+ }
2526
+ if (body.instance !== undefined) {
2527
+ lines.push(`Instance: ${body.instance}`);
2528
+ }
2529
+ if (body.status !== undefined) {
2530
+ lines.push(`Status: ${body.status}`);
2531
+ }
2532
+ return lines.join("\n");
2533
+ }
2534
+ function formatGraphQLErrorEnvelopeBody(body) {
2535
+ return body.errors
2536
+ .map((error) => {
2537
+ const lines = [`GraphQL error: ${error.message}`];
2538
+ if (error.path !== undefined) {
2539
+ lines.push(` at path: ${error.path.join(".")}`);
2540
+ }
2541
+ if (error.extensions?.code !== undefined) {
2542
+ lines.push(` code: ${error.extensions.code}`);
2543
+ }
2544
+ return lines.join("\n");
2545
+ })
2546
+ .join("\n\n");
2547
+ }
2548
+ function formatHttpErrorBody(body) {
2549
+ if (typeof body === "string") {
2550
+ return body;
2551
+ }
2552
+ if (isProblemDetailsLike(body)) {
2553
+ return formatProblemDetailsBody(body);
2554
+ }
2555
+ if (isGraphQLErrorEnvelopeLike(body)) {
2556
+ return formatGraphQLErrorEnvelopeBody(body);
2557
+ }
2558
+ const serialized = JSON.stringify(body, null, 2);
2559
+ return serialized === undefined ? String(body) : serialized;
2560
+ }
2561
+ function indentHttpErrorBlock(value) {
2562
+ return value
2563
+ .split("\n")
2564
+ .map((line) => ` ${line}`)
2565
+ .join("\n");
2566
+ }
2567
+ function formatHttpHeaderValue(name, value) {
2568
+ return name.toLowerCase() === "authorization" ? "Bearer ****" : value;
2569
+ }
2570
+ function formatHttpErrorHeaders(headers) {
2571
+ return Object.entries(headers).map(([name, value]) => ` ${name}: ${formatHttpHeaderValue(name, value)}`);
2572
+ }
2573
+ function formatHttpErrorSnippet(body) {
2574
+ return formatHttpErrorBody(body).replace(/\s+/g, " ").trim().slice(0, 200);
2575
+ }
2576
+ function renderHttpError(error, options) {
2577
+ const detailed = options.verbose || options.debugStackMode !== undefined;
2578
+ const lines = [
2579
+ styleHttpErrorLine(`Request: ${error.request.method} ${error.request.url}`, text.muted)
2580
+ ];
2581
+ if (detailed) {
2582
+ lines.push("", "Request headers:", ...formatHttpErrorHeaders(error.request.headers), "");
2583
+ if (error.request.body !== undefined) {
2584
+ lines.push("Request body:", indentHttpErrorBlock(formatHttpErrorBody(error.request.body)), "");
2585
+ }
2586
+ }
2587
+ lines.push(formatHttpErrorStatus(`Status: ${error.response.status} ${error.response.statusText}`));
2588
+ if (detailed) {
2589
+ lines.push("", "Response headers:", ...formatHttpErrorHeaders(error.response.headers), "", "Response body:", indentHttpErrorBlock(formatHttpErrorBody(error.response.body)));
2590
+ }
2591
+ else {
2592
+ lines.push("", `Response body: ${formatHttpErrorSnippet(error.response.body)}`, "Re-run with --verbose to see headers and full body.");
2593
+ }
2594
+ process.stderr.write(`${lines.join("\n")}\n`);
2595
+ const stack = error instanceof Error ? error.stack : undefined;
2596
+ if (options.debugStackMode !== undefined && stack) {
2597
+ process.stderr.write(`${formatDebugStack(stack, options.debugStackMode)}\n`);
2598
+ }
2599
+ }
2600
+ function handleRunError(error, options) {
2151
2601
  const logger = createLogger();
2152
2602
  if (error instanceof UserError) {
2153
- logger.error(error.message);
2603
+ logger.error(appendUsagePointer(error.message, {
2604
+ rootUsageName: options.rootUsageName,
2605
+ commandPath: options.commandPath
2606
+ }));
2607
+ process.exitCode = 1;
2608
+ return;
2609
+ }
2610
+ if (error instanceof Error && error.name === "ToolcraftBugError") {
2611
+ logger.error(`toolcraft hit an internal invariant: ${error.message}\n` +
2612
+ `This is a bug in toolcraft or in the command definition; ` +
2613
+ `it cannot be worked around by changing argv. ` +
2614
+ `Re-run with --debug for a stack trace and file an issue.`);
2615
+ if (options.debugStackMode !== undefined && error.stack) {
2616
+ process.stderr.write(`${formatDebugStack(error.stack, options.debugStackMode)}\n`);
2617
+ }
2154
2618
  process.exitCode = 1;
2155
2619
  return;
2156
2620
  }
@@ -2159,22 +2623,229 @@ function handleRunError(error, debug) {
2159
2623
  if (error.code === "commander.helpDisplayed" || error.code === "commander.version") {
2160
2624
  return;
2161
2625
  }
2626
+ if (error.code === "commander.unknownCommand") {
2627
+ logger.error(appendUsagePointer(formatUnknownCommandError(error, options.program, options.argv ?? process.argv), {
2628
+ rootUsageName: options.rootUsageName,
2629
+ commandPath: options.commandPath
2630
+ }));
2631
+ return;
2632
+ }
2633
+ if (error.code === "commander.unknownOption") {
2634
+ const argv = options.argv ?? process.argv;
2635
+ logger.error(appendUsagePointer(formatUnknownOptionError(error, options.program, argv), {
2636
+ rootUsageName: options.rootUsageName,
2637
+ commandPath: options.commandPath.length > 0
2638
+ ? options.commandPath
2639
+ : findCurrentCommanderCommandPath(options.program, argv)
2640
+ }));
2641
+ return;
2642
+ }
2643
+ return;
2644
+ }
2645
+ if (isHttpErrorLike(error)) {
2646
+ renderHttpError(error, options);
2647
+ process.exitCode = 1;
2162
2648
  return;
2163
2649
  }
2164
2650
  const message = error instanceof Error ? error.message : String(error);
2165
- logger.error(debug ? message : `${message} Use --debug for a stack trace.`);
2166
- if (debug && error instanceof Error && error.stack) {
2167
- process.stderr.write(`${error.stack}\n`);
2651
+ logger.error(options.debugStackMode !== undefined ? message : `${message} Use --debug for a stack trace.`);
2652
+ if (options.debugStackMode !== undefined && error instanceof Error && error.stack) {
2653
+ process.stderr.write(`${formatDebugStack(error.stack, options.debugStackMode)}\n`);
2168
2654
  }
2169
2655
  process.exitCode = 1;
2170
2656
  }
2657
+ function formatInvalidEnumMessage(label, value, values, opts = {}) {
2658
+ const suggestions = suggest(value, opts.candidates ?? values.map((candidate) => String(candidate)), opts);
2659
+ const suggestionLine = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?\n` : " ";
2660
+ return `Invalid value for "${label}".${suggestionLine}Expected one of: ${values.map((candidate) => String(candidate)).join(", ")}, got ${describeReceived(value)}.`;
2661
+ }
2662
+ function formatUnknownCommandError(error, program, argv) {
2663
+ const input = extractQuotedCommanderValue(error.message) ?? "";
2664
+ const currentCommand = program === undefined ? undefined : findCurrentCommanderCommand(program, argv);
2665
+ return formatUnknownCommandMessage(input, currentCommand);
2666
+ }
2667
+ function appendUsagePointer(message, options) {
2668
+ if (message.includes("--help")) {
2669
+ return message;
2670
+ }
2671
+ const helpTarget = options.commandPath.length === 0
2672
+ ? options.rootUsageName
2673
+ : `${options.rootUsageName} ${options.commandPath}`;
2674
+ return `${message}\nRun ${helpTarget} --help for usage.`;
2675
+ }
2676
+ function formatCliCommandPath(commandPath) {
2677
+ return commandPath
2678
+ .split(".")
2679
+ .filter((segment) => segment.length > 0)
2680
+ .join(" ");
2681
+ }
2682
+ function formatUnknownCommandMessage(input, currentCommand) {
2683
+ const suggestions = currentCommand === undefined
2684
+ ? []
2685
+ : suggest(input, currentCommand.commands.map((command) => command.name()));
2686
+ return formatSuggestionMessage(`Unknown command "${input}".`, suggestions);
2687
+ }
2688
+ function formatUnknownOptionError(error, program, argv) {
2689
+ const input = extractQuotedCommanderValue(error.message) ?? "";
2690
+ const currentCommand = program === undefined ? undefined : findCurrentCommanderCommand(program, argv);
2691
+ const suggestions = currentCommand === undefined
2692
+ ? []
2693
+ : suggest(input, currentCommand.options
2694
+ .map((option) => option.long)
2695
+ .filter((flag) => flag !== undefined));
2696
+ return formatSuggestionMessage(`Unknown option "${input}".`, suggestions);
2697
+ }
2698
+ function formatSuggestionMessage(message, suggestions) {
2699
+ if (suggestions.length === 0) {
2700
+ return message;
2701
+ }
2702
+ return `${message}\nDid you mean: ${suggestions.join(", ")}?`;
2703
+ }
2704
+ function extractQuotedCommanderValue(message) {
2705
+ const singleQuoted = extractBetweenQuotes(message, "'");
2706
+ if (singleQuoted !== undefined) {
2707
+ return singleQuoted;
2708
+ }
2709
+ return extractBetweenQuotes(message, '"');
2710
+ }
2711
+ function extractBetweenQuotes(message, quote) {
2712
+ const start = message.indexOf(quote);
2713
+ if (start === -1) {
2714
+ return undefined;
2715
+ }
2716
+ const end = message.indexOf(quote, start + 1);
2717
+ if (end === -1) {
2718
+ return undefined;
2719
+ }
2720
+ return message.slice(start + 1, end);
2721
+ }
2722
+ function resolveDebugStackMode(value) {
2723
+ if (value === true || value === "trim") {
2724
+ return "trim";
2725
+ }
2726
+ if (value === "raw") {
2727
+ return "raw";
2728
+ }
2729
+ return undefined;
2730
+ }
2731
+ function getDebugStackModeFromArgv(argv) {
2732
+ for (let index = 2; index < argv.length; index += 1) {
2733
+ const token = argv[index];
2734
+ if (token === undefined) {
2735
+ continue;
2736
+ }
2737
+ if (token === "--debug") {
2738
+ return "trim";
2739
+ }
2740
+ if (token === "--debug=raw") {
2741
+ return "raw";
2742
+ }
2743
+ }
2744
+ return undefined;
2745
+ }
2746
+ function findCurrentCommanderCommand(program, argv) {
2747
+ let current = program;
2748
+ const tokens = argv.slice(2);
2749
+ for (let index = 0; index < tokens.length; index += 1) {
2750
+ const token = tokens[index];
2751
+ if (token === undefined || token === "--") {
2752
+ break;
2753
+ }
2754
+ if (token.startsWith("-")) {
2755
+ const option = current.options.find((candidate) => candidate.long === token || candidate.short === token);
2756
+ if (option?.required === true && !token.includes("=")) {
2757
+ index += 1;
2758
+ }
2759
+ continue;
2760
+ }
2761
+ const child = current.commands.find((command) => command.name() === token || command.aliases().includes(token));
2762
+ if (child === undefined) {
2763
+ break;
2764
+ }
2765
+ current = child;
2766
+ }
2767
+ return current;
2768
+ }
2769
+ function findCurrentCommanderCommandPath(program, argv) {
2770
+ if (program === undefined) {
2771
+ return "";
2772
+ }
2773
+ let current = program;
2774
+ const pathSegments = [];
2775
+ const tokens = argv.slice(2);
2776
+ for (let index = 0; index < tokens.length; index += 1) {
2777
+ const token = tokens[index];
2778
+ if (token === undefined || token === "--" || token.startsWith("-")) {
2779
+ break;
2780
+ }
2781
+ const child = current.commands.find((command) => command.name() === token || command.aliases().includes(token));
2782
+ if (child === undefined) {
2783
+ break;
2784
+ }
2785
+ current = child;
2786
+ pathSegments.push(child.name());
2787
+ }
2788
+ return pathSegments.join(" ");
2789
+ }
2790
+ function findUnknownCommanderCommand(program, argv) {
2791
+ let current = program;
2792
+ const pathSegments = [];
2793
+ const tokens = argv.slice(2);
2794
+ for (let index = 0; index < tokens.length; index += 1) {
2795
+ const token = tokens[index];
2796
+ if (token === undefined || token === "--") {
2797
+ return undefined;
2798
+ }
2799
+ if (token.startsWith("-")) {
2800
+ const option = current.options.find((candidate) => candidate.long === token || candidate.short === token);
2801
+ if (option?.required === true && !token.includes("=")) {
2802
+ index += 1;
2803
+ }
2804
+ continue;
2805
+ }
2806
+ if (current.commands.length === 0 || getDefaultCommanderCommandName(current) !== undefined) {
2807
+ return undefined;
2808
+ }
2809
+ const child = current.commands.find((command) => command.name() === token || command.aliases().includes(token));
2810
+ if (child === undefined) {
2811
+ return {
2812
+ input: token,
2813
+ currentCommand: current,
2814
+ commandPath: pathSegments.join(" ")
2815
+ };
2816
+ }
2817
+ current = child;
2818
+ pathSegments.push(child.name());
2819
+ }
2820
+ return undefined;
2821
+ }
2822
+ function getDefaultCommanderCommandName(command) {
2823
+ const candidate = command;
2824
+ return typeof candidate._defaultCommandName === "string"
2825
+ ? candidate._defaultCommandName
2826
+ : undefined;
2827
+ }
2828
+ function configureCommanderSuggestionOutput(command) {
2829
+ command.exitOverride();
2830
+ command.configureOutput({
2831
+ outputError: (message, write) => {
2832
+ if (message.includes("unknown command") || message.includes("unknown option")) {
2833
+ return;
2834
+ }
2835
+ write(message);
2836
+ }
2837
+ });
2838
+ command.commands.forEach((child) => configureCommanderSuggestionOutput(child));
2839
+ }
2171
2840
  export async function runCLI(roots, options = {}) {
2841
+ enableSourceMaps();
2172
2842
  const root = mergeApprovalsGroup(normalizeRoots(roots, process.argv));
2173
2843
  await resolveMcpProxies(root, { projectRoot: options.projectRoot });
2174
2844
  const casing = options.casing ?? "kebab";
2175
2845
  const services = (options.services ?? {});
2176
2846
  const runtimeOptions = options.humanInLoop ?? {};
2177
2847
  const version = options.version ?? findEntrypointPackageMetadata(process.argv[1])?.version;
2848
+ const rootUsageName = options.rootUsageName ?? inferProgramName(process.argv);
2178
2849
  const servicesWithBuiltIns = {
2179
2850
  ...services,
2180
2851
  runtimeOptions,
@@ -2200,9 +2871,14 @@ export async function runCLI(roots, options = {}) {
2200
2871
  program.version(version, "--version");
2201
2872
  }
2202
2873
  let lastActionCommand;
2874
+ let resolvedCommandPath = "";
2875
+ let errorReportContext;
2203
2876
  const execute = async (state) => {
2204
2877
  lastActionCommand = state.actionCommand;
2205
- await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeOptions);
2878
+ resolvedCommandPath = formatCliCommandPath(state.commandPath);
2879
+ await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeOptions, (context) => {
2880
+ errorReportContext = context;
2881
+ });
2206
2882
  };
2207
2883
  for (const child of root.children) {
2208
2884
  const command = createNodeCommand(child, casing, globalLongOptionFlags, execute, presetsEnabled);
@@ -2214,6 +2890,16 @@ export async function runCLI(roots, options = {}) {
2214
2890
  (command.name() === root.default.name || command.aliases().includes(root.default.name));
2215
2891
  program.addCommand(command, isDefaultChild ? { isDefault: true } : undefined);
2216
2892
  }
2893
+ configureCommanderSuggestionOutput(program);
2894
+ const unknownCommand = findUnknownCommanderCommand(program, process.argv);
2895
+ if (unknownCommand !== undefined) {
2896
+ createLogger().error(appendUsagePointer(formatUnknownCommandMessage(unknownCommand.input, unknownCommand.currentCommand), {
2897
+ rootUsageName,
2898
+ commandPath: unknownCommand.commandPath
2899
+ }));
2900
+ process.exitCode = 1;
2901
+ return;
2902
+ }
2217
2903
  try {
2218
2904
  await program.parseAsync(process.argv);
2219
2905
  }
@@ -2222,8 +2908,31 @@ export async function runCLI(roots, options = {}) {
2222
2908
  renderApprovalDeclined(error);
2223
2909
  return;
2224
2910
  }
2225
- handleRunError(error, lastActionCommand
2226
- ? Boolean(getResolvedFlags(lastActionCommand).debug)
2227
- : process.argv.includes("--debug"));
2911
+ const resolvedFlags = lastActionCommand ? getResolvedFlags(lastActionCommand) : undefined;
2912
+ const report = await writeErrorReport({
2913
+ argv: process.argv,
2914
+ command: errorReportContext?.command,
2915
+ commandPath: errorReportContext?.commandPath ?? resolvedCommandPath,
2916
+ env: process.env,
2917
+ error,
2918
+ errorReports: options.errorReports,
2919
+ params: errorReportContext?.params,
2920
+ projectRoot: options.projectRoot,
2921
+ secrets: errorReportContext?.secrets,
2922
+ version
2923
+ });
2924
+ if (report !== undefined) {
2925
+ process.stderr.write(`Saved error report to ${report.displayPath}\n`);
2926
+ }
2927
+ handleRunError(error, {
2928
+ debugStackMode: resolvedFlags !== undefined
2929
+ ? resolveDebugStackMode(resolvedFlags.debug)
2930
+ : getDebugStackModeFromArgv(process.argv),
2931
+ verbose: resolvedFlags ? Boolean(resolvedFlags.verbose) : process.argv.includes("--verbose"),
2932
+ program,
2933
+ argv: process.argv,
2934
+ rootUsageName,
2935
+ commandPath: resolvedCommandPath
2936
+ });
2228
2937
  }
2229
2938
  }