libretto 0.5.3-experimental.5 → 0.5.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 +114 -37
- package/README.template.md +160 -0
- package/dist/cli/cli.js +22 -97
- package/dist/cli/commands/browser.js +86 -59
- package/dist/cli/commands/deploy.js +148 -0
- package/dist/cli/commands/execution.js +218 -96
- package/dist/cli/commands/init.js +34 -29
- package/dist/cli/commands/logs.js +4 -5
- package/dist/cli/commands/shared.js +30 -29
- package/dist/cli/commands/snapshot.js +26 -39
- package/dist/cli/core/ai-config.js +21 -4
- package/dist/cli/core/api-snapshot-analyzer.js +15 -5
- package/dist/cli/core/browser.js +207 -37
- package/dist/cli/core/context.js +4 -1
- package/dist/cli/core/deploy-artifact.js +687 -0
- package/dist/cli/core/session-telemetry.js +434 -174
- package/dist/cli/core/session.js +21 -8
- package/dist/cli/core/snapshot-analyzer.js +14 -31
- package/dist/cli/core/snapshot-api-config.js +2 -6
- package/dist/cli/core/telemetry.js +20 -4
- package/dist/cli/framework/simple-cli.js +144 -43
- package/dist/cli/router.js +16 -21
- package/dist/cli/workers/run-integration-runtime.js +25 -45
- package/dist/cli/workers/run-integration-worker-protocol.js +3 -2
- package/dist/cli/workers/run-integration-worker.js +1 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.js +13 -10
- package/dist/runtime/download/download.js +5 -1
- package/dist/runtime/extract/extract.js +11 -2
- package/dist/runtime/network/network.js +8 -1
- package/dist/runtime/recovery/agent.js +6 -2
- package/dist/runtime/recovery/errors.js +3 -1
- package/dist/runtime/recovery/recovery.js +3 -1
- package/dist/shared/condense-dom/condense-dom.js +17 -69
- package/dist/shared/config/config.d.ts +1 -9
- package/dist/shared/config/config.js +0 -18
- package/dist/shared/config/index.d.ts +2 -1
- package/dist/shared/config/index.js +0 -10
- package/dist/shared/debug/pause.js +9 -3
- package/dist/shared/dom-semantics.d.ts +8 -0
- package/dist/shared/dom-semantics.js +69 -0
- package/dist/shared/instrumentation/instrument.js +101 -5
- package/dist/shared/llm/ai-sdk-adapter.js +3 -1
- package/dist/shared/llm/client.js +3 -1
- package/dist/shared/logger/index.js +4 -1
- package/dist/shared/run/api.js +3 -1
- package/dist/shared/run/browser.js +47 -3
- package/dist/shared/state/session-state.d.ts +2 -1
- package/dist/shared/state/session-state.js +5 -2
- package/dist/shared/visualization/ghost-cursor.js +36 -14
- package/dist/shared/visualization/highlight.js +9 -6
- package/dist/shared/workflow/workflow.d.ts +18 -10
- package/dist/shared/workflow/workflow.js +50 -5
- package/package.json +14 -6
- package/scripts/generate-changelog.ts +132 -0
- package/scripts/postinstall.mjs +4 -3
- package/scripts/skills-libretto.mjs +2 -88
- package/scripts/summarize-evals.mjs +32 -10
- package/skills/libretto/SKILL.md +132 -62
- package/skills/libretto/references/action-logs.md +101 -0
- package/skills/libretto/references/auth-profiles.md +1 -2
- package/skills/libretto/references/code-generation-rules.md +176 -0
- package/skills/libretto/references/configuration-file-reference.md +53 -0
- package/skills/libretto/references/pages-and-page-targeting.md +1 -1
- package/skills/libretto/references/site-security-review.md +143 -0
- package/src/cli/cli.ts +23 -110
- package/src/cli/commands/browser.ts +94 -70
- package/src/cli/commands/deploy.ts +198 -0
- package/src/cli/commands/execution.ts +251 -111
- package/src/cli/commands/init.ts +37 -33
- package/src/cli/commands/logs.ts +7 -7
- package/src/cli/commands/shared.ts +36 -37
- package/src/cli/commands/snapshot.ts +44 -59
- package/src/cli/core/ai-config.ts +24 -4
- package/src/cli/core/api-snapshot-analyzer.ts +17 -6
- package/src/cli/core/browser.ts +260 -49
- package/src/cli/core/context.ts +7 -2
- package/src/cli/core/deploy-artifact.ts +938 -0
- package/src/cli/core/session-telemetry.ts +449 -197
- package/src/cli/core/session.ts +21 -7
- package/src/cli/core/snapshot-analyzer.ts +26 -46
- package/src/cli/core/snapshot-api-config.ts +170 -175
- package/src/cli/core/telemetry.ts +39 -4
- package/src/cli/framework/simple-cli.ts +281 -98
- package/src/cli/router.ts +15 -21
- package/src/cli/workers/run-integration-runtime.ts +35 -57
- package/src/cli/workers/run-integration-worker-protocol.ts +2 -1
- package/src/cli/workers/run-integration-worker.ts +1 -4
- package/src/index.ts +77 -67
- package/src/runtime/download/download.ts +62 -58
- package/src/runtime/download/index.ts +5 -5
- package/src/runtime/extract/extract.ts +71 -61
- package/src/runtime/network/index.ts +3 -3
- package/src/runtime/network/network.ts +99 -93
- package/src/runtime/recovery/agent.ts +217 -212
- package/src/runtime/recovery/errors.ts +107 -104
- package/src/runtime/recovery/index.ts +3 -3
- package/src/runtime/recovery/recovery.ts +38 -35
- package/src/shared/condense-dom/condense-dom.ts +27 -82
- package/src/shared/config/config.ts +0 -19
- package/src/shared/config/index.ts +0 -5
- package/src/shared/debug/pause.ts +57 -51
- package/src/shared/dom-semantics.ts +68 -0
- package/src/shared/instrumentation/errors.ts +64 -62
- package/src/shared/instrumentation/index.ts +5 -5
- package/src/shared/instrumentation/instrument.ts +339 -209
- package/src/shared/llm/ai-sdk-adapter.ts +58 -55
- package/src/shared/llm/client.ts +181 -174
- package/src/shared/llm/types.ts +39 -39
- package/src/shared/logger/index.ts +11 -4
- package/src/shared/logger/logger.ts +312 -306
- package/src/shared/logger/sinks.ts +118 -114
- package/src/shared/paths/paths.ts +50 -49
- package/src/shared/paths/repo-root.ts +17 -17
- package/src/shared/run/api.ts +5 -1
- package/src/shared/run/browser.ts +65 -3
- package/src/shared/state/index.ts +9 -9
- package/src/shared/state/session-state.ts +46 -43
- package/src/shared/visualization/ghost-cursor.ts +180 -149
- package/src/shared/visualization/highlight.ts +89 -86
- package/src/shared/visualization/index.ts +13 -13
- package/src/shared/workflow/workflow.ts +107 -30
- package/scripts/check-skills-sync.mjs +0 -23
- package/scripts/prepare-release.sh +0 -97
- package/skills/libretto/references/reverse-engineering-network-requests.md +0 -75
- package/skills/libretto/references/user-action-log.md +0 -31
|
@@ -4,6 +4,7 @@ type RecordUnknown = Record<string, unknown>;
|
|
|
4
4
|
|
|
5
5
|
export type SimpleCLICommandConfig = {
|
|
6
6
|
description: string;
|
|
7
|
+
experimental?: boolean;
|
|
7
8
|
};
|
|
8
9
|
|
|
9
10
|
export type SimpleCLIInputRaw = {
|
|
@@ -49,7 +50,10 @@ type SimpleCLIPositionalsDefinition = readonly SimpleCLIPositionalDefinition<
|
|
|
49
50
|
ZodTypeAny
|
|
50
51
|
>[];
|
|
51
52
|
|
|
52
|
-
type SimpleCLINamedDefinition = Record<
|
|
53
|
+
type SimpleCLINamedDefinition = Record<
|
|
54
|
+
string,
|
|
55
|
+
SimpleCLINamedArgDefinition<ZodTypeAny>
|
|
56
|
+
>;
|
|
53
57
|
|
|
54
58
|
type SimpleCLIInputDefinition = {
|
|
55
59
|
positionals: SimpleCLIPositionalsDefinition;
|
|
@@ -97,7 +101,8 @@ type NormalizedCommandDefinition<
|
|
|
97
101
|
|
|
98
102
|
type SimpleCLIRouteTree<TContext extends SimpleCLIContext = {}> = Record<
|
|
99
103
|
string,
|
|
100
|
-
|
|
104
|
+
| SimpleCLIGroup<TContext, any>
|
|
105
|
+
| SimpleCLICommandBuilder<any, TContext, any, any>
|
|
101
106
|
>;
|
|
102
107
|
|
|
103
108
|
export type SimpleCLIResolvedCommand = {
|
|
@@ -107,6 +112,7 @@ export type SimpleCLIResolvedCommand = {
|
|
|
107
112
|
};
|
|
108
113
|
|
|
109
114
|
type InternalResolvedCommand = SimpleCLIResolvedCommand & {
|
|
115
|
+
experimental?: boolean;
|
|
110
116
|
input?: SimpleCLIInput<unknown>;
|
|
111
117
|
middlewares: AnySimpleCLIMiddleware[];
|
|
112
118
|
handler: SimpleCLIHandler<unknown, SimpleCLIContext, unknown>;
|
|
@@ -123,6 +129,11 @@ type InternalResolvedRouteEntry = {
|
|
|
123
129
|
path: readonly string[];
|
|
124
130
|
};
|
|
125
131
|
|
|
132
|
+
type CollectedRouteTreeResult = {
|
|
133
|
+
commands: InternalResolvedCommand[];
|
|
134
|
+
groupDescriptions: Map<string, string | undefined>;
|
|
135
|
+
};
|
|
136
|
+
|
|
126
137
|
type ResolveRouteTreeResult = {
|
|
127
138
|
commands: InternalResolvedCommand[];
|
|
128
139
|
groups: InternalResolvedGroup[];
|
|
@@ -144,6 +155,9 @@ type ExtractedGlobalArgs = {
|
|
|
144
155
|
named: Readonly<Record<string, unknown>>;
|
|
145
156
|
};
|
|
146
157
|
|
|
158
|
+
const EXPERIMENTAL_COMMAND_PREFIX = "experimental";
|
|
159
|
+
const EXPERIMENTAL_GROUP_DESCRIPTION = "Experimental commands";
|
|
160
|
+
|
|
147
161
|
function toCamelCase(input: string): string {
|
|
148
162
|
return input.replace(/-([a-zA-Z0-9])/g, (_match, letter: string) =>
|
|
149
163
|
letter.toUpperCase(),
|
|
@@ -295,13 +309,23 @@ export class SimpleCLICommandBuilder<
|
|
|
295
309
|
TResult,
|
|
296
310
|
> {
|
|
297
311
|
constructor(
|
|
298
|
-
private readonly definition: NormalizedCommandDefinition<
|
|
312
|
+
private readonly definition: NormalizedCommandDefinition<
|
|
313
|
+
TInput,
|
|
314
|
+
TContextIn,
|
|
315
|
+
TContext,
|
|
316
|
+
TResult
|
|
317
|
+
>,
|
|
299
318
|
) {}
|
|
300
319
|
|
|
301
320
|
input<TNextInput>(
|
|
302
321
|
input: SimpleCLIInput<TNextInput>,
|
|
303
322
|
): SimpleCLICommandBuilder<TNextInput, TContextIn, TContext, TResult> {
|
|
304
|
-
return new SimpleCLICommandBuilder<
|
|
323
|
+
return new SimpleCLICommandBuilder<
|
|
324
|
+
TNextInput,
|
|
325
|
+
TContextIn,
|
|
326
|
+
TContext,
|
|
327
|
+
TResult
|
|
328
|
+
>({
|
|
305
329
|
config: this.definition.config,
|
|
306
330
|
input,
|
|
307
331
|
middlewares: this.definition.middlewares,
|
|
@@ -332,7 +356,12 @@ export class SimpleCLICommandBuilder<
|
|
|
332
356
|
handle<TNextResult>(
|
|
333
357
|
handler: SimpleCLIHandler<TInput, TContext, TNextResult>,
|
|
334
358
|
): SimpleCLICommandBuilder<TInput, TContextIn, TContext, TNextResult> {
|
|
335
|
-
return new SimpleCLICommandBuilder<
|
|
359
|
+
return new SimpleCLICommandBuilder<
|
|
360
|
+
TInput,
|
|
361
|
+
TContextIn,
|
|
362
|
+
TContext,
|
|
363
|
+
TNextResult
|
|
364
|
+
>({
|
|
336
365
|
config: this.definition.config,
|
|
337
366
|
input: this.definition.input,
|
|
338
367
|
middlewares: this.definition.middlewares,
|
|
@@ -340,7 +369,12 @@ export class SimpleCLICommandBuilder<
|
|
|
340
369
|
});
|
|
341
370
|
}
|
|
342
371
|
|
|
343
|
-
getDefinition(): NormalizedCommandDefinition<
|
|
372
|
+
getDefinition(): NormalizedCommandDefinition<
|
|
373
|
+
TInput,
|
|
374
|
+
TContextIn,
|
|
375
|
+
TContext,
|
|
376
|
+
TResult
|
|
377
|
+
> {
|
|
344
378
|
return this.definition;
|
|
345
379
|
}
|
|
346
380
|
}
|
|
@@ -358,7 +392,10 @@ export type SimpleCLIGroup<
|
|
|
358
392
|
};
|
|
359
393
|
|
|
360
394
|
export class SimpleCLIApp {
|
|
361
|
-
private readonly resolvedCommands = new Map<
|
|
395
|
+
private readonly resolvedCommands = new Map<
|
|
396
|
+
string,
|
|
397
|
+
InternalResolvedCommand
|
|
398
|
+
>();
|
|
362
399
|
private readonly resolvedGroups = new Map<string, InternalResolvedGroup>();
|
|
363
400
|
private readonly routeEntries: InternalResolvedRouteEntry[];
|
|
364
401
|
private readonly globalNamed: SimpleCLINamedDefinition;
|
|
@@ -485,7 +522,9 @@ export class SimpleCLIApp {
|
|
|
485
522
|
return [];
|
|
486
523
|
}
|
|
487
524
|
|
|
488
|
-
const helpFlagIndex = argsBeforePassthrough.findIndex((arg) =>
|
|
525
|
+
const helpFlagIndex = argsBeforePassthrough.findIndex((arg) =>
|
|
526
|
+
isHelpFlag(arg),
|
|
527
|
+
);
|
|
489
528
|
if (helpFlagIndex >= 0) {
|
|
490
529
|
return argsBeforePassthrough.slice(0, helpFlagIndex);
|
|
491
530
|
}
|
|
@@ -503,7 +542,10 @@ export class SimpleCLIApp {
|
|
|
503
542
|
throw new Error(`Unknown command: ${args.join(" ")}`);
|
|
504
543
|
}
|
|
505
544
|
|
|
506
|
-
const rawInput = this.parseCommandInput(
|
|
545
|
+
const rawInput = this.parseCommandInput(
|
|
546
|
+
command,
|
|
547
|
+
args.slice(command.path.length),
|
|
548
|
+
);
|
|
507
549
|
return {
|
|
508
550
|
routeKey: command.routeKey,
|
|
509
551
|
rawInput,
|
|
@@ -517,7 +559,9 @@ export class SimpleCLIApp {
|
|
|
517
559
|
const inputDefinition = command.input?.getDefinition();
|
|
518
560
|
if (!inputDefinition) {
|
|
519
561
|
if (args.length > 0) {
|
|
520
|
-
throw new Error(
|
|
562
|
+
throw new Error(
|
|
563
|
+
`Unexpected arguments for ${this.name} ${command.path.join(" ")}.`,
|
|
564
|
+
);
|
|
521
565
|
}
|
|
522
566
|
return {
|
|
523
567
|
positionals: [],
|
|
@@ -537,12 +581,19 @@ export class SimpleCLIApp {
|
|
|
537
581
|
|
|
538
582
|
if (arg === "--") {
|
|
539
583
|
if (!passthroughEntry) {
|
|
540
|
-
throw new Error(
|
|
584
|
+
throw new Error(
|
|
585
|
+
`Unexpected "--" for ${this.name} ${command.path.join(" ")}.`,
|
|
586
|
+
);
|
|
541
587
|
}
|
|
542
588
|
named["--"] = args.slice(index + 1);
|
|
543
589
|
break;
|
|
544
590
|
}
|
|
545
591
|
|
|
592
|
+
if (arg === "-") {
|
|
593
|
+
positionals.push(arg);
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
|
|
546
597
|
if (arg.startsWith("--")) {
|
|
547
598
|
const [rawName, inlineValue] = splitNamedArg(arg.slice(2));
|
|
548
599
|
const namedEntry = namedSpecs.get(rawName);
|
|
@@ -592,7 +643,11 @@ export class SimpleCLIApp {
|
|
|
592
643
|
positionals.push(arg);
|
|
593
644
|
}
|
|
594
645
|
|
|
595
|
-
validateParsedPositionals(
|
|
646
|
+
validateParsedPositionals(
|
|
647
|
+
command,
|
|
648
|
+
inputDefinition.positionals,
|
|
649
|
+
positionals,
|
|
650
|
+
);
|
|
596
651
|
validateRequiredNamedArgs(inputDefinition.named, named);
|
|
597
652
|
|
|
598
653
|
return {
|
|
@@ -685,7 +740,9 @@ export class SimpleCLIApp {
|
|
|
685
740
|
return rawInput;
|
|
686
741
|
}
|
|
687
742
|
|
|
688
|
-
const inputDefinition = this.resolvedCommands
|
|
743
|
+
const inputDefinition = this.resolvedCommands
|
|
744
|
+
.get(routeKey)
|
|
745
|
+
?.input?.getDefinition();
|
|
689
746
|
if (!inputDefinition) {
|
|
690
747
|
return rawInput;
|
|
691
748
|
}
|
|
@@ -712,7 +769,7 @@ export class SimpleCLIApp {
|
|
|
712
769
|
|
|
713
770
|
private renderRootHelp(): string {
|
|
714
771
|
const lines = [`Usage: ${this.name} <command>`, "", "Commands:"];
|
|
715
|
-
for (const entry of this.
|
|
772
|
+
for (const entry of this.getRootHelpEntries()) {
|
|
716
773
|
lines.push(formatListEntry(entry.label, entry.description));
|
|
717
774
|
}
|
|
718
775
|
return lines.join("\n");
|
|
@@ -756,8 +813,9 @@ export class SimpleCLIApp {
|
|
|
756
813
|
lines.push(...argumentLines);
|
|
757
814
|
}
|
|
758
815
|
|
|
759
|
-
const optionLines = Object.entries(inputDefinition.named).map(
|
|
760
|
-
|
|
816
|
+
const optionLines = Object.entries(inputDefinition.named).map(
|
|
817
|
+
([key, spec]) =>
|
|
818
|
+
formatListEntry(buildNamedArgHelpLabel(key, spec), spec.help),
|
|
761
819
|
);
|
|
762
820
|
|
|
763
821
|
if (optionLines.length > 0) {
|
|
@@ -819,7 +877,52 @@ export class SimpleCLIApp {
|
|
|
819
877
|
return entries;
|
|
820
878
|
}
|
|
821
879
|
|
|
822
|
-
private
|
|
880
|
+
private getRootHelpEntries(): Array<{
|
|
881
|
+
label: string;
|
|
882
|
+
description?: string;
|
|
883
|
+
}> {
|
|
884
|
+
return this.getImmediateRouteEntries([]).filter((entry) => {
|
|
885
|
+
const token = entry.label.replace(/\s+<subcommand>$/, "");
|
|
886
|
+
const group = this.findGroupByPath([token]);
|
|
887
|
+
if (!group) {
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
890
|
+
if (token === EXPERIMENTAL_COMMAND_PREFIX) {
|
|
891
|
+
return this.groupHasExperimentalCommand(group.path);
|
|
892
|
+
}
|
|
893
|
+
return this.groupHasVisibleNonExperimentalCommand(group.path);
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
private groupHasVisibleNonExperimentalCommand(path: readonly string[]): boolean {
|
|
898
|
+
for (const routeEntry of this.routeEntries) {
|
|
899
|
+
if (routeEntry.kind !== "command") continue;
|
|
900
|
+
if (!pathStartsWith(routeEntry.path, path)) continue;
|
|
901
|
+
const command = this.findCommandByPath(routeEntry.path);
|
|
902
|
+
if (command && !command.experimental) {
|
|
903
|
+
return true;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
return false;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
private groupHasExperimentalCommand(path: readonly string[]): boolean {
|
|
911
|
+
for (const routeEntry of this.routeEntries) {
|
|
912
|
+
if (routeEntry.kind !== "command") continue;
|
|
913
|
+
if (!pathStartsWith(routeEntry.path, path)) continue;
|
|
914
|
+
const command = this.findCommandByPath(routeEntry.path);
|
|
915
|
+
if (command?.experimental) {
|
|
916
|
+
return true;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
private findBestMatchingCommand(
|
|
924
|
+
args: readonly string[],
|
|
925
|
+
): InternalResolvedCommand | null {
|
|
823
926
|
let bestMatch: InternalResolvedCommand | null = null;
|
|
824
927
|
|
|
825
928
|
for (const command of this.resolvedCommands.values()) {
|
|
@@ -833,12 +936,16 @@ export class SimpleCLIApp {
|
|
|
833
936
|
return bestMatch;
|
|
834
937
|
}
|
|
835
938
|
|
|
836
|
-
private findCommandByPath(
|
|
939
|
+
private findCommandByPath(
|
|
940
|
+
path: readonly string[],
|
|
941
|
+
): InternalResolvedCommand | null {
|
|
837
942
|
const routeKey = pathToRouteKey(path);
|
|
838
943
|
return this.resolvedCommands.get(routeKey) ?? null;
|
|
839
944
|
}
|
|
840
945
|
|
|
841
|
-
private findGroupByPath(
|
|
946
|
+
private findGroupByPath(
|
|
947
|
+
path: readonly string[],
|
|
948
|
+
): InternalResolvedGroup | null {
|
|
842
949
|
const routeKey = pathToRouteKey(path);
|
|
843
950
|
return this.resolvedGroups.get(routeKey) ?? null;
|
|
844
951
|
}
|
|
@@ -847,10 +954,7 @@ export class SimpleCLIApp {
|
|
|
847
954
|
function splitNamedArg(arg: string): [string, string | undefined] {
|
|
848
955
|
const separatorIndex = arg.indexOf("=");
|
|
849
956
|
if (separatorIndex < 0) return [arg, undefined];
|
|
850
|
-
return [
|
|
851
|
-
arg.slice(0, separatorIndex),
|
|
852
|
-
arg.slice(separatorIndex + 1),
|
|
853
|
-
];
|
|
957
|
+
return [arg.slice(0, separatorIndex), arg.slice(separatorIndex + 1)];
|
|
854
958
|
}
|
|
855
959
|
|
|
856
960
|
function readNamedArgValue(
|
|
@@ -860,7 +964,10 @@ function readNamedArgValue(
|
|
|
860
964
|
displayName: string,
|
|
861
965
|
spec: SimpleCLINamedArgDefinition<ZodTypeAny>,
|
|
862
966
|
inlineValue: string | undefined,
|
|
863
|
-
namedSpecs: ReadonlyMap<
|
|
967
|
+
namedSpecs: ReadonlyMap<
|
|
968
|
+
string,
|
|
969
|
+
{ key: string; spec: SimpleCLINamedArgDefinition<ZodTypeAny> }
|
|
970
|
+
>,
|
|
864
971
|
): unknown {
|
|
865
972
|
if (spec.kind === "flag") {
|
|
866
973
|
return inlineValue === undefined
|
|
@@ -874,9 +981,9 @@ function readNamedArgValue(
|
|
|
874
981
|
|
|
875
982
|
const nextValue = args[index + 1];
|
|
876
983
|
if (
|
|
877
|
-
nextValue === undefined
|
|
878
|
-
|
|
879
|
-
|
|
984
|
+
nextValue === undefined ||
|
|
985
|
+
nextValue === "--" ||
|
|
986
|
+
isRecognizedNamedArgToken(nextValue, namedSpecs)
|
|
880
987
|
) {
|
|
881
988
|
throw new Error(`Missing value for ${displayName}.`);
|
|
882
989
|
}
|
|
@@ -886,7 +993,10 @@ function readNamedArgValue(
|
|
|
886
993
|
|
|
887
994
|
function isRecognizedNamedArgToken(
|
|
888
995
|
token: string,
|
|
889
|
-
namedSpecs: ReadonlyMap<
|
|
996
|
+
namedSpecs: ReadonlyMap<
|
|
997
|
+
string,
|
|
998
|
+
{ key: string; spec: SimpleCLINamedArgDefinition<ZodTypeAny> }
|
|
999
|
+
>,
|
|
890
1000
|
): boolean {
|
|
891
1001
|
if (token === "-" || !token.startsWith("-")) {
|
|
892
1002
|
return false;
|
|
@@ -899,11 +1009,13 @@ function isRecognizedNamedArgToken(
|
|
|
899
1009
|
return namedSpecs.has(rawName);
|
|
900
1010
|
}
|
|
901
1011
|
|
|
902
|
-
function buildNamedArgLookup(
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1012
|
+
function buildNamedArgLookup(
|
|
1013
|
+
namedDefinition: SimpleCLINamedDefinition,
|
|
1014
|
+
): Map<string, { key: string; spec: SimpleCLINamedArgDefinition<ZodTypeAny> }> {
|
|
1015
|
+
const lookup = new Map<
|
|
1016
|
+
string,
|
|
1017
|
+
{ key: string; spec: SimpleCLINamedArgDefinition<ZodTypeAny> }
|
|
1018
|
+
>();
|
|
907
1019
|
|
|
908
1020
|
for (const [key, spec] of Object.entries(namedDefinition)) {
|
|
909
1021
|
if (spec.source === "--") continue;
|
|
@@ -926,7 +1038,9 @@ function validateParsedPositionals(
|
|
|
926
1038
|
definitions: SimpleCLIPositionalsDefinition,
|
|
927
1039
|
positionals: readonly string[],
|
|
928
1040
|
): void {
|
|
929
|
-
const variadicDefinition = definitions.find(
|
|
1041
|
+
const variadicDefinition = definitions.find(
|
|
1042
|
+
(definition) => definition.variadic,
|
|
1043
|
+
);
|
|
930
1044
|
if (!variadicDefinition && positionals.length > definitions.length) {
|
|
931
1045
|
throw new Error(`Unexpected arguments for ${command.path.join(" ")}.`);
|
|
932
1046
|
}
|
|
@@ -935,17 +1049,22 @@ function validateParsedPositionals(
|
|
|
935
1049
|
const value = definition.variadic
|
|
936
1050
|
? positionals.slice(index)
|
|
937
1051
|
: positionals[index];
|
|
938
|
-
if (value !== undefined && (!Array.isArray(value) || value.length > 0))
|
|
1052
|
+
if (value !== undefined && (!Array.isArray(value) || value.length > 0))
|
|
1053
|
+
return;
|
|
939
1054
|
if (schemaAcceptsUndefined(definition.schema)) return;
|
|
940
1055
|
throw new Error(`Missing required argument <${definition.key}>.`);
|
|
941
1056
|
});
|
|
942
1057
|
}
|
|
943
1058
|
|
|
944
1059
|
function validateInputDefinition(definition: SimpleCLIInputDefinition): void {
|
|
945
|
-
const variadicIndex = definition.positionals.findIndex(
|
|
1060
|
+
const variadicIndex = definition.positionals.findIndex(
|
|
1061
|
+
(positional) => positional.variadic,
|
|
1062
|
+
);
|
|
946
1063
|
if (variadicIndex < 0) return;
|
|
947
1064
|
if (variadicIndex !== definition.positionals.length - 1) {
|
|
948
|
-
throw new Error(
|
|
1065
|
+
throw new Error(
|
|
1066
|
+
"Variadic positional arguments must be the last positional.",
|
|
1067
|
+
);
|
|
949
1068
|
}
|
|
950
1069
|
}
|
|
951
1070
|
|
|
@@ -955,7 +1074,8 @@ function validateRequiredNamedArgs(
|
|
|
955
1074
|
): void {
|
|
956
1075
|
for (const [key, spec] of Object.entries(definitions)) {
|
|
957
1076
|
if (schemaAcceptsUndefined(spec.schema)) continue;
|
|
958
|
-
const flagName =
|
|
1077
|
+
const flagName =
|
|
1078
|
+
spec.source === "--" ? "--" : buildNamedArgFlagName(key, spec);
|
|
959
1079
|
if (Object.prototype.hasOwnProperty.call(named, flagName)) continue;
|
|
960
1080
|
if (spec.source === "--") {
|
|
961
1081
|
throw new Error(`Missing required passthrough arguments after --.`);
|
|
@@ -964,66 +1084,128 @@ function validateRequiredNamedArgs(
|
|
|
964
1084
|
}
|
|
965
1085
|
}
|
|
966
1086
|
|
|
967
|
-
function resolveRouteTree(
|
|
1087
|
+
function resolveRouteTree(routes: SimpleCLIRouteTree<any>): ResolveRouteTreeResult {
|
|
1088
|
+
const collected = collectRouteTree(routes);
|
|
1089
|
+
const { groups, routeEntries } = buildResolvedRouteEntries(
|
|
1090
|
+
collected.commands,
|
|
1091
|
+
collected.groupDescriptions,
|
|
1092
|
+
);
|
|
1093
|
+
|
|
1094
|
+
return {
|
|
1095
|
+
commands: collected.commands,
|
|
1096
|
+
groups,
|
|
1097
|
+
routeEntries,
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function collectRouteTree(
|
|
968
1102
|
routes: SimpleCLIRouteTree<any>,
|
|
969
1103
|
parentPath: readonly string[] = [],
|
|
970
1104
|
parentMiddlewares: readonly AnySimpleCLIMiddleware[] = [],
|
|
971
|
-
):
|
|
972
|
-
const resolved:
|
|
1105
|
+
): CollectedRouteTreeResult {
|
|
1106
|
+
const resolved: CollectedRouteTreeResult = {
|
|
973
1107
|
commands: [],
|
|
974
|
-
|
|
975
|
-
routeEntries: [],
|
|
1108
|
+
groupDescriptions: new Map<string, string | undefined>(),
|
|
976
1109
|
};
|
|
977
1110
|
|
|
978
1111
|
for (const [token, routeValue] of Object.entries(routes)) {
|
|
979
1112
|
if (isGroup(routeValue)) {
|
|
980
1113
|
const groupPath = [...parentPath, token];
|
|
981
|
-
resolved.
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
description: routeValue.description,
|
|
985
|
-
});
|
|
986
|
-
resolved.routeEntries.push({
|
|
987
|
-
kind: "group",
|
|
988
|
-
path: groupPath,
|
|
989
|
-
});
|
|
990
|
-
|
|
991
|
-
const nested = resolveRouteTree(
|
|
992
|
-
routeValue.routes,
|
|
993
|
-
groupPath,
|
|
994
|
-
[...parentMiddlewares, ...routeValue.middlewares],
|
|
1114
|
+
resolved.groupDescriptions.set(
|
|
1115
|
+
pathToRouteKey(groupPath),
|
|
1116
|
+
routeValue.description,
|
|
995
1117
|
);
|
|
1118
|
+
|
|
1119
|
+
const nested = collectRouteTree(routeValue.routes, groupPath, [
|
|
1120
|
+
...parentMiddlewares,
|
|
1121
|
+
...routeValue.middlewares,
|
|
1122
|
+
]);
|
|
996
1123
|
resolved.commands.push(...nested.commands);
|
|
997
|
-
|
|
998
|
-
|
|
1124
|
+
for (const [routeKey, description] of nested.groupDescriptions) {
|
|
1125
|
+
resolved.groupDescriptions.set(routeKey, description);
|
|
1126
|
+
}
|
|
999
1127
|
continue;
|
|
1000
1128
|
}
|
|
1001
1129
|
|
|
1002
1130
|
const command = routeValue.getDefinition();
|
|
1003
1131
|
if (!command.handler) {
|
|
1004
|
-
throw new Error(
|
|
1132
|
+
throw new Error(
|
|
1133
|
+
`Command "${[...parentPath, token].join(" ")}" is missing a handler.`,
|
|
1134
|
+
);
|
|
1005
1135
|
}
|
|
1006
1136
|
|
|
1007
|
-
const
|
|
1137
|
+
const rawPath = [...parentPath, token];
|
|
1138
|
+
const path = command.config.experimental
|
|
1139
|
+
? [EXPERIMENTAL_COMMAND_PREFIX, ...rawPath]
|
|
1140
|
+
: rawPath;
|
|
1008
1141
|
resolved.commands.push({
|
|
1009
1142
|
routeKey: pathToRouteKey(path),
|
|
1010
1143
|
path,
|
|
1011
1144
|
description: command.config.description,
|
|
1145
|
+
experimental: command.config.experimental,
|
|
1012
1146
|
input: command.input,
|
|
1013
|
-
middlewares: mergeInheritedMiddlewares(
|
|
1147
|
+
middlewares: mergeInheritedMiddlewares(
|
|
1148
|
+
parentMiddlewares,
|
|
1149
|
+
command.middlewares,
|
|
1150
|
+
),
|
|
1014
1151
|
handler: command.handler as unknown as SimpleCLIHandler<
|
|
1015
1152
|
unknown,
|
|
1016
1153
|
SimpleCLIContext,
|
|
1017
1154
|
unknown
|
|
1018
1155
|
>,
|
|
1019
1156
|
});
|
|
1020
|
-
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
return resolved;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function buildResolvedRouteEntries(
|
|
1163
|
+
commands: readonly InternalResolvedCommand[],
|
|
1164
|
+
groupDescriptions: ReadonlyMap<string, string | undefined>,
|
|
1165
|
+
): Pick<ResolveRouteTreeResult, "groups" | "routeEntries"> {
|
|
1166
|
+
const groups = new Map<string, InternalResolvedGroup>();
|
|
1167
|
+
const routeEntries: InternalResolvedRouteEntry[] = [];
|
|
1168
|
+
|
|
1169
|
+
for (const command of commands) {
|
|
1170
|
+
for (let depth = 1; depth < command.path.length; depth += 1) {
|
|
1171
|
+
const path = command.path.slice(0, depth);
|
|
1172
|
+
const routeKey = pathToRouteKey(path);
|
|
1173
|
+
if (groups.has(routeKey)) continue;
|
|
1174
|
+
|
|
1175
|
+
groups.set(routeKey, {
|
|
1176
|
+
routeKey,
|
|
1177
|
+
path,
|
|
1178
|
+
description: resolveGroupDescription(path, groupDescriptions),
|
|
1179
|
+
});
|
|
1180
|
+
routeEntries.push({
|
|
1181
|
+
kind: "group",
|
|
1182
|
+
path,
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
routeEntries.push({
|
|
1021
1187
|
kind: "command",
|
|
1022
|
-
path,
|
|
1188
|
+
path: command.path,
|
|
1023
1189
|
});
|
|
1024
1190
|
}
|
|
1025
1191
|
|
|
1026
|
-
return
|
|
1192
|
+
return {
|
|
1193
|
+
groups: [...groups.values()],
|
|
1194
|
+
routeEntries,
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function resolveGroupDescription(
|
|
1199
|
+
path: readonly string[],
|
|
1200
|
+
groupDescriptions: ReadonlyMap<string, string | undefined>,
|
|
1201
|
+
): string | undefined {
|
|
1202
|
+
if (path.length === 1 && path[0] === EXPERIMENTAL_COMMAND_PREFIX) {
|
|
1203
|
+
return EXPERIMENTAL_GROUP_DESCRIPTION;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const originalPath =
|
|
1207
|
+
path[0] === EXPERIMENTAL_COMMAND_PREFIX ? path.slice(1) : path;
|
|
1208
|
+
return groupDescriptions.get(pathToRouteKey(originalPath));
|
|
1027
1209
|
}
|
|
1028
1210
|
|
|
1029
1211
|
function mergeInheritedMiddlewares(
|
|
@@ -1035,8 +1217,10 @@ function mergeInheritedMiddlewares(
|
|
|
1035
1217
|
}
|
|
1036
1218
|
|
|
1037
1219
|
if (
|
|
1038
|
-
commandMiddlewares.length >= parentMiddlewares.length
|
|
1039
|
-
|
|
1220
|
+
commandMiddlewares.length >= parentMiddlewares.length &&
|
|
1221
|
+
parentMiddlewares.every(
|
|
1222
|
+
(middleware, index) => commandMiddlewares[index] === middleware,
|
|
1223
|
+
)
|
|
1040
1224
|
) {
|
|
1041
1225
|
return [...commandMiddlewares];
|
|
1042
1226
|
}
|
|
@@ -1053,12 +1237,10 @@ function isGroup(
|
|
|
1053
1237
|
function buildInputNormalizer<
|
|
1054
1238
|
TPositionals extends SimpleCLIPositionalsDefinition,
|
|
1055
1239
|
TNamed extends SimpleCLINamedDefinition,
|
|
1056
|
-
>(
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
},
|
|
1061
|
-
): (raw: SimpleCLIInputRaw) => InputObjectFor<TPositionals, TNamed> {
|
|
1240
|
+
>(definition: {
|
|
1241
|
+
positionals: TPositionals;
|
|
1242
|
+
named: TNamed;
|
|
1243
|
+
}): (raw: SimpleCLIInputRaw) => InputObjectFor<TPositionals, TNamed> {
|
|
1062
1244
|
return (raw) => {
|
|
1063
1245
|
const output: RecordUnknown = {};
|
|
1064
1246
|
const positionals = raw.positionals ?? [];
|
|
@@ -1077,10 +1259,7 @@ function buildInputNormalizer<
|
|
|
1077
1259
|
spec.name ? toCamelCase(spec.name) : "",
|
|
1078
1260
|
...(spec.aliases ?? []).flatMap((alias) => {
|
|
1079
1261
|
const normalizedAlias = normalizeNamedArgToken(alias);
|
|
1080
|
-
return [
|
|
1081
|
-
normalizedAlias,
|
|
1082
|
-
toCamelCase(normalizedAlias),
|
|
1083
|
-
];
|
|
1262
|
+
return [normalizedAlias, toCamelCase(normalizedAlias)];
|
|
1084
1263
|
}),
|
|
1085
1264
|
toKebabCase(key),
|
|
1086
1265
|
key,
|
|
@@ -1103,12 +1282,10 @@ function buildInputNormalizer<
|
|
|
1103
1282
|
function buildInputSchema<
|
|
1104
1283
|
TPositionals extends SimpleCLIPositionalsDefinition,
|
|
1105
1284
|
TNamed extends SimpleCLINamedDefinition,
|
|
1106
|
-
>(
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
},
|
|
1111
|
-
): z.ZodType<InputObjectFor<TPositionals, TNamed>, unknown> {
|
|
1285
|
+
>(definition: {
|
|
1286
|
+
positionals: TPositionals;
|
|
1287
|
+
named: TNamed;
|
|
1288
|
+
}): z.ZodType<InputObjectFor<TPositionals, TNamed>, unknown> {
|
|
1112
1289
|
const shape: Record<string, ZodTypeAny> = {};
|
|
1113
1290
|
|
|
1114
1291
|
for (const positional of definition.positionals) {
|
|
@@ -1140,7 +1317,12 @@ function positional<TKey extends string, TSchema extends ZodTypeAny>(
|
|
|
1140
1317
|
|
|
1141
1318
|
function option<TSchema extends ZodTypeAny>(
|
|
1142
1319
|
schema: TSchema,
|
|
1143
|
-
options?: {
|
|
1320
|
+
options?: {
|
|
1321
|
+
help?: string;
|
|
1322
|
+
name?: string;
|
|
1323
|
+
aliases?: readonly string[];
|
|
1324
|
+
source?: "--";
|
|
1325
|
+
},
|
|
1144
1326
|
): SimpleCLINamedArgDefinition<TSchema> {
|
|
1145
1327
|
return {
|
|
1146
1328
|
kind: "option",
|
|
@@ -1152,9 +1334,11 @@ function option<TSchema extends ZodTypeAny>(
|
|
|
1152
1334
|
};
|
|
1153
1335
|
}
|
|
1154
1336
|
|
|
1155
|
-
function flag(
|
|
1156
|
-
|
|
1157
|
-
|
|
1337
|
+
function flag(options?: {
|
|
1338
|
+
help?: string;
|
|
1339
|
+
name?: string;
|
|
1340
|
+
aliases?: readonly string[];
|
|
1341
|
+
}): SimpleCLINamedArgDefinition<z.ZodDefault<z.ZodBoolean>> {
|
|
1158
1342
|
return {
|
|
1159
1343
|
kind: "flag",
|
|
1160
1344
|
schema: z.boolean().default(false),
|
|
@@ -1184,9 +1368,11 @@ type SimpleCLIScope<
|
|
|
1184
1368
|
TContext extends SimpleCLIContext,
|
|
1185
1369
|
> = {
|
|
1186
1370
|
use<TContextOut extends SimpleCLIContext>(
|
|
1187
|
-
middleware: SimpleCLIMiddleware<unknown, TContext, TContextOut
|
|
1371
|
+
middleware: SimpleCLIMiddleware<unknown, TContext, TContextOut>,
|
|
1188
1372
|
): SimpleCLIScope<TParentContext, TContextOut>;
|
|
1189
|
-
group(
|
|
1373
|
+
group(
|
|
1374
|
+
config: SimpleCLIGroupConfig<TContext>,
|
|
1375
|
+
): SimpleCLIGroup<TParentContext, TContext>;
|
|
1190
1376
|
command(
|
|
1191
1377
|
config: SimpleCLICommandConfig,
|
|
1192
1378
|
): SimpleCLICommandBuilder<unknown, TParentContext, TContext, unknown>;
|
|
@@ -1201,9 +1387,7 @@ function command(
|
|
|
1201
1387
|
});
|
|
1202
1388
|
}
|
|
1203
1389
|
|
|
1204
|
-
function group(
|
|
1205
|
-
config: SimpleCLIGroupConfig<{}>,
|
|
1206
|
-
): SimpleCLIGroup<{}, {}> {
|
|
1390
|
+
function group(config: SimpleCLIGroupConfig<{}>): SimpleCLIGroup<{}, {}> {
|
|
1207
1391
|
return createScope<{}, {}>([]).group(config);
|
|
1208
1392
|
}
|
|
1209
1393
|
|
|
@@ -1222,7 +1406,9 @@ function createScope<
|
|
|
1222
1406
|
middleware,
|
|
1223
1407
|
]);
|
|
1224
1408
|
},
|
|
1225
|
-
group(
|
|
1409
|
+
group(
|
|
1410
|
+
config: SimpleCLIGroupConfig<TContext>,
|
|
1411
|
+
): SimpleCLIGroup<TParentContext, TContext> {
|
|
1226
1412
|
return {
|
|
1227
1413
|
kind: "group",
|
|
1228
1414
|
description: config.description,
|
|
@@ -1255,11 +1441,8 @@ function define(
|
|
|
1255
1441
|
return new SimpleCLIApp(name, routes, config);
|
|
1256
1442
|
}
|
|
1257
1443
|
|
|
1258
|
-
export type InferInput<TInput extends SimpleCLIInput<unknown>> =
|
|
1259
|
-
infer TOutput
|
|
1260
|
-
>
|
|
1261
|
-
? TOutput
|
|
1262
|
-
: never;
|
|
1444
|
+
export type InferInput<TInput extends SimpleCLIInput<unknown>> =
|
|
1445
|
+
TInput extends SimpleCLIInput<infer TOutput> ? TOutput : never;
|
|
1263
1446
|
|
|
1264
1447
|
export const SimpleCLI = {
|
|
1265
1448
|
define,
|