libretto 0.4.0 → 0.4.2
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/dist/cli/cli.js +83 -223
- package/dist/cli/commands/ai.js +32 -16
- package/dist/cli/commands/browser.js +126 -85
- package/dist/cli/commands/execution.js +147 -108
- package/dist/cli/commands/init.js +103 -40
- package/dist/cli/commands/logs.js +90 -65
- package/dist/cli/commands/shared.js +50 -0
- package/dist/cli/commands/snapshot.js +31 -16
- package/dist/cli/framework/simple-cli.js +776 -0
- package/dist/cli/router.js +29 -0
- package/package.json +2 -4
- /package/{.agents/skills → skills}/libretto/SKILL.md +0 -0
- /package/{.agents/skills → skills}/libretto/code-generation-rules.md +0 -0
- /package/{.agents/skills → skills}/libretto/integration-approach-selection.md +0 -0
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
function toCamelCase(input2) {
|
|
3
|
+
return input2.replace(
|
|
4
|
+
/-([a-zA-Z0-9])/g,
|
|
5
|
+
(_match, letter) => letter.toUpperCase()
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
function toKebabCase(input2) {
|
|
9
|
+
return input2.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
10
|
+
}
|
|
11
|
+
function zodObjectFromShape(shape) {
|
|
12
|
+
return z.object(shape);
|
|
13
|
+
}
|
|
14
|
+
function schemaAcceptsUndefined(schema) {
|
|
15
|
+
return schema.safeParse(void 0).success;
|
|
16
|
+
}
|
|
17
|
+
function pathToRouteKey(path) {
|
|
18
|
+
return path.join(".");
|
|
19
|
+
}
|
|
20
|
+
function pathStartsWith(path, prefix) {
|
|
21
|
+
if (prefix.length > path.length) return false;
|
|
22
|
+
return prefix.every((token, index) => path[index] === token);
|
|
23
|
+
}
|
|
24
|
+
function formatListEntry(label, description) {
|
|
25
|
+
return description ? ` ${label} ${description}` : ` ${label}`;
|
|
26
|
+
}
|
|
27
|
+
function isHelpFlag(arg) {
|
|
28
|
+
return arg === "--help" || arg === "-h";
|
|
29
|
+
}
|
|
30
|
+
function parseBooleanFlagValue(rawValue, flagName) {
|
|
31
|
+
if (rawValue === "true") return true;
|
|
32
|
+
if (rawValue === "false") return false;
|
|
33
|
+
throw new Error(`Invalid value for --${flagName}: expected true or false.`);
|
|
34
|
+
}
|
|
35
|
+
function buildUsagePositionalToken(positional2) {
|
|
36
|
+
return schemaAcceptsUndefined(positional2.schema) ? `[${positional2.key}]` : `<${positional2.key}>`;
|
|
37
|
+
}
|
|
38
|
+
function buildNamedArgFlagName(key, spec) {
|
|
39
|
+
if (spec.source === "--") return "--";
|
|
40
|
+
return spec.name ?? toKebabCase(key);
|
|
41
|
+
}
|
|
42
|
+
function buildNamedArgHelpLabel(key, spec) {
|
|
43
|
+
if (spec.source === "--") return "-- <args...>";
|
|
44
|
+
const flagName = buildNamedArgFlagName(key, spec);
|
|
45
|
+
if (spec.kind === "flag") {
|
|
46
|
+
return `--${flagName}`;
|
|
47
|
+
}
|
|
48
|
+
return `--${flagName} <value>`;
|
|
49
|
+
}
|
|
50
|
+
function normalizeNamedArgToken(token) {
|
|
51
|
+
return token.replace(/^-{1,2}/, "");
|
|
52
|
+
}
|
|
53
|
+
class SimpleCLIInput {
|
|
54
|
+
constructor(normalize, schema, definition) {
|
|
55
|
+
this.normalize = normalize;
|
|
56
|
+
this.schema = schema;
|
|
57
|
+
this.definition = definition;
|
|
58
|
+
}
|
|
59
|
+
parse(raw) {
|
|
60
|
+
try {
|
|
61
|
+
return this.schema.parse(this.normalize(raw));
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error instanceof z.ZodError) {
|
|
64
|
+
const messages = error.issues.map((issue) => issue.message).filter((message) => message.length > 0);
|
|
65
|
+
if (messages.length > 0) {
|
|
66
|
+
throw new Error(messages.join("\n"));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
getDefinition() {
|
|
73
|
+
return this.definition;
|
|
74
|
+
}
|
|
75
|
+
refine(check, message) {
|
|
76
|
+
const nextSchema = this.schema.refine(
|
|
77
|
+
(value) => Boolean(check(value)),
|
|
78
|
+
message ? { message } : void 0
|
|
79
|
+
);
|
|
80
|
+
return new SimpleCLIInput(this.normalize, nextSchema, this.definition);
|
|
81
|
+
}
|
|
82
|
+
superRefine(check) {
|
|
83
|
+
const nextSchema = this.schema.superRefine(check);
|
|
84
|
+
return new SimpleCLIInput(this.normalize, nextSchema, this.definition);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
class SimpleCLICommandBuilder {
|
|
88
|
+
constructor(definition) {
|
|
89
|
+
this.definition = definition;
|
|
90
|
+
}
|
|
91
|
+
input(input2) {
|
|
92
|
+
return new SimpleCLICommandBuilder({
|
|
93
|
+
config: this.definition.config,
|
|
94
|
+
input: input2,
|
|
95
|
+
middlewares: this.definition.middlewares,
|
|
96
|
+
handler: this.definition.handler
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
use(middleware) {
|
|
100
|
+
return new SimpleCLICommandBuilder({
|
|
101
|
+
config: this.definition.config,
|
|
102
|
+
input: this.definition.input,
|
|
103
|
+
middlewares: [...this.definition.middlewares, middleware],
|
|
104
|
+
handler: this.definition.handler
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
handle(handler) {
|
|
108
|
+
return new SimpleCLICommandBuilder({
|
|
109
|
+
config: this.definition.config,
|
|
110
|
+
input: this.definition.input,
|
|
111
|
+
middlewares: this.definition.middlewares,
|
|
112
|
+
handler
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
getDefinition() {
|
|
116
|
+
return this.definition;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
class SimpleCLIApp {
|
|
120
|
+
constructor(name, routes, config = {}) {
|
|
121
|
+
this.name = name;
|
|
122
|
+
const resolution = resolveRouteTree(routes);
|
|
123
|
+
this.globalNamed = config.globalNamed ?? {};
|
|
124
|
+
for (const group2 of resolution.groups) {
|
|
125
|
+
if (this.resolvedGroups.has(group2.routeKey)) {
|
|
126
|
+
throw new Error(`Duplicate group route key: ${group2.routeKey}`);
|
|
127
|
+
}
|
|
128
|
+
this.resolvedGroups.set(group2.routeKey, group2);
|
|
129
|
+
}
|
|
130
|
+
for (const command2 of resolution.commands) {
|
|
131
|
+
if (this.resolvedCommands.has(command2.routeKey)) {
|
|
132
|
+
throw new Error(`Duplicate command route key: ${command2.routeKey}`);
|
|
133
|
+
}
|
|
134
|
+
this.resolvedCommands.set(command2.routeKey, command2);
|
|
135
|
+
}
|
|
136
|
+
this.routeEntries = resolution.routeEntries;
|
|
137
|
+
}
|
|
138
|
+
resolvedCommands = /* @__PURE__ */ new Map();
|
|
139
|
+
resolvedGroups = /* @__PURE__ */ new Map();
|
|
140
|
+
routeEntries;
|
|
141
|
+
globalNamed;
|
|
142
|
+
getCommands() {
|
|
143
|
+
return [...this.resolvedCommands.values()].map((command2) => ({
|
|
144
|
+
routeKey: command2.routeKey,
|
|
145
|
+
path: command2.path,
|
|
146
|
+
description: command2.description
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
async invoke(routeKey, rawInput, initialContext = {}) {
|
|
150
|
+
const command2 = this.resolvedCommands.get(routeKey);
|
|
151
|
+
if (!command2) {
|
|
152
|
+
throw new Error(`Unknown command route key "${routeKey}".`);
|
|
153
|
+
}
|
|
154
|
+
const input2 = command2.input ? command2.input.parse(rawInput) : rawInput;
|
|
155
|
+
let ctx = { ...initialContext };
|
|
156
|
+
const meta = {
|
|
157
|
+
routeKey: command2.routeKey,
|
|
158
|
+
path: command2.path,
|
|
159
|
+
description: command2.description
|
|
160
|
+
};
|
|
161
|
+
for (const middleware of command2.middlewares) {
|
|
162
|
+
const next = await middleware({ input: input2, ctx, command: meta });
|
|
163
|
+
if (next !== void 0) {
|
|
164
|
+
ctx = next;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return command2.handler({ input: input2, ctx, command: meta });
|
|
168
|
+
}
|
|
169
|
+
async run(args) {
|
|
170
|
+
const extractedGlobalArgs = this.extractGlobalArgs(args);
|
|
171
|
+
const normalizedArgs = extractedGlobalArgs.args;
|
|
172
|
+
const helpPath = this.resolveHelpPath(normalizedArgs);
|
|
173
|
+
if (helpPath) {
|
|
174
|
+
return this.renderHelp(helpPath);
|
|
175
|
+
}
|
|
176
|
+
const exactGroup = this.findGroupByPath(normalizedArgs);
|
|
177
|
+
if (exactGroup) {
|
|
178
|
+
return this.renderGroupHelp(exactGroup);
|
|
179
|
+
}
|
|
180
|
+
const parsed = this.parseInvocation(normalizedArgs);
|
|
181
|
+
return this.invoke(
|
|
182
|
+
parsed.routeKey,
|
|
183
|
+
this.injectGlobalNamedArgs(
|
|
184
|
+
parsed.routeKey,
|
|
185
|
+
parsed.rawInput,
|
|
186
|
+
extractedGlobalArgs.named
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
renderHelp(path = []) {
|
|
191
|
+
if (path.length === 0) {
|
|
192
|
+
return this.renderRootHelp();
|
|
193
|
+
}
|
|
194
|
+
const group2 = this.findGroupByPath(path);
|
|
195
|
+
if (group2) {
|
|
196
|
+
return this.renderGroupHelp(group2);
|
|
197
|
+
}
|
|
198
|
+
const command2 = this.findCommandByPath(path);
|
|
199
|
+
if (command2) {
|
|
200
|
+
return this.renderCommandHelp(command2);
|
|
201
|
+
}
|
|
202
|
+
throw new Error(`Unknown help topic "${path.join(" ")}".`);
|
|
203
|
+
}
|
|
204
|
+
resolveHelpPath(args) {
|
|
205
|
+
if (args.length === 0) return [];
|
|
206
|
+
if (args[0] === "help") {
|
|
207
|
+
return args.slice(1);
|
|
208
|
+
}
|
|
209
|
+
const passthroughIndex = args.indexOf("--");
|
|
210
|
+
const argsBeforePassthrough = passthroughIndex >= 0 ? args.slice(0, passthroughIndex) : args;
|
|
211
|
+
if (argsBeforePassthrough.length === 0) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
if (isHelpFlag(argsBeforePassthrough[0])) {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
const helpFlagIndex = argsBeforePassthrough.findIndex((arg) => isHelpFlag(arg));
|
|
218
|
+
if (helpFlagIndex >= 0) {
|
|
219
|
+
return argsBeforePassthrough.slice(0, helpFlagIndex);
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
parseInvocation(args) {
|
|
224
|
+
const command2 = this.findBestMatchingCommand(args);
|
|
225
|
+
if (!command2) {
|
|
226
|
+
const exactGroup = this.findGroupByPath(args);
|
|
227
|
+
if (exactGroup) {
|
|
228
|
+
throw new Error(this.renderGroupHelp(exactGroup));
|
|
229
|
+
}
|
|
230
|
+
throw new Error(`Unknown command: ${args.join(" ")}`);
|
|
231
|
+
}
|
|
232
|
+
const rawInput = this.parseCommandInput(command2, args.slice(command2.path.length));
|
|
233
|
+
return {
|
|
234
|
+
routeKey: command2.routeKey,
|
|
235
|
+
rawInput
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
parseCommandInput(command2, args) {
|
|
239
|
+
const inputDefinition = command2.input?.getDefinition();
|
|
240
|
+
if (!inputDefinition) {
|
|
241
|
+
if (args.length > 0) {
|
|
242
|
+
throw new Error(`Unexpected arguments for ${this.name} ${command2.path.join(" ")}.`);
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
positionals: [],
|
|
246
|
+
named: {}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const positionals = [];
|
|
250
|
+
const named = {};
|
|
251
|
+
const namedSpecs = buildNamedArgLookup(inputDefinition.named);
|
|
252
|
+
const passthroughEntry = Object.entries(inputDefinition.named).find(
|
|
253
|
+
([, spec]) => spec.source === "--"
|
|
254
|
+
);
|
|
255
|
+
for (let index = 0; index < args.length; index++) {
|
|
256
|
+
const arg = args[index];
|
|
257
|
+
if (arg === "--") {
|
|
258
|
+
if (!passthroughEntry) {
|
|
259
|
+
throw new Error(`Unexpected "--" for ${this.name} ${command2.path.join(" ")}.`);
|
|
260
|
+
}
|
|
261
|
+
named["--"] = args.slice(index + 1);
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
if (arg.startsWith("--")) {
|
|
265
|
+
const [rawName, inlineValue] = splitNamedArg(arg.slice(2));
|
|
266
|
+
const namedEntry = namedSpecs.get(rawName);
|
|
267
|
+
if (!namedEntry) {
|
|
268
|
+
throw new Error(`Unknown option: --${rawName}`);
|
|
269
|
+
}
|
|
270
|
+
const storeKey = buildNamedArgFlagName(namedEntry.key, namedEntry.spec);
|
|
271
|
+
named[storeKey] = readNamedArgValue(
|
|
272
|
+
args,
|
|
273
|
+
index,
|
|
274
|
+
rawName,
|
|
275
|
+
`--${rawName}`,
|
|
276
|
+
namedEntry.spec,
|
|
277
|
+
inlineValue,
|
|
278
|
+
namedSpecs
|
|
279
|
+
);
|
|
280
|
+
if (inlineValue === void 0 && namedEntry.spec.kind !== "flag") {
|
|
281
|
+
index += 1;
|
|
282
|
+
}
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (arg.startsWith("-")) {
|
|
286
|
+
const [rawName, inlineValue] = splitNamedArg(arg.slice(1));
|
|
287
|
+
const namedEntry = namedSpecs.get(rawName);
|
|
288
|
+
if (!namedEntry) {
|
|
289
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
290
|
+
}
|
|
291
|
+
const storeKey = buildNamedArgFlagName(namedEntry.key, namedEntry.spec);
|
|
292
|
+
named[storeKey] = readNamedArgValue(
|
|
293
|
+
args,
|
|
294
|
+
index,
|
|
295
|
+
rawName,
|
|
296
|
+
`-${rawName}`,
|
|
297
|
+
namedEntry.spec,
|
|
298
|
+
inlineValue,
|
|
299
|
+
namedSpecs
|
|
300
|
+
);
|
|
301
|
+
if (inlineValue === void 0 && namedEntry.spec.kind !== "flag") {
|
|
302
|
+
index += 1;
|
|
303
|
+
}
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
positionals.push(arg);
|
|
307
|
+
}
|
|
308
|
+
validateParsedPositionals(command2, inputDefinition.positionals, positionals);
|
|
309
|
+
validateRequiredNamedArgs(inputDefinition.named, named);
|
|
310
|
+
return {
|
|
311
|
+
positionals,
|
|
312
|
+
named
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
extractGlobalArgs(args) {
|
|
316
|
+
if (Object.keys(this.globalNamed).length === 0) {
|
|
317
|
+
return {
|
|
318
|
+
args,
|
|
319
|
+
named: {}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const remainingArgs = [];
|
|
323
|
+
const named = {};
|
|
324
|
+
const namedSpecs = buildNamedArgLookup(this.globalNamed);
|
|
325
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
326
|
+
const arg = args[index];
|
|
327
|
+
if (arg === "--") {
|
|
328
|
+
remainingArgs.push(...args.slice(index));
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
if (arg.startsWith("--")) {
|
|
332
|
+
const [rawName, inlineValue] = splitNamedArg(arg.slice(2));
|
|
333
|
+
const namedEntry = namedSpecs.get(rawName);
|
|
334
|
+
if (!namedEntry) {
|
|
335
|
+
remainingArgs.push(arg);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
named[namedEntry.key] = readNamedArgValue(
|
|
339
|
+
args,
|
|
340
|
+
index,
|
|
341
|
+
rawName,
|
|
342
|
+
`--${rawName}`,
|
|
343
|
+
namedEntry.spec,
|
|
344
|
+
inlineValue,
|
|
345
|
+
namedSpecs
|
|
346
|
+
);
|
|
347
|
+
if (inlineValue === void 0 && namedEntry.spec.kind !== "flag") {
|
|
348
|
+
index += 1;
|
|
349
|
+
}
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (arg.startsWith("-")) {
|
|
353
|
+
const [rawName, inlineValue] = splitNamedArg(arg.slice(1));
|
|
354
|
+
const namedEntry = namedSpecs.get(rawName);
|
|
355
|
+
if (!namedEntry) {
|
|
356
|
+
remainingArgs.push(arg);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
named[namedEntry.key] = readNamedArgValue(
|
|
360
|
+
args,
|
|
361
|
+
index,
|
|
362
|
+
rawName,
|
|
363
|
+
`-${rawName}`,
|
|
364
|
+
namedEntry.spec,
|
|
365
|
+
inlineValue,
|
|
366
|
+
namedSpecs
|
|
367
|
+
);
|
|
368
|
+
if (inlineValue === void 0 && namedEntry.spec.kind !== "flag") {
|
|
369
|
+
index += 1;
|
|
370
|
+
}
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
remainingArgs.push(arg);
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
args: remainingArgs,
|
|
377
|
+
named
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
injectGlobalNamedArgs(routeKey, rawInput, globalNamed) {
|
|
381
|
+
if (Object.keys(globalNamed).length === 0) {
|
|
382
|
+
return rawInput;
|
|
383
|
+
}
|
|
384
|
+
const inputDefinition = this.resolvedCommands.get(routeKey)?.input?.getDefinition();
|
|
385
|
+
if (!inputDefinition) {
|
|
386
|
+
return rawInput;
|
|
387
|
+
}
|
|
388
|
+
const named = { ...rawInput.named ?? {} };
|
|
389
|
+
let changed = false;
|
|
390
|
+
for (const key of Object.keys(inputDefinition.named)) {
|
|
391
|
+
if (Object.prototype.hasOwnProperty.call(named, key)) continue;
|
|
392
|
+
if (!Object.prototype.hasOwnProperty.call(globalNamed, key)) continue;
|
|
393
|
+
named[key] = globalNamed[key];
|
|
394
|
+
changed = true;
|
|
395
|
+
}
|
|
396
|
+
if (!changed) {
|
|
397
|
+
return rawInput;
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
positionals: rawInput.positionals ?? [],
|
|
401
|
+
named
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
renderRootHelp() {
|
|
405
|
+
const lines = [`Usage: ${this.name} <command>`, "", "Commands:"];
|
|
406
|
+
for (const entry of this.getImmediateRouteEntries([])) {
|
|
407
|
+
lines.push(formatListEntry(entry.label, entry.description));
|
|
408
|
+
}
|
|
409
|
+
return lines.join("\n");
|
|
410
|
+
}
|
|
411
|
+
renderGroupHelp(group2) {
|
|
412
|
+
const lines = [];
|
|
413
|
+
if (group2.description) {
|
|
414
|
+
lines.push(group2.description, "");
|
|
415
|
+
}
|
|
416
|
+
lines.push(`Usage: ${this.name} ${group2.path.join(" ")} <subcommand>`);
|
|
417
|
+
lines.push("", "Commands:");
|
|
418
|
+
for (const entry of this.getImmediateRouteEntries(group2.path)) {
|
|
419
|
+
lines.push(formatListEntry(entry.label, entry.description));
|
|
420
|
+
}
|
|
421
|
+
return lines.join("\n");
|
|
422
|
+
}
|
|
423
|
+
renderCommandHelp(command2) {
|
|
424
|
+
const lines = [
|
|
425
|
+
command2.description,
|
|
426
|
+
"",
|
|
427
|
+
`Usage: ${this.buildCommandUsage(command2)}`
|
|
428
|
+
];
|
|
429
|
+
const inputDefinition = command2.input?.getDefinition();
|
|
430
|
+
if (!inputDefinition) {
|
|
431
|
+
return lines.join("\n");
|
|
432
|
+
}
|
|
433
|
+
const argumentLines = inputDefinition.positionals.map(
|
|
434
|
+
(positional2) => formatListEntry(buildUsagePositionalToken(positional2), positional2.help)
|
|
435
|
+
);
|
|
436
|
+
if (argumentLines.length > 0) {
|
|
437
|
+
lines.push("", "Arguments:");
|
|
438
|
+
lines.push(...argumentLines);
|
|
439
|
+
}
|
|
440
|
+
const optionLines = Object.entries(inputDefinition.named).map(
|
|
441
|
+
([key, spec]) => formatListEntry(buildNamedArgHelpLabel(key, spec), spec.help)
|
|
442
|
+
);
|
|
443
|
+
if (optionLines.length > 0) {
|
|
444
|
+
lines.push("", "Options:");
|
|
445
|
+
lines.push(...optionLines);
|
|
446
|
+
}
|
|
447
|
+
return lines.join("\n");
|
|
448
|
+
}
|
|
449
|
+
buildCommandUsage(command2) {
|
|
450
|
+
const tokens = [this.name, ...command2.path];
|
|
451
|
+
const inputDefinition = command2.input?.getDefinition();
|
|
452
|
+
if (!inputDefinition) {
|
|
453
|
+
return tokens.join(" ");
|
|
454
|
+
}
|
|
455
|
+
tokens.push(...inputDefinition.positionals.map(buildUsagePositionalToken));
|
|
456
|
+
if (Object.keys(inputDefinition.named).length > 0) {
|
|
457
|
+
tokens.push("[options]");
|
|
458
|
+
}
|
|
459
|
+
return tokens.join(" ");
|
|
460
|
+
}
|
|
461
|
+
getImmediateRouteEntries(path) {
|
|
462
|
+
const seen = /* @__PURE__ */ new Set();
|
|
463
|
+
const entries = [];
|
|
464
|
+
for (const routeEntry of this.routeEntries) {
|
|
465
|
+
if (!pathStartsWith(routeEntry.path, path)) continue;
|
|
466
|
+
if (routeEntry.path.length !== path.length + 1) continue;
|
|
467
|
+
const token = routeEntry.path[path.length];
|
|
468
|
+
if (seen.has(token)) continue;
|
|
469
|
+
seen.add(token);
|
|
470
|
+
if (routeEntry.kind === "group") {
|
|
471
|
+
const group2 = this.findGroupByPath(routeEntry.path);
|
|
472
|
+
entries.push({
|
|
473
|
+
label: `${token} <subcommand>`,
|
|
474
|
+
description: group2?.description
|
|
475
|
+
});
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const command2 = this.findCommandByPath(routeEntry.path);
|
|
479
|
+
entries.push({
|
|
480
|
+
label: token,
|
|
481
|
+
description: command2?.description
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
return entries;
|
|
485
|
+
}
|
|
486
|
+
findBestMatchingCommand(args) {
|
|
487
|
+
let bestMatch = null;
|
|
488
|
+
for (const command2 of this.resolvedCommands.values()) {
|
|
489
|
+
if (command2.path.length > args.length) continue;
|
|
490
|
+
if (!pathStartsWith(args, command2.path)) continue;
|
|
491
|
+
if (!bestMatch || command2.path.length > bestMatch.path.length) {
|
|
492
|
+
bestMatch = command2;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return bestMatch;
|
|
496
|
+
}
|
|
497
|
+
findCommandByPath(path) {
|
|
498
|
+
const routeKey = pathToRouteKey(path);
|
|
499
|
+
return this.resolvedCommands.get(routeKey) ?? null;
|
|
500
|
+
}
|
|
501
|
+
findGroupByPath(path) {
|
|
502
|
+
const routeKey = pathToRouteKey(path);
|
|
503
|
+
return this.resolvedGroups.get(routeKey) ?? null;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function splitNamedArg(arg) {
|
|
507
|
+
const separatorIndex = arg.indexOf("=");
|
|
508
|
+
if (separatorIndex < 0) return [arg, void 0];
|
|
509
|
+
return [
|
|
510
|
+
arg.slice(0, separatorIndex),
|
|
511
|
+
arg.slice(separatorIndex + 1)
|
|
512
|
+
];
|
|
513
|
+
}
|
|
514
|
+
function readNamedArgValue(args, index, rawName, displayName, spec, inlineValue, namedSpecs) {
|
|
515
|
+
if (spec.kind === "flag") {
|
|
516
|
+
return inlineValue === void 0 ? true : parseBooleanFlagValue(inlineValue, rawName);
|
|
517
|
+
}
|
|
518
|
+
if (inlineValue !== void 0) {
|
|
519
|
+
return inlineValue;
|
|
520
|
+
}
|
|
521
|
+
const nextValue = args[index + 1];
|
|
522
|
+
if (nextValue === void 0 || nextValue === "--" || isRecognizedNamedArgToken(nextValue, namedSpecs)) {
|
|
523
|
+
throw new Error(`Missing value for ${displayName}.`);
|
|
524
|
+
}
|
|
525
|
+
return nextValue;
|
|
526
|
+
}
|
|
527
|
+
function isRecognizedNamedArgToken(token, namedSpecs) {
|
|
528
|
+
if (token === "-" || !token.startsWith("-")) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
const normalizedToken = token.startsWith("--") ? token.slice(2) : token.slice(1);
|
|
532
|
+
const [rawName] = splitNamedArg(normalizedToken);
|
|
533
|
+
return namedSpecs.has(rawName);
|
|
534
|
+
}
|
|
535
|
+
function buildNamedArgLookup(namedDefinition) {
|
|
536
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
537
|
+
for (const [key, spec] of Object.entries(namedDefinition)) {
|
|
538
|
+
if (spec.source === "--") continue;
|
|
539
|
+
const flagName = buildNamedArgFlagName(key, spec);
|
|
540
|
+
lookup.set(flagName, { key, spec });
|
|
541
|
+
lookup.set(key, { key, spec });
|
|
542
|
+
lookup.set(toCamelCase(flagName), { key, spec });
|
|
543
|
+
for (const alias of spec.aliases ?? []) {
|
|
544
|
+
const normalizedAlias = normalizeNamedArgToken(alias);
|
|
545
|
+
lookup.set(normalizedAlias, { key, spec });
|
|
546
|
+
lookup.set(toCamelCase(normalizedAlias), { key, spec });
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return lookup;
|
|
550
|
+
}
|
|
551
|
+
function validateParsedPositionals(command2, definitions, positionals) {
|
|
552
|
+
const variadicDefinition = definitions.find((definition) => definition.variadic);
|
|
553
|
+
if (!variadicDefinition && positionals.length > definitions.length) {
|
|
554
|
+
throw new Error(`Unexpected arguments for ${command2.path.join(" ")}.`);
|
|
555
|
+
}
|
|
556
|
+
definitions.forEach((definition, index) => {
|
|
557
|
+
const value = definition.variadic ? positionals.slice(index) : positionals[index];
|
|
558
|
+
if (value !== void 0 && (!Array.isArray(value) || value.length > 0)) return;
|
|
559
|
+
if (schemaAcceptsUndefined(definition.schema)) return;
|
|
560
|
+
throw new Error(`Missing required argument <${definition.key}>.`);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
function validateInputDefinition(definition) {
|
|
564
|
+
const variadicIndex = definition.positionals.findIndex((positional2) => positional2.variadic);
|
|
565
|
+
if (variadicIndex < 0) return;
|
|
566
|
+
if (variadicIndex !== definition.positionals.length - 1) {
|
|
567
|
+
throw new Error("Variadic positional arguments must be the last positional.");
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
function validateRequiredNamedArgs(definitions, named) {
|
|
571
|
+
for (const [key, spec] of Object.entries(definitions)) {
|
|
572
|
+
if (schemaAcceptsUndefined(spec.schema)) continue;
|
|
573
|
+
const flagName = spec.source === "--" ? "--" : buildNamedArgFlagName(key, spec);
|
|
574
|
+
if (Object.prototype.hasOwnProperty.call(named, flagName)) continue;
|
|
575
|
+
if (spec.source === "--") {
|
|
576
|
+
throw new Error(`Missing required passthrough arguments after --.`);
|
|
577
|
+
}
|
|
578
|
+
throw new Error(`Missing required option --${flagName}.`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function resolveRouteTree(routes, parentPath = [], parentMiddlewares = []) {
|
|
582
|
+
const resolved = {
|
|
583
|
+
commands: [],
|
|
584
|
+
groups: [],
|
|
585
|
+
routeEntries: []
|
|
586
|
+
};
|
|
587
|
+
for (const [token, routeValue] of Object.entries(routes)) {
|
|
588
|
+
if (isGroup(routeValue)) {
|
|
589
|
+
const groupPath = [...parentPath, token];
|
|
590
|
+
resolved.groups.push({
|
|
591
|
+
routeKey: pathToRouteKey(groupPath),
|
|
592
|
+
path: groupPath,
|
|
593
|
+
description: routeValue.description
|
|
594
|
+
});
|
|
595
|
+
resolved.routeEntries.push({
|
|
596
|
+
kind: "group",
|
|
597
|
+
path: groupPath
|
|
598
|
+
});
|
|
599
|
+
const nested = resolveRouteTree(
|
|
600
|
+
routeValue.routes,
|
|
601
|
+
groupPath,
|
|
602
|
+
[...parentMiddlewares, ...routeValue.middlewares]
|
|
603
|
+
);
|
|
604
|
+
resolved.commands.push(...nested.commands);
|
|
605
|
+
resolved.groups.push(...nested.groups);
|
|
606
|
+
resolved.routeEntries.push(...nested.routeEntries);
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
const command2 = routeValue.getDefinition();
|
|
610
|
+
if (!command2.handler) {
|
|
611
|
+
throw new Error(`Command "${[...parentPath, token].join(" ")}" is missing a handler.`);
|
|
612
|
+
}
|
|
613
|
+
const path = [...parentPath, token];
|
|
614
|
+
resolved.commands.push({
|
|
615
|
+
routeKey: pathToRouteKey(path),
|
|
616
|
+
path,
|
|
617
|
+
description: command2.config.description,
|
|
618
|
+
input: command2.input,
|
|
619
|
+
middlewares: mergeInheritedMiddlewares(parentMiddlewares, command2.middlewares),
|
|
620
|
+
handler: command2.handler
|
|
621
|
+
});
|
|
622
|
+
resolved.routeEntries.push({
|
|
623
|
+
kind: "command",
|
|
624
|
+
path
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
return resolved;
|
|
628
|
+
}
|
|
629
|
+
function mergeInheritedMiddlewares(parentMiddlewares, commandMiddlewares) {
|
|
630
|
+
if (parentMiddlewares.length === 0) {
|
|
631
|
+
return [...commandMiddlewares];
|
|
632
|
+
}
|
|
633
|
+
if (commandMiddlewares.length >= parentMiddlewares.length && parentMiddlewares.every((middleware, index) => commandMiddlewares[index] === middleware)) {
|
|
634
|
+
return [...commandMiddlewares];
|
|
635
|
+
}
|
|
636
|
+
return [...parentMiddlewares, ...commandMiddlewares];
|
|
637
|
+
}
|
|
638
|
+
function isGroup(value) {
|
|
639
|
+
return value.kind === "group";
|
|
640
|
+
}
|
|
641
|
+
function buildInputNormalizer(definition) {
|
|
642
|
+
return (raw) => {
|
|
643
|
+
const output = {};
|
|
644
|
+
const positionals = raw.positionals ?? [];
|
|
645
|
+
const named = raw.named ?? {};
|
|
646
|
+
definition.positionals.forEach((positional2, index) => {
|
|
647
|
+
output[positional2.key] = positional2.variadic ? positionals.slice(index) : positionals[index];
|
|
648
|
+
});
|
|
649
|
+
for (const [key, spec] of Object.entries(definition.named)) {
|
|
650
|
+
const sourceKey = spec.source === "--" ? "--" : spec.name ?? key;
|
|
651
|
+
const normalizedCandidates = [
|
|
652
|
+
sourceKey,
|
|
653
|
+
spec.name ? toCamelCase(spec.name) : "",
|
|
654
|
+
...(spec.aliases ?? []).flatMap((alias) => {
|
|
655
|
+
const normalizedAlias = normalizeNamedArgToken(alias);
|
|
656
|
+
return [
|
|
657
|
+
normalizedAlias,
|
|
658
|
+
toCamelCase(normalizedAlias)
|
|
659
|
+
];
|
|
660
|
+
}),
|
|
661
|
+
toKebabCase(key),
|
|
662
|
+
key
|
|
663
|
+
].filter((candidate) => candidate.length > 0);
|
|
664
|
+
let value = void 0;
|
|
665
|
+
for (const candidate of normalizedCandidates) {
|
|
666
|
+
if (Object.prototype.hasOwnProperty.call(named, candidate)) {
|
|
667
|
+
value = named[candidate];
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
output[key] = value;
|
|
672
|
+
}
|
|
673
|
+
return output;
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function buildInputSchema(definition) {
|
|
677
|
+
const shape = {};
|
|
678
|
+
for (const positional2 of definition.positionals) {
|
|
679
|
+
shape[positional2.key] = positional2.schema;
|
|
680
|
+
}
|
|
681
|
+
for (const [key, named] of Object.entries(definition.named)) {
|
|
682
|
+
shape[key] = named.schema;
|
|
683
|
+
}
|
|
684
|
+
return zodObjectFromShape(shape);
|
|
685
|
+
}
|
|
686
|
+
function positional(key, schema, options) {
|
|
687
|
+
return {
|
|
688
|
+
kind: "positional",
|
|
689
|
+
key,
|
|
690
|
+
schema,
|
|
691
|
+
help: options?.help,
|
|
692
|
+
variadic: options?.variadic
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function option(schema, options) {
|
|
696
|
+
return {
|
|
697
|
+
kind: "option",
|
|
698
|
+
schema,
|
|
699
|
+
help: options?.help,
|
|
700
|
+
name: options?.name,
|
|
701
|
+
aliases: options?.aliases,
|
|
702
|
+
source: options?.source
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function flag(options) {
|
|
706
|
+
return {
|
|
707
|
+
kind: "flag",
|
|
708
|
+
schema: z.boolean().default(false),
|
|
709
|
+
help: options?.help,
|
|
710
|
+
name: options?.name,
|
|
711
|
+
aliases: options?.aliases
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
function input(definition) {
|
|
715
|
+
validateInputDefinition(definition);
|
|
716
|
+
return new SimpleCLIInput(
|
|
717
|
+
buildInputNormalizer(definition),
|
|
718
|
+
buildInputSchema(definition),
|
|
719
|
+
definition
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
function command(config) {
|
|
723
|
+
return new SimpleCLICommandBuilder({
|
|
724
|
+
config,
|
|
725
|
+
middlewares: []
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
function group(config) {
|
|
729
|
+
return createScope([]).group(config);
|
|
730
|
+
}
|
|
731
|
+
function createScope(middlewares) {
|
|
732
|
+
return {
|
|
733
|
+
use(middleware) {
|
|
734
|
+
return createScope([
|
|
735
|
+
...middlewares,
|
|
736
|
+
middleware
|
|
737
|
+
]);
|
|
738
|
+
},
|
|
739
|
+
group(config) {
|
|
740
|
+
return {
|
|
741
|
+
kind: "group",
|
|
742
|
+
description: config.description,
|
|
743
|
+
routes: config.routes,
|
|
744
|
+
middlewares: [...middlewares]
|
|
745
|
+
};
|
|
746
|
+
},
|
|
747
|
+
command(config) {
|
|
748
|
+
return new SimpleCLICommandBuilder({
|
|
749
|
+
config,
|
|
750
|
+
middlewares: [...middlewares]
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function use(middleware) {
|
|
756
|
+
return createScope([middleware]);
|
|
757
|
+
}
|
|
758
|
+
function define(name, routes, config) {
|
|
759
|
+
return new SimpleCLIApp(name, routes, config);
|
|
760
|
+
}
|
|
761
|
+
const SimpleCLI = {
|
|
762
|
+
define,
|
|
763
|
+
command,
|
|
764
|
+
group,
|
|
765
|
+
use,
|
|
766
|
+
input,
|
|
767
|
+
positional,
|
|
768
|
+
option,
|
|
769
|
+
flag
|
|
770
|
+
};
|
|
771
|
+
export {
|
|
772
|
+
SimpleCLI,
|
|
773
|
+
SimpleCLIApp,
|
|
774
|
+
SimpleCLICommandBuilder,
|
|
775
|
+
SimpleCLIInput
|
|
776
|
+
};
|