toolcraft 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +458 -58
  2. package/dist/cli.compile-check.js +2 -1
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +771 -43
  5. package/dist/human-in-loop/approval-tasks.d.ts +31 -0
  6. package/dist/human-in-loop/approval-tasks.js +201 -0
  7. package/dist/human-in-loop/approvals-commands.d.ts +11 -0
  8. package/dist/human-in-loop/approvals-commands.js +191 -0
  9. package/dist/human-in-loop/config.d.ts +11 -0
  10. package/dist/human-in-loop/config.js +21 -0
  11. package/dist/human-in-loop/default-provider.d.ts +2 -0
  12. package/dist/human-in-loop/default-provider.js +26 -0
  13. package/dist/human-in-loop/gate.d.ts +4 -0
  14. package/dist/human-in-loop/gate.js +57 -0
  15. package/dist/human-in-loop/index.d.ts +7 -0
  16. package/dist/human-in-loop/index.js +4 -0
  17. package/dist/human-in-loop/runner.d.ts +3 -0
  18. package/dist/human-in-loop/runner.js +196 -0
  19. package/dist/human-in-loop/spawn.d.ts +3 -0
  20. package/dist/human-in-loop/spawn.js +16 -0
  21. package/dist/human-in-loop/state-machine.d.ts +4 -0
  22. package/dist/human-in-loop/state-machine.js +10 -0
  23. package/dist/human-in-loop/types.d.ts +41 -0
  24. package/dist/human-in-loop/types.js +13 -0
  25. package/dist/index.compile-check.js +25 -1
  26. package/dist/index.d.ts +35 -16
  27. package/dist/index.js +89 -24
  28. package/dist/json-schema-converter.d.ts +21 -0
  29. package/dist/json-schema-converter.js +432 -0
  30. package/dist/mcp-proxy.d.ts +8 -0
  31. package/dist/mcp-proxy.js +383 -0
  32. package/dist/mcp.compile-check.js +3 -2
  33. package/dist/mcp.d.ts +2 -0
  34. package/dist/mcp.js +104 -12
  35. package/dist/number-schema.d.ts +1 -1
  36. package/dist/schema-scope.d.ts +1 -1
  37. package/dist/sdk.compile-check.js +78 -1
  38. package/dist/sdk.d.ts +15 -6
  39. package/dist/sdk.js +57 -6
  40. package/dist/user-error.d.ts +3 -0
  41. package/dist/user-error.js +6 -0
  42. package/node_modules/@poe-code/agent-human-in-loop/README.md +42 -0
  43. package/node_modules/@poe-code/agent-human-in-loop/dist/index.d.ts +5 -0
  44. package/node_modules/@poe-code/agent-human-in-loop/dist/index.js +3 -0
  45. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.d.ts +2 -0
  46. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.js +11 -0
  47. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.d.ts +4 -0
  48. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +40 -0
  49. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.d.ts +6 -0
  50. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +33 -0
  51. package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.d.ts +4 -0
  52. package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.js +4 -0
  53. package/node_modules/@poe-code/agent-human-in-loop/dist/types.d.ts +14 -0
  54. package/node_modules/@poe-code/agent-human-in-loop/dist/types.js +1 -0
  55. package/node_modules/@poe-code/agent-human-in-loop/package.json +25 -0
  56. package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +6 -0
  57. package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +175 -0
  58. package/node_modules/@poe-code/agent-mcp-config/dist/configs.d.ts +22 -0
  59. package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +74 -0
  60. package/node_modules/@poe-code/agent-mcp-config/dist/index.d.ts +3 -0
  61. package/node_modules/@poe-code/agent-mcp-config/dist/index.js +2 -0
  62. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +31 -0
  63. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +87 -0
  64. package/node_modules/@poe-code/agent-mcp-config/dist/types.d.ts +25 -0
  65. package/node_modules/@poe-code/agent-mcp-config/dist/types.js +1 -0
  66. package/node_modules/@poe-code/agent-mcp-config/package.json +25 -0
  67. package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.d.ts +1 -1
  68. package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.js +1 -1
  69. package/node_modules/@poe-code/task-list/README.md +114 -0
  70. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.d.ts +2 -0
  71. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +466 -0
  72. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +8 -0
  73. package/node_modules/@poe-code/task-list/dist/backends/utils.js +58 -0
  74. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.d.ts +2 -0
  75. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +444 -0
  76. package/node_modules/@poe-code/task-list/dist/index.d.ts +4 -0
  77. package/node_modules/@poe-code/task-list/dist/index.js +4 -0
  78. package/node_modules/@poe-code/task-list/dist/open.d.ts +3 -0
  79. package/node_modules/@poe-code/task-list/dist/open.js +34 -0
  80. package/node_modules/@poe-code/task-list/dist/schema/store.schema.json +32 -0
  81. package/node_modules/@poe-code/task-list/dist/schema/task.schema.json +33 -0
  82. package/node_modules/@poe-code/task-list/dist/state-machine.d.ts +16 -0
  83. package/node_modules/@poe-code/task-list/dist/state-machine.js +67 -0
  84. package/node_modules/@poe-code/task-list/dist/state.d.ts +29 -0
  85. package/node_modules/@poe-code/task-list/dist/state.js +61 -0
  86. package/node_modules/@poe-code/task-list/dist/types.d.ts +116 -0
  87. package/node_modules/@poe-code/task-list/dist/types.js +37 -0
  88. package/node_modules/@poe-code/task-list/package.json +26 -0
  89. package/package.json +25 -10
package/dist/cli.js CHANGED
@@ -2,17 +2,21 @@ import { access, readFile, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { Command as CommanderCommand, CommanderError, InvalidArgumentError, Option } from "commander";
4
4
  import { cancel, confirm, createLogger, formatCommandList, formatOptionList, getTheme, isCancel, note, promptText, renderTable, resetOutputFormatCache, select, text, } from "@poe-code/design-system";
5
- import { UserError, assertCommandRequirements, getCommandSourcePath, resolveCommandSecrets } from "./index.js";
5
+ import { ApprovalDeclinedError, UserError, assertCommandRequirements, getCommandSourcePath, resolveCommandSecrets } from "./index.js";
6
+ import { mergeApprovalsGroup } from "./human-in-loop/approvals-commands.js";
7
+ import { invokeWithHumanInLoop } from "./human-in-loop/index.js";
8
+ import { resolveMcpProxies } from "./mcp-proxy.js";
6
9
  import { getExpectedNumberDescription, isValidNumberSchemaValue } from "./number-schema.js";
7
10
  import { renderResult } from "./renderer.js";
8
11
  const RESERVED_SERVICE_NAMES = new Set(["params", "secrets", "fetch", "fs", "env", "progress"]);
12
+ const NULL_OPTION_VALUE = Symbol("toolcraft.cli.null");
9
13
  function inferProgramName(argv) {
10
14
  const entrypoint = argv[1];
11
15
  if (typeof entrypoint !== "string" || entrypoint.length === 0) {
12
- return "agent-kit";
16
+ return "toolcraft";
13
17
  }
14
18
  const parsed = path.parse(entrypoint);
15
- return parsed.name.length > 0 ? parsed.name : "agent-kit";
19
+ return parsed.name.length > 0 ? parsed.name : "toolcraft";
16
20
  }
17
21
  function normalizeRoots(roots, argv) {
18
22
  if (!Array.isArray(roots)) {
@@ -89,17 +93,198 @@ function toOptionAttribute(path, casing) {
89
93
  function toDisplayPath(path) {
90
94
  return path.join(".");
91
95
  }
92
- function collectFields(schema, casing, path = [], inheritedOptional = false) {
93
- const fields = [];
96
+ function toUnionKindControlPath(path) {
97
+ if (path.length === 0) {
98
+ return ["kind"];
99
+ }
100
+ const head = path.slice(0, -1);
101
+ const tail = path[path.length - 1] ?? "";
102
+ return [...head, `${tail}Kind`];
103
+ }
104
+ function toUnionKindDisplayPath(path) {
105
+ if (path.length === 0) {
106
+ return "kind";
107
+ }
108
+ const head = path.slice(0, -1);
109
+ const tail = path[path.length - 1] ?? "";
110
+ return [...head, `${tail}-kind`].join(".");
111
+ }
112
+ function createSyntheticEnumSchema(values) {
113
+ if (values.length === 0) {
114
+ throw new Error("Synthetic enum schema requires at least one value.");
115
+ }
116
+ return {
117
+ kind: "enum",
118
+ values: values,
119
+ };
120
+ }
121
+ function getRequiredBranchFingerprint(branch, casing) {
122
+ const requiredKeys = Object.entries(branch.shape)
123
+ .filter(([, schema]) => schema.kind !== "optional")
124
+ .map(([key]) => formatSegment(key, casing))
125
+ .sort();
126
+ return requiredKeys.join("+");
127
+ }
128
+ function collectFields(schema, casing, path = [], inheritedOptional = false, variantContext) {
129
+ const collected = {
130
+ dynamicFields: [],
131
+ fields: [],
132
+ variants: [],
133
+ };
94
134
  for (const [key, rawChildSchema] of Object.entries(schema.shape)) {
95
135
  const nextPath = [...path, key];
96
- const optional = inheritedOptional || rawChildSchema.kind === "optional";
136
+ const runtimeOptional = inheritedOptional || rawChildSchema.kind === "optional";
97
137
  const childSchema = unwrapOptional(rawChildSchema);
138
+ const requiredWhenActive = rawChildSchema.kind !== "optional" && childSchema.default === undefined;
98
139
  if (childSchema.kind === "object") {
99
- fields.push(...collectFields(childSchema, casing, nextPath, optional));
140
+ const nested = collectFields(childSchema, casing, nextPath, runtimeOptional, variantContext);
141
+ collected.dynamicFields.push(...nested.dynamicFields);
142
+ collected.fields.push(...nested.fields);
143
+ collected.variants.push(...nested.variants);
144
+ continue;
145
+ }
146
+ if (childSchema.kind === "oneOf") {
147
+ const variantId = `${toDisplayPath(nextPath)}:oneOf`;
148
+ const branchIds = Object.keys(childSchema.branches);
149
+ const controlField = {
150
+ id: toDisplayPath([...nextPath, childSchema.discriminator]),
151
+ path: [...nextPath, childSchema.discriminator],
152
+ displayPath: toDisplayPath([...nextPath, childSchema.discriminator]),
153
+ optionAttribute: toOptionAttribute([...nextPath, childSchema.discriminator], casing),
154
+ commanderOptionAttribute: toCommanderOptionAttribute([...nextPath, childSchema.discriminator], casing),
155
+ optionFlag: toOptionFlag([...nextPath, childSchema.discriminator], casing),
156
+ shortFlag: undefined,
157
+ schema: createSyntheticEnumSchema(branchIds),
158
+ description: childSchema.description,
159
+ optional: runtimeOptional,
160
+ hasDefault: false,
161
+ defaultValue: undefined,
162
+ requiredWhenActive,
163
+ };
164
+ collected.fields.push(controlField);
165
+ const branches = [];
166
+ for (const [branchId, branchSchema] of Object.entries(childSchema.branches)) {
167
+ const branch = collectFields(branchSchema, casing, nextPath, true, {
168
+ id: variantId,
169
+ branchId,
170
+ });
171
+ collected.dynamicFields.push(...branch.dynamicFields);
172
+ collected.fields.push(...branch.fields);
173
+ collected.variants.push(...branch.variants);
174
+ branches.push({
175
+ branchId,
176
+ dynamicFieldIds: branch.dynamicFields.map((field) => field.id),
177
+ fieldIds: branch.fields.map((field) => field.id),
178
+ requiredDynamicFieldIds: branch.dynamicFields
179
+ .filter((field) => field.requiredWhenActive)
180
+ .map((field) => field.id),
181
+ requiredFieldIds: branch.fields
182
+ .filter((field) => field.requiredWhenActive)
183
+ .map((field) => field.id),
184
+ });
185
+ }
186
+ collected.variants.push({
187
+ id: variantId,
188
+ controlDisplayPath: controlField.displayPath,
189
+ controlFieldId: controlField.id,
190
+ optional: runtimeOptional,
191
+ branches,
192
+ });
193
+ continue;
194
+ }
195
+ if (childSchema.kind === "union") {
196
+ const variantId = `${toDisplayPath(nextPath)}:union`;
197
+ const controlPath = toUnionKindControlPath(nextPath);
198
+ const controlDisplayPath = toUnionKindDisplayPath(nextPath);
199
+ const branchIds = childSchema.branches.map((branch) => getRequiredBranchFingerprint(branch, casing));
200
+ const controlField = {
201
+ id: controlDisplayPath,
202
+ path: controlPath,
203
+ displayPath: controlDisplayPath,
204
+ optionAttribute: toOptionAttribute(controlPath, casing),
205
+ commanderOptionAttribute: toCommanderOptionAttribute(controlPath, casing),
206
+ optionFlag: toOptionFlag(controlPath, casing),
207
+ shortFlag: undefined,
208
+ schema: createSyntheticEnumSchema(branchIds),
209
+ description: childSchema.description,
210
+ optional: runtimeOptional,
211
+ hasDefault: false,
212
+ defaultValue: undefined,
213
+ requiredWhenActive,
214
+ synthetic: true,
215
+ };
216
+ collected.fields.push(controlField);
217
+ const branches = [];
218
+ childSchema.branches.forEach((branchSchema, index) => {
219
+ const branchId = branchIds[index] ?? "";
220
+ const branch = collectFields(branchSchema, casing, nextPath, true, {
221
+ id: variantId,
222
+ branchId,
223
+ });
224
+ collected.dynamicFields.push(...branch.dynamicFields);
225
+ collected.fields.push(...branch.fields);
226
+ collected.variants.push(...branch.variants);
227
+ branches.push({
228
+ branchId,
229
+ dynamicFieldIds: branch.dynamicFields.map((field) => field.id),
230
+ fieldIds: branch.fields.map((field) => field.id),
231
+ requiredDynamicFieldIds: branch.dynamicFields
232
+ .filter((field) => field.requiredWhenActive)
233
+ .map((field) => field.id),
234
+ requiredFieldIds: branch.fields
235
+ .filter((field) => field.requiredWhenActive)
236
+ .map((field) => field.id),
237
+ });
238
+ });
239
+ collected.variants.push({
240
+ id: variantId,
241
+ controlDisplayPath,
242
+ controlFieldId: controlField.id,
243
+ optional: runtimeOptional,
244
+ branches,
245
+ });
246
+ continue;
247
+ }
248
+ if (childSchema.kind === "record") {
249
+ collected.dynamicFields.push({
250
+ id: toDisplayPath(nextPath),
251
+ path: nextPath,
252
+ displayPath: toDisplayPath(nextPath),
253
+ optionPath: nextPath,
254
+ optionPathDisplay: `${toDisplayPath(nextPath)}.<key>`,
255
+ optionFlag: `${toOptionFlag(nextPath, casing)}.<key>`,
256
+ description: childSchema.description,
257
+ optional: runtimeOptional,
258
+ hasDefault: childSchema.default !== undefined,
259
+ defaultValue: childSchema.default,
260
+ requiredWhenActive,
261
+ schema: childSchema,
262
+ variantId: variantContext?.id,
263
+ variantBranchId: variantContext?.branchId,
264
+ });
265
+ continue;
266
+ }
267
+ if (childSchema.kind === "array" && unwrapOptional(childSchema.item).kind === "object") {
268
+ collected.dynamicFields.push({
269
+ id: toDisplayPath(nextPath),
270
+ path: nextPath,
271
+ displayPath: toDisplayPath(nextPath),
272
+ optionPath: nextPath,
273
+ optionPathDisplay: `${toDisplayPath(nextPath)}.<index>`,
274
+ optionFlag: `${toOptionFlag(nextPath, casing)}.<index>`,
275
+ description: childSchema.description,
276
+ optional: runtimeOptional,
277
+ hasDefault: childSchema.default !== undefined,
278
+ defaultValue: childSchema.default,
279
+ requiredWhenActive,
280
+ schema: childSchema,
281
+ variantId: variantContext?.id,
282
+ variantBranchId: variantContext?.branchId,
283
+ });
100
284
  continue;
101
285
  }
102
- fields.push({
286
+ collected.fields.push({
287
+ id: toDisplayPath(nextPath),
103
288
  path: nextPath,
104
289
  displayPath: toDisplayPath(nextPath),
105
290
  optionAttribute: toOptionAttribute(nextPath, casing),
@@ -108,12 +293,15 @@ function collectFields(schema, casing, path = [], inheritedOptional = false) {
108
293
  shortFlag: childSchema.short,
109
294
  schema: childSchema,
110
295
  description: childSchema.description,
111
- optional,
296
+ optional: runtimeOptional,
112
297
  hasDefault: childSchema.default !== undefined,
113
298
  defaultValue: childSchema.default,
299
+ requiredWhenActive,
300
+ variantId: variantContext?.id,
301
+ variantBranchId: variantContext?.branchId,
114
302
  });
115
303
  }
116
- return fields;
304
+ return collected;
117
305
  }
118
306
  function toCommanderOptionAttribute(path, casing) {
119
307
  const optionAttribute = toOptionAttribute(path, casing);
@@ -185,10 +373,36 @@ function parseEnumValue(value, values, label) {
185
373
  }
186
374
  return match;
187
375
  }
376
+ function validateStringPattern(value, schema, label) {
377
+ if (schema.pattern === undefined) {
378
+ return value;
379
+ }
380
+ if (!matchesStringPattern(value, schema.pattern)) {
381
+ throw new UserError(`Invalid value for "${label}": "${value}" does not match pattern "${schema.pattern}".`);
382
+ }
383
+ return value;
384
+ }
385
+ function matchesStringPattern(value, pattern) {
386
+ return new RegExp(pattern).test(value);
387
+ }
388
+ function parseJsonText(value, label) {
389
+ try {
390
+ return JSON.parse(value);
391
+ }
392
+ catch {
393
+ throw new InvalidArgumentError(`Invalid value for "${label}". Expected valid JSON.`);
394
+ }
395
+ }
396
+ function normalizeCommanderOptionValue(value) {
397
+ return value === NULL_OPTION_VALUE ? null : value;
398
+ }
188
399
  function parseScalarValue(value, schema, label) {
400
+ if (value === "null" && schema.nullable === true) {
401
+ return null;
402
+ }
189
403
  switch (schema.kind) {
190
404
  case "string":
191
- return value;
405
+ return validateStringPattern(value, schema, label);
192
406
  case "number": {
193
407
  const parsed = Number(value);
194
408
  if (!isValidNumberSchemaValue(parsed, schema)) {
@@ -200,9 +414,8 @@ function parseScalarValue(value, schema, label) {
200
414
  return parseBooleanText(value, label);
201
415
  case "enum":
202
416
  return parseEnumValue(value, schema.values, label);
203
- default:
204
- throw new UserError(`Unsupported CLI schema kind "${schema.kind}".`);
205
417
  }
418
+ throw new UserError("Unsupported CLI schema kind.");
206
419
  }
207
420
  function splitArrayInput(value) {
208
421
  const items = [];
@@ -226,6 +439,9 @@ function splitArrayInput(value) {
226
439
  return items;
227
440
  }
228
441
  function parseArrayValue(value, schema, label) {
442
+ if (value === "null" && schema.nullable === true) {
443
+ return null;
444
+ }
229
445
  const itemSchema = unwrapOptional(schema.item);
230
446
  if (itemSchema.kind === "array" || itemSchema.kind === "object") {
231
447
  throw new UserError(`Array parameter "${label}" must use scalar items.`);
@@ -235,6 +451,7 @@ function parseArrayValue(value, schema, label) {
235
451
  function createOption(field) {
236
452
  const flags = formatOptionFlags(field);
237
453
  const collidesWithGlobalFlag = GLOBAL_LONG_OPTION_FLAGS.has(field.optionFlag);
454
+ const commanderValue = (value) => (value === null ? NULL_OPTION_VALUE : value);
238
455
  if (field.schema.kind === "boolean") {
239
456
  if (collidesWithGlobalFlag) {
240
457
  return [createCommanderOption(flags, field.description, field)];
@@ -242,7 +459,7 @@ function createOption(field) {
242
459
  const mainOption = createCommanderOption(`${flags} [value]`, field.description, field);
243
460
  mainOption.preset(true);
244
461
  // Commander v14 passes the preset value through argParser too, so guard with typeof check
245
- mainOption.argParser((value) => typeof value === "boolean" ? value : parseBooleanText(value, field.displayPath));
462
+ mainOption.argParser((value) => typeof value === "boolean" ? value : commanderValue(parseBooleanText(value, field.displayPath)));
246
463
  return [
247
464
  mainOption,
248
465
  createCommanderOption(`--no-${field.optionFlag.slice(2)}`, field.description, field),
@@ -250,17 +467,25 @@ function createOption(field) {
250
467
  }
251
468
  if (field.schema.kind === "array") {
252
469
  return [
253
- createCommanderOption(`${flags} <value...>`, field.description, field).argParser((value, previous = []) => [
254
- ...previous,
255
- ...parseArrayValue(value, field.schema, field.displayPath),
256
- ]),
470
+ createCommanderOption(`${flags} <value...>`, field.description, field).argParser((value, previous = []) => {
471
+ const parsed = parseArrayValue(value, field.schema, field.displayPath);
472
+ if (parsed === null) {
473
+ return NULL_OPTION_VALUE;
474
+ }
475
+ return [...(previous ?? []), ...parsed];
476
+ }),
477
+ ];
478
+ }
479
+ if (field.schema.kind === "json") {
480
+ return [
481
+ createCommanderOption(`${flags} <json>`, field.description, field).argParser((value) => commanderValue(parseJsonText(value, field.displayPath))),
257
482
  ];
258
483
  }
259
484
  const option = createCommanderOption(`${flags} <value>`, field.description, field);
260
485
  if (field.schema.kind === "enum" && field.schema.values.every((value) => typeof value === "string")) {
261
486
  option.choices([...field.schema.values]);
262
487
  }
263
- option.argParser((value) => parseScalarValue(value, field.schema, field.displayPath));
488
+ option.argParser((value) => commanderValue(parseScalarValue(value, field.schema, field.displayPath)));
264
489
  return [option];
265
490
  }
266
491
  const GLOBAL_LONG_OPTION_FLAGS = new Set(["--preset", "--yes", "--output", "--verbose"]);
@@ -348,6 +573,8 @@ function describeSchemaType(schema) {
348
573
  return "value";
349
574
  case "array":
350
575
  return `${describeSchemaType(unwrapOptional(schema.item))}...`;
576
+ case "json":
577
+ return "json";
351
578
  default:
352
579
  throw new UserError("Unsupported CLI schema kind.");
353
580
  }
@@ -381,6 +608,96 @@ function formatHelpFieldDescription(field) {
381
608
  }
382
609
  return appendHelpMetadata(description, metadata);
383
610
  }
611
+ function describeDynamicFieldType(field) {
612
+ if (field.schema.kind === "record") {
613
+ const valueSchema = unwrapOptional(field.schema.value);
614
+ if (valueSchema.kind === "json") {
615
+ return "json";
616
+ }
617
+ if (valueSchema.kind === "array") {
618
+ return `${describeSchemaType(valueSchema)}`;
619
+ }
620
+ if (valueSchema.kind === "object") {
621
+ return "value";
622
+ }
623
+ return describeSchemaType(valueSchema);
624
+ }
625
+ return "value";
626
+ }
627
+ function formatDynamicHelpMetadata(field) {
628
+ const metadata = [];
629
+ if (!field.optional && !field.hasDefault) {
630
+ metadata.push("required");
631
+ }
632
+ if (field.hasDefault) {
633
+ metadata.push(`default: ${formatResolvedValue(field.defaultValue)}`);
634
+ }
635
+ return metadata;
636
+ }
637
+ function collectDynamicObjectHelpRows(schema, casing, optionPrefix, displayPrefix, metadata) {
638
+ const rows = [];
639
+ for (const [key, rawChildSchema] of Object.entries(schema.shape)) {
640
+ const childSchema = unwrapOptional(rawChildSchema);
641
+ const optionFlag = `${optionPrefix}.${formatSegment(key, casing)}`;
642
+ const displayPath = `${displayPrefix}.${key}`;
643
+ const description = childSchema.description ?? displayPath;
644
+ if (childSchema.kind === "object") {
645
+ rows.push(...collectDynamicObjectHelpRows(childSchema, casing, optionFlag, displayPath, metadata));
646
+ continue;
647
+ }
648
+ if (childSchema.kind === "record") {
649
+ rows.push({
650
+ flags: `${optionFlag}.<key> <${describeDynamicFieldType({
651
+ ...{
652
+ id: displayPath,
653
+ path: [],
654
+ displayPath,
655
+ optionPath: [],
656
+ optionPathDisplay: `${displayPath}.<key>`,
657
+ optionFlag: `${optionFlag}.<key>`,
658
+ optional: false,
659
+ hasDefault: false,
660
+ defaultValue: undefined,
661
+ requiredWhenActive: false,
662
+ schema: childSchema,
663
+ },
664
+ })}>`,
665
+ description: appendHelpMetadata(description, metadata),
666
+ });
667
+ continue;
668
+ }
669
+ if (childSchema.kind === "array" && unwrapOptional(childSchema.item).kind === "object") {
670
+ rows.push(...collectDynamicObjectHelpRows(unwrapOptional(childSchema.item), casing, `${optionFlag}.<index>`, `${displayPath}.<index>`, metadata));
671
+ continue;
672
+ }
673
+ rows.push({
674
+ flags: childSchema.kind === "boolean"
675
+ ? `${optionFlag} [value]`
676
+ : `${optionFlag} <${describeSchemaType(childSchema)}>`,
677
+ description: appendHelpMetadata(description, metadata),
678
+ });
679
+ }
680
+ return rows;
681
+ }
682
+ function formatDynamicHelpFields(field, casing) {
683
+ const metadata = formatDynamicHelpMetadata(field);
684
+ if (field.schema.kind === "record") {
685
+ const valueSchema = unwrapOptional(field.schema.value);
686
+ if (valueSchema.kind === "object") {
687
+ return collectDynamicObjectHelpRows(valueSchema, casing, `${field.optionFlag}`, `${field.optionPathDisplay}`, metadata);
688
+ }
689
+ }
690
+ if (field.schema.kind === "array") {
691
+ const itemSchema = unwrapOptional(field.schema.item);
692
+ if (itemSchema.kind === "object") {
693
+ return collectDynamicObjectHelpRows(itemSchema, casing, `${field.optionFlag}`, `${field.optionPathDisplay}`, metadata);
694
+ }
695
+ }
696
+ return [{
697
+ flags: `${field.optionFlag} <${describeDynamicFieldType(field)}>`,
698
+ description: appendHelpMetadata(field.description ?? field.optionPathDisplay, metadata),
699
+ }];
700
+ }
384
701
  function formatSecretRows(secrets) {
385
702
  return Object.values(secrets).map((secret) => ({
386
703
  flags: secret.env,
@@ -453,11 +770,12 @@ function renderGroupHelp(group, breadcrumb, scope, showVersion, rootUsageName) {
453
770
  }
454
771
  function renderLeafHelp(command, breadcrumb, casing, rootUsageName) {
455
772
  const sections = [];
456
- const fields = assignPositionals(collectFields(command.params, casing), command.positional);
773
+ const collected = collectFields(command.params, casing);
774
+ const fields = assignPositionals(collected.fields, command.positional);
457
775
  const optionRows = fields.map((field) => ({
458
776
  flags: formatHelpFieldFlags(field),
459
777
  description: formatHelpFieldDescription(field),
460
- }));
778
+ })).concat(collected.dynamicFields.flatMap((field) => formatDynamicHelpFields(field, casing)));
461
779
  if (optionRows.length > 0) {
462
780
  sections.push(`${text.section("Options:")}\n${formatOptionList(optionRows)}`);
463
781
  }
@@ -506,19 +824,25 @@ async function renderGeneratedHelp(root, argv, options) {
506
824
  process.stdout.write(rendered);
507
825
  });
508
826
  }
509
- function createNodeCommand(node, casing, execute) {
827
+ function createNodeCommand(node, casing, execute, pathSegments = []) {
828
+ const nextPathSegments = [...pathSegments, node.name];
510
829
  if (node.kind === "command") {
511
830
  if (!node.scope.includes("cli")) {
512
831
  return null;
513
832
  }
514
833
  const command = new CommanderCommand(node.name);
515
- const fields = assignPositionals(collectFields(node.params, casing), node.positional);
834
+ const collected = collectFields(node.params, casing);
835
+ const fields = assignPositionals(collected.fields, node.positional);
516
836
  if (node.description !== undefined) {
517
837
  command.description(node.description);
518
838
  }
519
839
  node.aliases.forEach((alias) => command.alias(alias));
520
840
  command.addHelpCommand(false);
521
841
  addGlobalOptions(command);
842
+ command.allowExcessArguments(true);
843
+ if (collected.dynamicFields.length > 0) {
844
+ command.allowUnknownOption(true);
845
+ }
522
846
  for (const field of fields) {
523
847
  if (field.positionalIndex !== undefined) {
524
848
  command.argument(formatPositionalToken(field));
@@ -533,9 +857,14 @@ function createNodeCommand(node, casing, execute) {
533
857
  const positionalValues = args.slice(0, -2);
534
858
  await execute({
535
859
  command: node,
860
+ commandPath: nextPathSegments.join("."),
861
+ casing,
862
+ dynamicFields: collected.dynamicFields,
536
863
  fields,
537
864
  positionalValues,
865
+ rawArgv: actionCommand.args,
538
866
  actionCommand,
867
+ variants: collected.variants,
539
868
  });
540
869
  });
541
870
  return command;
@@ -544,7 +873,7 @@ function createNodeCommand(node, casing, execute) {
544
873
  return null;
545
874
  }
546
875
  const visibleChildren = node.children
547
- .map((child) => createNodeCommand(child, casing, execute))
876
+ .map((child) => createNodeCommand(child, casing, execute, nextPathSegments))
548
877
  .filter((child) => child !== null);
549
878
  const group = new CommanderCommand(node.name);
550
879
  if (node.description !== undefined) {
@@ -653,6 +982,12 @@ async function promptForField(field) {
653
982
  if (field.schema.kind === "array") {
654
983
  return parseArrayValue(entered, field.schema, field.displayPath);
655
984
  }
985
+ if (field.schema.kind === "json") {
986
+ if (entered === "null" && field.schema.nullable === true) {
987
+ return null;
988
+ }
989
+ return parseJsonText(entered, field.displayPath);
990
+ }
656
991
  return parseScalarValue(entered, field.schema, field.displayPath);
657
992
  }
658
993
  function resolveOutput(resolvedFlags) {
@@ -727,6 +1062,9 @@ function describeExpectedPresetValue(schema) {
727
1062
  if (schema.kind === "array") {
728
1063
  return "an array";
729
1064
  }
1065
+ if (schema.kind === "json") {
1066
+ return "valid JSON";
1067
+ }
730
1068
  if (schema.kind === "enum") {
731
1069
  return `one of: ${schema.values.map((value) => JSON.stringify(value)).join(", ")}`;
732
1070
  }
@@ -741,6 +1079,9 @@ function validatePresetScalarValue(value, schema, fieldPath, presetPath) {
741
1079
  if (typeof value !== "string") {
742
1080
  break;
743
1081
  }
1082
+ if (schema.pattern !== undefined && !matchesStringPattern(value, schema.pattern)) {
1083
+ throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}": "${value}" does not match pattern "${schema.pattern}".`);
1084
+ }
744
1085
  return value;
745
1086
  case "number":
746
1087
  if (!isValidNumberSchemaValue(value, schema)) {
@@ -759,12 +1100,13 @@ function validatePresetScalarValue(value, schema, fieldPath, presetPath) {
759
1100
  }
760
1101
  break;
761
1102
  }
762
- default:
763
- throw new UserError(`Unsupported CLI schema kind "${schema.kind}".`);
764
1103
  }
765
1104
  throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}". Expected ${describeExpectedPresetValue(schema)}.`);
766
1105
  }
767
1106
  function validatePresetFieldValue(value, field, presetPath) {
1107
+ if (field.schema.kind === "json") {
1108
+ return value;
1109
+ }
768
1110
  if (field.schema.kind !== "array") {
769
1111
  return validatePresetScalarValue(value, field.schema, field.displayPath, presetPath);
770
1112
  }
@@ -1084,7 +1426,7 @@ function createFixtureEnvValues(command) {
1084
1426
  return values;
1085
1427
  }
1086
1428
  async function resolveFixtureRuntime(command, services, requirementOptions) {
1087
- const selector = process.env.AGENT_KIT_FIXTURE;
1429
+ const selector = process.env.TOOLCRAFT_FIXTURE;
1088
1430
  if (selector === undefined || selector.length === 0) {
1089
1431
  return {
1090
1432
  env: createEnv(),
@@ -1121,6 +1463,24 @@ function writeRichHeader(title) {
1121
1463
  const padding = Math.max(12, 34 - title.length);
1122
1464
  process.stdout.write(`── ${title} ${"─".repeat(padding)}\n`);
1123
1465
  }
1466
+ function isHumanInLoopPending(result) {
1467
+ return (typeof result === "object" &&
1468
+ result !== null &&
1469
+ result.status === "pending-approval" &&
1470
+ typeof result.approvalId === "string" &&
1471
+ typeof result.message === "string" &&
1472
+ typeof result.enqueuedAt === "string");
1473
+ }
1474
+ function renderHumanInLoopPending(pending) {
1475
+ process.stdout.write(`✓ Queued for human approval (id: ${pending.approvalId})\n` +
1476
+ ` Message: ${pending.message}\n` +
1477
+ ` Track: toolcraft approvals show ${pending.approvalId}\n`);
1478
+ }
1479
+ function renderApprovalDeclined(error) {
1480
+ const logger = createLogger();
1481
+ logger.error(error.message);
1482
+ process.exitCode = 1;
1483
+ }
1124
1484
  function validateServices(services) {
1125
1485
  for (const name of Object.keys(services)) {
1126
1486
  if (RESERVED_SERVICE_NAMES.has(name)) {
@@ -1128,13 +1488,336 @@ function validateServices(services) {
1128
1488
  }
1129
1489
  }
1130
1490
  }
1131
- async function resolveParams(fields, positionalValues, optionValues, presetPath, shouldPrompt) {
1491
+ function getNestedValue(target, path) {
1492
+ return path.reduce((current, segment) => current !== null && typeof current === "object"
1493
+ ? current[segment]
1494
+ : undefined, target);
1495
+ }
1496
+ function parseFieldInputValue(value, schema, label) {
1497
+ if (schema.kind === "array") {
1498
+ return parseArrayValue(value, schema, label);
1499
+ }
1500
+ if (schema.kind === "json") {
1501
+ if (value === "null" && schema.nullable === true) {
1502
+ return null;
1503
+ }
1504
+ return parseJsonText(value, label);
1505
+ }
1506
+ return parseScalarValue(value, schema, label);
1507
+ }
1508
+ function consumeFieldValue(args, index, schema, label, inlineValue) {
1509
+ if (schema.kind === "boolean") {
1510
+ if (inlineValue !== undefined) {
1511
+ return {
1512
+ nextIndex: index,
1513
+ value: parseScalarValue(inlineValue, schema, label),
1514
+ };
1515
+ }
1516
+ const next = args[index + 1];
1517
+ if (next === "true" || next === "false" || (schema.nullable === true && next === "null")) {
1518
+ return {
1519
+ nextIndex: index + 1,
1520
+ value: parseScalarValue(next, schema, label),
1521
+ };
1522
+ }
1523
+ return {
1524
+ nextIndex: index,
1525
+ value: true,
1526
+ };
1527
+ }
1528
+ if (inlineValue !== undefined) {
1529
+ return {
1530
+ nextIndex: index,
1531
+ value: parseFieldInputValue(inlineValue, schema, label),
1532
+ };
1533
+ }
1534
+ if (schema.kind === "array") {
1535
+ const values = [];
1536
+ let nextIndex = index;
1537
+ let cursor = index + 1;
1538
+ while (cursor < args.length) {
1539
+ const token = args[cursor] ?? "";
1540
+ if (token.startsWith("-")) {
1541
+ break;
1542
+ }
1543
+ const parsed = parseArrayValue(token, schema, label);
1544
+ if (parsed === null) {
1545
+ return {
1546
+ nextIndex: cursor,
1547
+ value: null,
1548
+ };
1549
+ }
1550
+ values.push(...parsed);
1551
+ nextIndex = cursor;
1552
+ cursor += 1;
1553
+ }
1554
+ if (values.length === 0) {
1555
+ throw new InvalidArgumentError(`option '${label}' argument missing`);
1556
+ }
1557
+ return {
1558
+ nextIndex,
1559
+ value: values,
1560
+ };
1561
+ }
1562
+ const next = args[index + 1];
1563
+ if (next === undefined) {
1564
+ throw new InvalidArgumentError(`option '${label}' argument missing`);
1565
+ }
1566
+ return {
1567
+ nextIndex: index + 1,
1568
+ value: parseFieldInputValue(next, schema, label),
1569
+ };
1570
+ }
1571
+ function resolveDynamicLeaf(schema, rawSegments, casing, outputPath = [], displayPath = []) {
1572
+ const unwrappedSchema = unwrapOptional(schema);
1573
+ if (rawSegments.length === 0) {
1574
+ if (unwrappedSchema.kind === "json" ||
1575
+ unwrappedSchema.kind === "array" ||
1576
+ unwrappedSchema.kind === "string" ||
1577
+ unwrappedSchema.kind === "number" ||
1578
+ unwrappedSchema.kind === "boolean" ||
1579
+ unwrappedSchema.kind === "enum") {
1580
+ return {
1581
+ displayPath: toDisplayPath(displayPath),
1582
+ path: outputPath,
1583
+ schema: unwrappedSchema,
1584
+ };
1585
+ }
1586
+ throw new UserError(`Unsupported dynamic CLI schema kind "${unwrappedSchema.kind}".`);
1587
+ }
1588
+ switch (unwrappedSchema.kind) {
1589
+ case "object": {
1590
+ const [head, ...rest] = rawSegments;
1591
+ for (const [key, childSchema] of Object.entries(unwrappedSchema.shape)) {
1592
+ if (formatSegment(key, casing) !== head) {
1593
+ continue;
1594
+ }
1595
+ return resolveDynamicLeaf(childSchema, rest, casing, [...outputPath, key], [...displayPath, key]);
1596
+ }
1597
+ throw new UserError(`Unknown parameter "${[...displayPath, head].join(".")}".`);
1598
+ }
1599
+ case "record": {
1600
+ const [head, ...rest] = rawSegments;
1601
+ return resolveDynamicLeaf(unwrappedSchema.value, rest, casing, [...outputPath, head ?? ""], [...displayPath, head ?? ""]);
1602
+ }
1603
+ case "array": {
1604
+ const itemSchema = unwrapOptional(unwrappedSchema.item);
1605
+ if (itemSchema.kind !== "object") {
1606
+ throw new UserError(`Array parameter "${toDisplayPath(displayPath)}" must use object items.`);
1607
+ }
1608
+ const [head, ...rest] = rawSegments;
1609
+ if (head === undefined || !isNumericFixtureSelector(head)) {
1610
+ throw new UserError(`Array parameter "${toDisplayPath(displayPath)}" must use numeric indices.`);
1611
+ }
1612
+ return resolveDynamicLeaf(itemSchema, rest, casing, [...outputPath, head], [...displayPath, head]);
1613
+ }
1614
+ default:
1615
+ throw new UserError(`Unknown parameter "${[...displayPath, ...rawSegments].join(".")}".`);
1616
+ }
1617
+ }
1618
+ function finalizeDynamicValue(schema, value, displayPath) {
1619
+ const unwrappedSchema = unwrapOptional(schema);
1620
+ if (value === undefined) {
1621
+ return undefined;
1622
+ }
1623
+ if (value === null && unwrappedSchema.nullable === true) {
1624
+ return null;
1625
+ }
1626
+ switch (unwrappedSchema.kind) {
1627
+ case "string":
1628
+ case "number":
1629
+ case "boolean":
1630
+ case "enum":
1631
+ case "json":
1632
+ return value;
1633
+ case "array": {
1634
+ const itemSchema = unwrapOptional(unwrappedSchema.item);
1635
+ if (itemSchema.kind !== "object") {
1636
+ return value;
1637
+ }
1638
+ if (!isPlainObject(value)) {
1639
+ throw new UserError(`Invalid value for "${displayPath}". Expected indexed object entries.`);
1640
+ }
1641
+ const entries = Object.entries(value);
1642
+ const indices = entries.map(([key]) => Number(key)).sort((left, right) => left - right);
1643
+ if (indices.some((index) => !Number.isInteger(index) || index < 0)) {
1644
+ throw new UserError(`Array parameter "${displayPath}" must use numeric indices.`);
1645
+ }
1646
+ for (let index = 0; index < indices.length; index += 1) {
1647
+ if (indices[index] !== index) {
1648
+ throw new UserError(`Array parameter "${displayPath}" must use contiguous indices starting at 0.`);
1649
+ }
1650
+ }
1651
+ return indices.map((index) => finalizeDynamicValue(unwrappedSchema.item, value[String(index)], `${displayPath}.${index}`));
1652
+ }
1653
+ case "object": {
1654
+ if (!isPlainObject(value)) {
1655
+ throw new UserError(`Invalid value for "${displayPath}". Expected an object.`);
1656
+ }
1657
+ const result = {};
1658
+ for (const [key, rawChildSchema] of Object.entries(unwrappedSchema.shape)) {
1659
+ const childSchema = unwrapOptional(rawChildSchema);
1660
+ const childValue = value[key];
1661
+ const childDisplayPath = displayPath.length === 0 ? key : `${displayPath}.${key}`;
1662
+ if (childValue === undefined) {
1663
+ if (childSchema.default !== undefined) {
1664
+ result[key] = childSchema.default;
1665
+ continue;
1666
+ }
1667
+ if (rawChildSchema.kind === "optional") {
1668
+ continue;
1669
+ }
1670
+ throw new UserError(`Missing required parameter "${childDisplayPath}".`);
1671
+ }
1672
+ result[key] = finalizeDynamicValue(rawChildSchema, childValue, childDisplayPath);
1673
+ }
1674
+ return result;
1675
+ }
1676
+ case "record": {
1677
+ if (!isPlainObject(value)) {
1678
+ throw new UserError(`Invalid value for "${displayPath}". Expected an object.`);
1679
+ }
1680
+ return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [
1681
+ key,
1682
+ finalizeDynamicValue(unwrappedSchema.value, entryValue, displayPath.length === 0 ? key : `${displayPath}.${key}`),
1683
+ ]));
1684
+ }
1685
+ default:
1686
+ throw new UserError(`Unsupported dynamic CLI schema kind "${unwrappedSchema.kind}".`);
1687
+ }
1688
+ }
1689
+ function parseDynamicValues(dynamicFields, rawArgv, casing) {
1690
+ const rawValues = new Map();
1691
+ const providedFieldIds = new Set();
1692
+ const sortedFields = [...dynamicFields].sort((left, right) => right.optionPath.length - left.optionPath.length);
1693
+ for (let index = 0; index < rawArgv.length; index += 1) {
1694
+ const token = rawArgv[index] ?? "";
1695
+ if (!token.startsWith("--")) {
1696
+ continue;
1697
+ }
1698
+ const negated = token.startsWith("--no-");
1699
+ const normalized = negated ? `--${token.slice("--no-".length)}` : token;
1700
+ const equalsIndex = normalized.indexOf("=");
1701
+ const flagName = equalsIndex >= 0 ? normalized.slice(2, equalsIndex) : normalized.slice(2);
1702
+ const inlineValue = equalsIndex >= 0 ? normalized.slice(equalsIndex + 1) : undefined;
1703
+ const flagPath = flagName.split(".");
1704
+ const match = sortedFields.find((field) => {
1705
+ const optionPath = field.optionPath.map((segment) => formatSegment(segment, casing));
1706
+ return (flagPath.length > optionPath.length &&
1707
+ optionPath.every((segment, segmentIndex) => flagPath[segmentIndex] === segment));
1708
+ });
1709
+ if (match === undefined) {
1710
+ throw new UserError(`Unknown parameter "${flagName}".`);
1711
+ }
1712
+ const optionPath = match.optionPath.map((segment) => formatSegment(segment, casing));
1713
+ const remainder = flagPath.slice(optionPath.length);
1714
+ const leaf = resolveDynamicLeaf(match.schema, remainder, casing);
1715
+ const rawStore = rawValues.get(match.id) ?? {};
1716
+ const label = `${match.displayPath}.${leaf.displayPath}`.replace(/^\./u, "");
1717
+ const parsed = negated && leaf.schema.kind === "boolean"
1718
+ ? {
1719
+ nextIndex: index,
1720
+ value: false,
1721
+ }
1722
+ : consumeFieldValue(rawArgv, index, leaf.schema, label, inlineValue);
1723
+ setNestedValue(rawStore, leaf.path, parsed.value);
1724
+ rawValues.set(match.id, rawStore);
1725
+ providedFieldIds.add(match.id);
1726
+ index = parsed.nextIndex;
1727
+ }
1728
+ return {
1729
+ providedFieldIds,
1730
+ values: new Map(dynamicFields
1731
+ .filter((field) => rawValues.has(field.id))
1732
+ .map((field) => [
1733
+ field.id,
1734
+ finalizeDynamicValue(field.schema.kind === "record" ? field.schema : field.schema, rawValues.get(field.id), field.displayPath),
1735
+ ])),
1736
+ };
1737
+ }
1738
+ async function enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, providedDynamicFieldIds, providedFieldIds, shouldPrompt) {
1739
+ const fieldById = new Map(fields.map((field) => [field.id, field]));
1740
+ const dynamicFieldById = new Map(dynamicFields.map((field) => [field.id, field]));
1741
+ for (const variant of variants) {
1742
+ let selectedBranchId = resolvedFieldValues.get(variant.controlFieldId);
1743
+ if (selectedBranchId === undefined && shouldPrompt) {
1744
+ const controlField = fieldById.get(variant.controlFieldId);
1745
+ if (controlField !== undefined) {
1746
+ selectedBranchId = await promptForField(controlField);
1747
+ resolvedFieldValues.set(controlField.id, selectedBranchId);
1748
+ if (!controlField.synthetic) {
1749
+ setNestedValue(params, controlField.path, selectedBranchId);
1750
+ }
1751
+ }
1752
+ }
1753
+ if (selectedBranchId === undefined) {
1754
+ if (variant.optional) {
1755
+ continue;
1756
+ }
1757
+ throw new UserError(`Missing required parameter "${variant.controlDisplayPath}".`);
1758
+ }
1759
+ const selectedBranch = variant.branches.find((branch) => branch.branchId === selectedBranchId);
1760
+ if (selectedBranch === undefined) {
1761
+ throw new UserError(`Invalid value for "${variant.controlDisplayPath}". Expected one of: ${variant.branches.map((branch) => branch.branchId).join(", ")}.`);
1762
+ }
1763
+ for (const branch of variant.branches) {
1764
+ if (branch.branchId === selectedBranch.branchId) {
1765
+ continue;
1766
+ }
1767
+ const invalidFieldId = branch.fieldIds.find((fieldId) => providedFieldIds.has(fieldId));
1768
+ if (invalidFieldId !== undefined) {
1769
+ const field = fieldById.get(invalidFieldId);
1770
+ if (field !== undefined) {
1771
+ throw new UserError(`Parameter "${field.displayPath}" is not valid when "${variant.controlDisplayPath}" is "${selectedBranch.branchId}".`);
1772
+ }
1773
+ }
1774
+ const invalidDynamicFieldId = branch.dynamicFieldIds.find((fieldId) => providedDynamicFieldIds.has(fieldId));
1775
+ if (invalidDynamicFieldId !== undefined) {
1776
+ const field = dynamicFieldById.get(invalidDynamicFieldId);
1777
+ if (field !== undefined) {
1778
+ throw new UserError(`Parameter "${field.displayPath}" is not valid when "${variant.controlDisplayPath}" is "${selectedBranch.branchId}".`);
1779
+ }
1780
+ }
1781
+ }
1782
+ for (const fieldId of selectedBranch.requiredFieldIds) {
1783
+ const field = fieldById.get(fieldId);
1784
+ if (field === undefined || field.synthetic) {
1785
+ continue;
1786
+ }
1787
+ if (getNestedValue(params, field.path) !== undefined) {
1788
+ continue;
1789
+ }
1790
+ if (shouldPrompt) {
1791
+ const promptedValue = await promptForField(field);
1792
+ resolvedFieldValues.set(field.id, promptedValue);
1793
+ setNestedValue(params, field.path, promptedValue);
1794
+ providedFieldIds.add(field.id);
1795
+ continue;
1796
+ }
1797
+ throw new UserError(`Missing required parameter "${field.displayPath}".`);
1798
+ }
1799
+ for (const fieldId of selectedBranch.requiredDynamicFieldIds) {
1800
+ const field = dynamicFieldById.get(fieldId);
1801
+ if (field === undefined) {
1802
+ continue;
1803
+ }
1804
+ if (getNestedValue(params, field.path) !== undefined) {
1805
+ continue;
1806
+ }
1807
+ throw new UserError(`Missing required parameter "${field.displayPath}".`);
1808
+ }
1809
+ }
1810
+ }
1811
+ async function resolveParams(fields, dynamicFields, variants, positionalValues, optionValues, rawArgv, casing, presetPath, shouldPrompt) {
1132
1812
  const params = {};
1133
1813
  const presetValues = typeof presetPath === "string" && presetPath.length > 0
1134
1814
  ? await loadPresetValues(fields, presetPath)
1135
1815
  : {};
1816
+ const providedFieldIds = new Set();
1817
+ const resolvedFieldValues = new Map();
1136
1818
  for (const field of fields) {
1137
1819
  let value;
1820
+ let source;
1138
1821
  if (field.positionalIndex !== undefined) {
1139
1822
  const positionalValue = positionalValues[field.positionalIndex];
1140
1823
  if (field.schema.kind === "array") {
@@ -1144,32 +1827,39 @@ async function resolveParams(fields, positionalValues, optionValues, presetPath,
1144
1827
  throw new UserError(`Array parameter "${field.displayPath}" must use scalar items.`);
1145
1828
  }
1146
1829
  value = positionalValue.map((item) => parseScalarValue(String(item), itemSchema, field.displayPath));
1830
+ source = "positional";
1147
1831
  }
1148
1832
  }
1149
1833
  else if (typeof positionalValue === "string" && positionalValue.length > 0) {
1150
- value = parseScalarValue(positionalValue, field.schema, field.displayPath);
1834
+ value = parseFieldInputValue(positionalValue, field.schema, field.displayPath);
1835
+ source = "positional";
1151
1836
  }
1152
1837
  }
1153
1838
  if (value === undefined &&
1154
1839
  Object.prototype.hasOwnProperty.call(optionValues, field.commanderOptionAttribute) &&
1155
1840
  hasFieldValue(optionValues[field.commanderOptionAttribute])) {
1156
- value = optionValues[field.commanderOptionAttribute];
1841
+ value = normalizeCommanderOptionValue(optionValues[field.commanderOptionAttribute]);
1842
+ source = "option";
1157
1843
  }
1158
1844
  if (value === undefined &&
1159
1845
  field.commanderOptionAttribute === field.optionAttribute &&
1160
1846
  Object.prototype.hasOwnProperty.call(optionValues, field.optionAttribute) &&
1161
1847
  hasFieldValue(optionValues[field.optionAttribute])) {
1162
- value = optionValues[field.optionAttribute];
1848
+ value = normalizeCommanderOptionValue(optionValues[field.optionAttribute]);
1849
+ source = "option";
1163
1850
  }
1164
1851
  if (value === undefined &&
1165
1852
  Object.prototype.hasOwnProperty.call(presetValues, field.optionAttribute)) {
1166
1853
  value = presetValues[field.optionAttribute];
1854
+ source = "preset";
1167
1855
  }
1168
1856
  if (value === undefined && shouldPrompt && !field.optional) {
1169
1857
  value = await promptForField(field);
1858
+ source = "prompt";
1170
1859
  }
1171
1860
  if (value === undefined && field.hasDefault) {
1172
1861
  value = field.defaultValue;
1862
+ source = "default";
1173
1863
  }
1174
1864
  if (value === undefined) {
1175
1865
  if (field.optional) {
@@ -1177,15 +1867,41 @@ async function resolveParams(fields, positionalValues, optionValues, presetPath,
1177
1867
  }
1178
1868
  throw new UserError(`Missing required parameter "${field.displayPath}".`);
1179
1869
  }
1870
+ resolvedFieldValues.set(field.id, value);
1871
+ if (source !== undefined && source !== "default") {
1872
+ providedFieldIds.add(field.id);
1873
+ }
1874
+ if (!field.synthetic) {
1875
+ setNestedValue(params, field.path, value);
1876
+ }
1877
+ }
1878
+ const dynamicResults = dynamicFields.length > 0
1879
+ ? parseDynamicValues(dynamicFields, rawArgv, casing)
1880
+ : {
1881
+ providedFieldIds: new Set(),
1882
+ values: new Map(),
1883
+ };
1884
+ for (const field of dynamicFields) {
1885
+ let value = dynamicResults.values.get(field.id);
1886
+ if (value === undefined && field.hasDefault) {
1887
+ value = field.defaultValue;
1888
+ }
1889
+ if (value === undefined) {
1890
+ if (field.optional || field.variantId !== undefined) {
1891
+ continue;
1892
+ }
1893
+ throw new UserError(`Missing required parameter "${field.displayPath}".`);
1894
+ }
1180
1895
  setNestedValue(params, field.path, value);
1181
1896
  }
1897
+ await enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, dynamicResults.providedFieldIds, providedFieldIds, shouldPrompt);
1182
1898
  return params;
1183
1899
  }
1184
1900
  function getResolvedFlags(command) {
1185
1901
  const flags = command.optsWithGlobals();
1186
1902
  return flags;
1187
1903
  }
1188
- async function executeCommand(state, services, requirementOptions) {
1904
+ async function executeCommand(state, services, requirementOptions, runtimeOptions) {
1189
1905
  const logger = createLogger();
1190
1906
  const primitives = {
1191
1907
  logger,
@@ -1210,12 +1926,12 @@ async function executeCommand(state, services, requirementOptions) {
1210
1926
  };
1211
1927
  await withOutputFormat(output, async () => {
1212
1928
  await assertCommandRequirements(state.command, preflightContext, runtime.requirementOptions);
1213
- const params = await resolveParams(state.fields, state.positionalValues, optionValues, resolvedFlags.preset, shouldPrompt);
1929
+ const params = await resolveParams(state.fields, state.dynamicFields, state.variants, state.positionalValues, optionValues, state.rawArgv, state.casing, resolvedFlags.preset, shouldPrompt);
1214
1930
  const context = {
1215
1931
  ...preflightContext,
1216
1932
  params,
1217
1933
  };
1218
- if (state.command.confirm && !resolvedFlags.yes && process.stdin.isTTY) {
1934
+ if (state.command.confirm && !state.command.humanInLoop && !resolvedFlags.yes && process.stdin.isTTY) {
1219
1935
  for (const field of state.fields) {
1220
1936
  const value = field.path.reduce((current, segment) => current && typeof current === "object"
1221
1937
  ? current[segment]
@@ -1236,10 +1952,14 @@ async function executeCommand(state, services, requirementOptions) {
1236
1952
  throw new UserError("Operation cancelled.");
1237
1953
  }
1238
1954
  }
1239
- const result = await state.command.handler(context);
1955
+ const result = await invokeWithHumanInLoop(state.command, context, runtimeOptions, state.commandPath);
1240
1956
  if (output === "rich" && runtime.isFixture) {
1241
1957
  writeRichHeader(`${state.command.name} (fixture)`);
1242
1958
  }
1959
+ if (isHumanInLoopPending(result)) {
1960
+ renderHumanInLoopPending(result);
1961
+ return;
1962
+ }
1243
1963
  renderResult(state.command, result, output, primitives);
1244
1964
  });
1245
1965
  }
@@ -1265,9 +1985,16 @@ function handleRunError(error, verbose) {
1265
1985
  process.exitCode = 1;
1266
1986
  }
1267
1987
  export async function runCLI(roots, options = {}) {
1268
- const root = normalizeRoots(roots, process.argv);
1988
+ const root = mergeApprovalsGroup(normalizeRoots(roots, process.argv));
1989
+ await resolveMcpProxies(root);
1269
1990
  const casing = options.casing ?? "kebab";
1270
1991
  const services = (options.services ?? {});
1992
+ const runtimeOptions = options.humanInLoop ?? {};
1993
+ const servicesWithBuiltIns = {
1994
+ ...services,
1995
+ runtimeOptions,
1996
+ root,
1997
+ };
1271
1998
  const requirementOptions = {
1272
1999
  apiVersion: options.apiVersion,
1273
2000
  };
@@ -1285,13 +2012,10 @@ export async function runCLI(roots, options = {}) {
1285
2012
  if (options.version !== undefined) {
1286
2013
  program.version(options.version, "--version");
1287
2014
  }
2015
+ let lastActionCommand;
1288
2016
  const execute = async (state) => {
1289
- try {
1290
- await executeCommand(state, services, requirementOptions);
1291
- }
1292
- catch (error) {
1293
- handleRunError(error, Boolean(getResolvedFlags(state.actionCommand).verbose));
1294
- }
2017
+ lastActionCommand = state.actionCommand;
2018
+ await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeOptions);
1295
2019
  };
1296
2020
  for (const child of root.children) {
1297
2021
  const command = createNodeCommand(child, casing, execute);
@@ -1307,6 +2031,10 @@ export async function runCLI(roots, options = {}) {
1307
2031
  await program.parseAsync(process.argv);
1308
2032
  }
1309
2033
  catch (error) {
1310
- handleRunError(error, process.argv.includes("--verbose"));
2034
+ if (error instanceof ApprovalDeclinedError) {
2035
+ renderApprovalDeclined(error);
2036
+ return;
2037
+ }
2038
+ handleRunError(error, lastActionCommand ? Boolean(getResolvedFlags(lastActionCommand).verbose) : process.argv.includes("--verbose"));
1311
2039
  }
1312
2040
  }