toolcraft 0.0.2 → 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.
- package/README.md +458 -58
- package/dist/cli.compile-check.js +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +768 -40
- package/dist/human-in-loop/approval-tasks.d.ts +31 -0
- package/dist/human-in-loop/approval-tasks.js +201 -0
- package/dist/human-in-loop/approvals-commands.d.ts +11 -0
- package/dist/human-in-loop/approvals-commands.js +191 -0
- package/dist/human-in-loop/config.d.ts +11 -0
- package/dist/human-in-loop/config.js +21 -0
- package/dist/human-in-loop/default-provider.d.ts +2 -0
- package/dist/human-in-loop/default-provider.js +26 -0
- package/dist/human-in-loop/gate.d.ts +4 -0
- package/dist/human-in-loop/gate.js +57 -0
- package/dist/human-in-loop/index.d.ts +7 -0
- package/dist/human-in-loop/index.js +4 -0
- package/dist/human-in-loop/runner.d.ts +3 -0
- package/dist/human-in-loop/runner.js +196 -0
- package/dist/human-in-loop/spawn.d.ts +3 -0
- package/dist/human-in-loop/spawn.js +16 -0
- package/dist/human-in-loop/state-machine.d.ts +4 -0
- package/dist/human-in-loop/state-machine.js +10 -0
- package/dist/human-in-loop/types.d.ts +41 -0
- package/dist/human-in-loop/types.js +13 -0
- package/dist/index.compile-check.js +24 -0
- package/dist/index.d.ts +32 -13
- package/dist/index.js +82 -17
- package/dist/json-schema-converter.d.ts +21 -0
- package/dist/json-schema-converter.js +432 -0
- package/dist/mcp-proxy.d.ts +8 -0
- package/dist/mcp-proxy.js +383 -0
- package/dist/mcp.compile-check.js +1 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +103 -11
- package/dist/sdk.compile-check.js +77 -0
- package/dist/sdk.d.ts +14 -5
- package/dist/sdk.js +57 -6
- package/dist/user-error.d.ts +3 -0
- package/dist/user-error.js +6 -0
- package/node_modules/@poe-code/agent-human-in-loop/README.md +42 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/index.d.ts +5 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/index.js +3 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.d.ts +2 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.js +11 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.d.ts +4 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +40 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.d.ts +6 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +33 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.d.ts +4 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.js +4 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/types.d.ts +14 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/types.js +1 -0
- package/node_modules/@poe-code/agent-human-in-loop/package.json +25 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +6 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +175 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/configs.d.ts +22 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +74 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/index.d.ts +3 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/index.js +2 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +31 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +87 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/types.d.ts +25 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/types.js +1 -0
- package/node_modules/@poe-code/agent-mcp-config/package.json +25 -0
- package/node_modules/@poe-code/task-list/README.md +114 -0
- package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +466 -0
- package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +8 -0
- package/node_modules/@poe-code/task-list/dist/backends/utils.js +58 -0
- package/node_modules/@poe-code/task-list/dist/backends/yaml-file.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +444 -0
- package/node_modules/@poe-code/task-list/dist/index.d.ts +4 -0
- package/node_modules/@poe-code/task-list/dist/index.js +4 -0
- package/node_modules/@poe-code/task-list/dist/open.d.ts +3 -0
- package/node_modules/@poe-code/task-list/dist/open.js +34 -0
- package/node_modules/@poe-code/task-list/dist/schema/store.schema.json +32 -0
- package/node_modules/@poe-code/task-list/dist/schema/task.schema.json +33 -0
- package/node_modules/@poe-code/task-list/dist/state-machine.d.ts +16 -0
- package/node_modules/@poe-code/task-list/dist/state-machine.js +67 -0
- package/node_modules/@poe-code/task-list/dist/state.d.ts +29 -0
- package/node_modules/@poe-code/task-list/dist/state.js +61 -0
- package/node_modules/@poe-code/task-list/dist/types.d.ts +116 -0
- package/node_modules/@poe-code/task-list/dist/types.js +37 -0
- package/node_modules/@poe-code/task-list/package.json +26 -0
- package/package.json +22 -7
package/dist/cli.js
CHANGED
|
@@ -2,10 +2,14 @@ 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) {
|
|
@@ -89,17 +93,198 @@ function toOptionAttribute(path, casing) {
|
|
|
89
93
|
function toDisplayPath(path) {
|
|
90
94
|
return path.join(".");
|
|
91
95
|
}
|
|
92
|
-
function
|
|
93
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
255
|
-
|
|
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
|
|
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
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
1290
|
-
|
|
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
|
-
|
|
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
|
}
|