libretto 0.5.0 → 0.5.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/README.md +109 -35
- package/dist/cli/cli.js +22 -97
- package/dist/cli/commands/browser.js +86 -59
- package/dist/cli/commands/execution.js +199 -86
- 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/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 +45 -25
- package/dist/cli/router.js +14 -21
- package/dist/cli/workers/run-integration-runtime.js +24 -5
- package/dist/cli/workers/run-integration-worker-protocol.js +3 -1
- package/dist/cli/workers/run-integration-worker.js +1 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.js +7 -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 +4 -5
- package/dist/shared/workflow/workflow.js +3 -5
- package/package.json +6 -2
- package/scripts/check-skills-sync.mjs +25 -0
- package/scripts/compare-eval-summary.mjs +47 -0
- package/scripts/postinstall.mjs +15 -15
- package/scripts/prepare-release.sh +97 -0
- package/scripts/skills-libretto.mjs +103 -0
- package/scripts/summarize-evals.mjs +135 -0
- package/scripts/sync-skills.mjs +12 -0
- package/skills/libretto/SKILL.md +132 -54
- 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 +210 -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/execution.ts +233 -102
- 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/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 +144 -77
- package/src/cli/router.ts +13 -21
- package/src/cli/workers/run-integration-runtime.ts +36 -9
- package/src/cli/workers/run-integration-worker-protocol.ts +2 -0
- package/src/cli/workers/run-integration-worker.ts +1 -4
- package/src/index.ts +73 -66
- 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 +19 -25
- package/skills/libretto/references/reverse-engineering-network-requests.md +0 -39
- package/skills/libretto/references/user-action-log.md +0 -31
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
appendFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
writeFileSync,
|
|
6
|
+
} from "node:fs";
|
|
2
7
|
import type { Page } from "playwright";
|
|
3
8
|
import {
|
|
4
9
|
getSessionActionsLogPath,
|
|
@@ -21,7 +26,12 @@ export type NetworkLogEntry = {
|
|
|
21
26
|
|
|
22
27
|
export function readNetworkLog(
|
|
23
28
|
session: string,
|
|
24
|
-
opts: {
|
|
29
|
+
opts: {
|
|
30
|
+
last?: number;
|
|
31
|
+
filter?: string;
|
|
32
|
+
method?: string;
|
|
33
|
+
pageId?: string;
|
|
34
|
+
} = {},
|
|
25
35
|
): NetworkLogEntry[] {
|
|
26
36
|
assertSessionStateExistsOrThrow(session);
|
|
27
37
|
const logPath = getSessionNetworkLogPath(session);
|
|
@@ -86,6 +96,15 @@ export type ActionLogEntry = {
|
|
|
86
96
|
action: string;
|
|
87
97
|
source: "user" | "agent";
|
|
88
98
|
selector?: string;
|
|
99
|
+
bestSemanticSelector?: string;
|
|
100
|
+
targetSelector?: string;
|
|
101
|
+
ancestorSelectors?: string[];
|
|
102
|
+
nearbyText?: string;
|
|
103
|
+
composedPath?: string[];
|
|
104
|
+
coordinates?: {
|
|
105
|
+
x: number;
|
|
106
|
+
y: number;
|
|
107
|
+
};
|
|
89
108
|
value?: string;
|
|
90
109
|
url?: string;
|
|
91
110
|
duration?: number;
|
|
@@ -99,7 +118,10 @@ export function parentLogAction(
|
|
|
99
118
|
): void {
|
|
100
119
|
try {
|
|
101
120
|
const record = { ts: new Date().toISOString(), ...entry };
|
|
102
|
-
appendFileSync(
|
|
121
|
+
appendFileSync(
|
|
122
|
+
getSessionActionsLogPath(session),
|
|
123
|
+
JSON.stringify(record) + "\n",
|
|
124
|
+
);
|
|
103
125
|
} catch {}
|
|
104
126
|
}
|
|
105
127
|
|
|
@@ -139,6 +161,11 @@ export function readActionLog(
|
|
|
139
161
|
(e) =>
|
|
140
162
|
re.test(e.action) ||
|
|
141
163
|
re.test(e.selector || "") ||
|
|
164
|
+
re.test(e.bestSemanticSelector || "") ||
|
|
165
|
+
re.test(e.targetSelector || "") ||
|
|
166
|
+
re.test((e.ancestorSelectors || []).join(" ")) ||
|
|
167
|
+
re.test(e.nearbyText || "") ||
|
|
168
|
+
re.test((e.composedPath || []).join(" ")) ||
|
|
142
169
|
re.test(e.value || "") ||
|
|
143
170
|
re.test(e.url || ""),
|
|
144
171
|
);
|
|
@@ -158,8 +185,16 @@ export function readActionLog(
|
|
|
158
185
|
export function formatActionEntry(e: ActionLogEntry): string {
|
|
159
186
|
const time = e.ts.replace(/.*T/, "").replace(/\.\d+Z$/, "");
|
|
160
187
|
const src = e.source.toUpperCase().padEnd(5);
|
|
188
|
+
const displaySelector = e.bestSemanticSelector || e.selector;
|
|
161
189
|
const parts = [`[${time}]`, `[${src}]`, e.action];
|
|
162
|
-
if (
|
|
190
|
+
if (displaySelector) parts.push(displaySelector);
|
|
191
|
+
if (e.targetSelector && e.targetSelector !== displaySelector) {
|
|
192
|
+
parts.push(`target=${e.targetSelector}`);
|
|
193
|
+
}
|
|
194
|
+
if (e.nearbyText) parts.push(`text="${e.nearbyText}"`);
|
|
195
|
+
if (e.coordinates) {
|
|
196
|
+
parts.push(`@(${e.coordinates.x},${e.coordinates.y})`);
|
|
197
|
+
}
|
|
163
198
|
if (e.value) parts.push(`"${e.value}"`);
|
|
164
199
|
if (e.url) parts.push(e.url);
|
|
165
200
|
if (e.duration != null) parts.push(`${e.duration}ms`);
|
|
@@ -49,7 +49,10 @@ type SimpleCLIPositionalsDefinition = readonly SimpleCLIPositionalDefinition<
|
|
|
49
49
|
ZodTypeAny
|
|
50
50
|
>[];
|
|
51
51
|
|
|
52
|
-
type SimpleCLINamedDefinition = Record<
|
|
52
|
+
type SimpleCLINamedDefinition = Record<
|
|
53
|
+
string,
|
|
54
|
+
SimpleCLINamedArgDefinition<ZodTypeAny>
|
|
55
|
+
>;
|
|
53
56
|
|
|
54
57
|
type SimpleCLIInputDefinition = {
|
|
55
58
|
positionals: SimpleCLIPositionalsDefinition;
|
|
@@ -97,7 +100,8 @@ type NormalizedCommandDefinition<
|
|
|
97
100
|
|
|
98
101
|
type SimpleCLIRouteTree<TContext extends SimpleCLIContext = {}> = Record<
|
|
99
102
|
string,
|
|
100
|
-
|
|
103
|
+
| SimpleCLIGroup<TContext, any>
|
|
104
|
+
| SimpleCLICommandBuilder<any, TContext, any, any>
|
|
101
105
|
>;
|
|
102
106
|
|
|
103
107
|
export type SimpleCLIResolvedCommand = {
|
|
@@ -295,13 +299,23 @@ export class SimpleCLICommandBuilder<
|
|
|
295
299
|
TResult,
|
|
296
300
|
> {
|
|
297
301
|
constructor(
|
|
298
|
-
private readonly definition: NormalizedCommandDefinition<
|
|
302
|
+
private readonly definition: NormalizedCommandDefinition<
|
|
303
|
+
TInput,
|
|
304
|
+
TContextIn,
|
|
305
|
+
TContext,
|
|
306
|
+
TResult
|
|
307
|
+
>,
|
|
299
308
|
) {}
|
|
300
309
|
|
|
301
310
|
input<TNextInput>(
|
|
302
311
|
input: SimpleCLIInput<TNextInput>,
|
|
303
312
|
): SimpleCLICommandBuilder<TNextInput, TContextIn, TContext, TResult> {
|
|
304
|
-
return new SimpleCLICommandBuilder<
|
|
313
|
+
return new SimpleCLICommandBuilder<
|
|
314
|
+
TNextInput,
|
|
315
|
+
TContextIn,
|
|
316
|
+
TContext,
|
|
317
|
+
TResult
|
|
318
|
+
>({
|
|
305
319
|
config: this.definition.config,
|
|
306
320
|
input,
|
|
307
321
|
middlewares: this.definition.middlewares,
|
|
@@ -332,7 +346,12 @@ export class SimpleCLICommandBuilder<
|
|
|
332
346
|
handle<TNextResult>(
|
|
333
347
|
handler: SimpleCLIHandler<TInput, TContext, TNextResult>,
|
|
334
348
|
): SimpleCLICommandBuilder<TInput, TContextIn, TContext, TNextResult> {
|
|
335
|
-
return new SimpleCLICommandBuilder<
|
|
349
|
+
return new SimpleCLICommandBuilder<
|
|
350
|
+
TInput,
|
|
351
|
+
TContextIn,
|
|
352
|
+
TContext,
|
|
353
|
+
TNextResult
|
|
354
|
+
>({
|
|
336
355
|
config: this.definition.config,
|
|
337
356
|
input: this.definition.input,
|
|
338
357
|
middlewares: this.definition.middlewares,
|
|
@@ -340,7 +359,12 @@ export class SimpleCLICommandBuilder<
|
|
|
340
359
|
});
|
|
341
360
|
}
|
|
342
361
|
|
|
343
|
-
getDefinition(): NormalizedCommandDefinition<
|
|
362
|
+
getDefinition(): NormalizedCommandDefinition<
|
|
363
|
+
TInput,
|
|
364
|
+
TContextIn,
|
|
365
|
+
TContext,
|
|
366
|
+
TResult
|
|
367
|
+
> {
|
|
344
368
|
return this.definition;
|
|
345
369
|
}
|
|
346
370
|
}
|
|
@@ -358,7 +382,10 @@ export type SimpleCLIGroup<
|
|
|
358
382
|
};
|
|
359
383
|
|
|
360
384
|
export class SimpleCLIApp {
|
|
361
|
-
private readonly resolvedCommands = new Map<
|
|
385
|
+
private readonly resolvedCommands = new Map<
|
|
386
|
+
string,
|
|
387
|
+
InternalResolvedCommand
|
|
388
|
+
>();
|
|
362
389
|
private readonly resolvedGroups = new Map<string, InternalResolvedGroup>();
|
|
363
390
|
private readonly routeEntries: InternalResolvedRouteEntry[];
|
|
364
391
|
private readonly globalNamed: SimpleCLINamedDefinition;
|
|
@@ -485,7 +512,9 @@ export class SimpleCLIApp {
|
|
|
485
512
|
return [];
|
|
486
513
|
}
|
|
487
514
|
|
|
488
|
-
const helpFlagIndex = argsBeforePassthrough.findIndex((arg) =>
|
|
515
|
+
const helpFlagIndex = argsBeforePassthrough.findIndex((arg) =>
|
|
516
|
+
isHelpFlag(arg),
|
|
517
|
+
);
|
|
489
518
|
if (helpFlagIndex >= 0) {
|
|
490
519
|
return argsBeforePassthrough.slice(0, helpFlagIndex);
|
|
491
520
|
}
|
|
@@ -503,7 +532,10 @@ export class SimpleCLIApp {
|
|
|
503
532
|
throw new Error(`Unknown command: ${args.join(" ")}`);
|
|
504
533
|
}
|
|
505
534
|
|
|
506
|
-
const rawInput = this.parseCommandInput(
|
|
535
|
+
const rawInput = this.parseCommandInput(
|
|
536
|
+
command,
|
|
537
|
+
args.slice(command.path.length),
|
|
538
|
+
);
|
|
507
539
|
return {
|
|
508
540
|
routeKey: command.routeKey,
|
|
509
541
|
rawInput,
|
|
@@ -517,7 +549,9 @@ export class SimpleCLIApp {
|
|
|
517
549
|
const inputDefinition = command.input?.getDefinition();
|
|
518
550
|
if (!inputDefinition) {
|
|
519
551
|
if (args.length > 0) {
|
|
520
|
-
throw new Error(
|
|
552
|
+
throw new Error(
|
|
553
|
+
`Unexpected arguments for ${this.name} ${command.path.join(" ")}.`,
|
|
554
|
+
);
|
|
521
555
|
}
|
|
522
556
|
return {
|
|
523
557
|
positionals: [],
|
|
@@ -537,7 +571,9 @@ export class SimpleCLIApp {
|
|
|
537
571
|
|
|
538
572
|
if (arg === "--") {
|
|
539
573
|
if (!passthroughEntry) {
|
|
540
|
-
throw new Error(
|
|
574
|
+
throw new Error(
|
|
575
|
+
`Unexpected "--" for ${this.name} ${command.path.join(" ")}.`,
|
|
576
|
+
);
|
|
541
577
|
}
|
|
542
578
|
named["--"] = args.slice(index + 1);
|
|
543
579
|
break;
|
|
@@ -592,7 +628,11 @@ export class SimpleCLIApp {
|
|
|
592
628
|
positionals.push(arg);
|
|
593
629
|
}
|
|
594
630
|
|
|
595
|
-
validateParsedPositionals(
|
|
631
|
+
validateParsedPositionals(
|
|
632
|
+
command,
|
|
633
|
+
inputDefinition.positionals,
|
|
634
|
+
positionals,
|
|
635
|
+
);
|
|
596
636
|
validateRequiredNamedArgs(inputDefinition.named, named);
|
|
597
637
|
|
|
598
638
|
return {
|
|
@@ -685,7 +725,9 @@ export class SimpleCLIApp {
|
|
|
685
725
|
return rawInput;
|
|
686
726
|
}
|
|
687
727
|
|
|
688
|
-
const inputDefinition = this.resolvedCommands
|
|
728
|
+
const inputDefinition = this.resolvedCommands
|
|
729
|
+
.get(routeKey)
|
|
730
|
+
?.input?.getDefinition();
|
|
689
731
|
if (!inputDefinition) {
|
|
690
732
|
return rawInput;
|
|
691
733
|
}
|
|
@@ -756,8 +798,9 @@ export class SimpleCLIApp {
|
|
|
756
798
|
lines.push(...argumentLines);
|
|
757
799
|
}
|
|
758
800
|
|
|
759
|
-
const optionLines = Object.entries(inputDefinition.named).map(
|
|
760
|
-
|
|
801
|
+
const optionLines = Object.entries(inputDefinition.named).map(
|
|
802
|
+
([key, spec]) =>
|
|
803
|
+
formatListEntry(buildNamedArgHelpLabel(key, spec), spec.help),
|
|
761
804
|
);
|
|
762
805
|
|
|
763
806
|
if (optionLines.length > 0) {
|
|
@@ -819,7 +862,9 @@ export class SimpleCLIApp {
|
|
|
819
862
|
return entries;
|
|
820
863
|
}
|
|
821
864
|
|
|
822
|
-
private findBestMatchingCommand(
|
|
865
|
+
private findBestMatchingCommand(
|
|
866
|
+
args: readonly string[],
|
|
867
|
+
): InternalResolvedCommand | null {
|
|
823
868
|
let bestMatch: InternalResolvedCommand | null = null;
|
|
824
869
|
|
|
825
870
|
for (const command of this.resolvedCommands.values()) {
|
|
@@ -833,12 +878,16 @@ export class SimpleCLIApp {
|
|
|
833
878
|
return bestMatch;
|
|
834
879
|
}
|
|
835
880
|
|
|
836
|
-
private findCommandByPath(
|
|
881
|
+
private findCommandByPath(
|
|
882
|
+
path: readonly string[],
|
|
883
|
+
): InternalResolvedCommand | null {
|
|
837
884
|
const routeKey = pathToRouteKey(path);
|
|
838
885
|
return this.resolvedCommands.get(routeKey) ?? null;
|
|
839
886
|
}
|
|
840
887
|
|
|
841
|
-
private findGroupByPath(
|
|
888
|
+
private findGroupByPath(
|
|
889
|
+
path: readonly string[],
|
|
890
|
+
): InternalResolvedGroup | null {
|
|
842
891
|
const routeKey = pathToRouteKey(path);
|
|
843
892
|
return this.resolvedGroups.get(routeKey) ?? null;
|
|
844
893
|
}
|
|
@@ -847,10 +896,7 @@ export class SimpleCLIApp {
|
|
|
847
896
|
function splitNamedArg(arg: string): [string, string | undefined] {
|
|
848
897
|
const separatorIndex = arg.indexOf("=");
|
|
849
898
|
if (separatorIndex < 0) return [arg, undefined];
|
|
850
|
-
return [
|
|
851
|
-
arg.slice(0, separatorIndex),
|
|
852
|
-
arg.slice(separatorIndex + 1),
|
|
853
|
-
];
|
|
899
|
+
return [arg.slice(0, separatorIndex), arg.slice(separatorIndex + 1)];
|
|
854
900
|
}
|
|
855
901
|
|
|
856
902
|
function readNamedArgValue(
|
|
@@ -860,7 +906,10 @@ function readNamedArgValue(
|
|
|
860
906
|
displayName: string,
|
|
861
907
|
spec: SimpleCLINamedArgDefinition<ZodTypeAny>,
|
|
862
908
|
inlineValue: string | undefined,
|
|
863
|
-
namedSpecs: ReadonlyMap<
|
|
909
|
+
namedSpecs: ReadonlyMap<
|
|
910
|
+
string,
|
|
911
|
+
{ key: string; spec: SimpleCLINamedArgDefinition<ZodTypeAny> }
|
|
912
|
+
>,
|
|
864
913
|
): unknown {
|
|
865
914
|
if (spec.kind === "flag") {
|
|
866
915
|
return inlineValue === undefined
|
|
@@ -874,9 +923,9 @@ function readNamedArgValue(
|
|
|
874
923
|
|
|
875
924
|
const nextValue = args[index + 1];
|
|
876
925
|
if (
|
|
877
|
-
nextValue === undefined
|
|
878
|
-
|
|
879
|
-
|
|
926
|
+
nextValue === undefined ||
|
|
927
|
+
nextValue === "--" ||
|
|
928
|
+
isRecognizedNamedArgToken(nextValue, namedSpecs)
|
|
880
929
|
) {
|
|
881
930
|
throw new Error(`Missing value for ${displayName}.`);
|
|
882
931
|
}
|
|
@@ -886,7 +935,10 @@ function readNamedArgValue(
|
|
|
886
935
|
|
|
887
936
|
function isRecognizedNamedArgToken(
|
|
888
937
|
token: string,
|
|
889
|
-
namedSpecs: ReadonlyMap<
|
|
938
|
+
namedSpecs: ReadonlyMap<
|
|
939
|
+
string,
|
|
940
|
+
{ key: string; spec: SimpleCLINamedArgDefinition<ZodTypeAny> }
|
|
941
|
+
>,
|
|
890
942
|
): boolean {
|
|
891
943
|
if (token === "-" || !token.startsWith("-")) {
|
|
892
944
|
return false;
|
|
@@ -899,11 +951,13 @@ function isRecognizedNamedArgToken(
|
|
|
899
951
|
return namedSpecs.has(rawName);
|
|
900
952
|
}
|
|
901
953
|
|
|
902
|
-
function buildNamedArgLookup(
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
954
|
+
function buildNamedArgLookup(
|
|
955
|
+
namedDefinition: SimpleCLINamedDefinition,
|
|
956
|
+
): Map<string, { key: string; spec: SimpleCLINamedArgDefinition<ZodTypeAny> }> {
|
|
957
|
+
const lookup = new Map<
|
|
958
|
+
string,
|
|
959
|
+
{ key: string; spec: SimpleCLINamedArgDefinition<ZodTypeAny> }
|
|
960
|
+
>();
|
|
907
961
|
|
|
908
962
|
for (const [key, spec] of Object.entries(namedDefinition)) {
|
|
909
963
|
if (spec.source === "--") continue;
|
|
@@ -926,7 +980,9 @@ function validateParsedPositionals(
|
|
|
926
980
|
definitions: SimpleCLIPositionalsDefinition,
|
|
927
981
|
positionals: readonly string[],
|
|
928
982
|
): void {
|
|
929
|
-
const variadicDefinition = definitions.find(
|
|
983
|
+
const variadicDefinition = definitions.find(
|
|
984
|
+
(definition) => definition.variadic,
|
|
985
|
+
);
|
|
930
986
|
if (!variadicDefinition && positionals.length > definitions.length) {
|
|
931
987
|
throw new Error(`Unexpected arguments for ${command.path.join(" ")}.`);
|
|
932
988
|
}
|
|
@@ -935,17 +991,22 @@ function validateParsedPositionals(
|
|
|
935
991
|
const value = definition.variadic
|
|
936
992
|
? positionals.slice(index)
|
|
937
993
|
: positionals[index];
|
|
938
|
-
if (value !== undefined && (!Array.isArray(value) || value.length > 0))
|
|
994
|
+
if (value !== undefined && (!Array.isArray(value) || value.length > 0))
|
|
995
|
+
return;
|
|
939
996
|
if (schemaAcceptsUndefined(definition.schema)) return;
|
|
940
997
|
throw new Error(`Missing required argument <${definition.key}>.`);
|
|
941
998
|
});
|
|
942
999
|
}
|
|
943
1000
|
|
|
944
1001
|
function validateInputDefinition(definition: SimpleCLIInputDefinition): void {
|
|
945
|
-
const variadicIndex = definition.positionals.findIndex(
|
|
1002
|
+
const variadicIndex = definition.positionals.findIndex(
|
|
1003
|
+
(positional) => positional.variadic,
|
|
1004
|
+
);
|
|
946
1005
|
if (variadicIndex < 0) return;
|
|
947
1006
|
if (variadicIndex !== definition.positionals.length - 1) {
|
|
948
|
-
throw new Error(
|
|
1007
|
+
throw new Error(
|
|
1008
|
+
"Variadic positional arguments must be the last positional.",
|
|
1009
|
+
);
|
|
949
1010
|
}
|
|
950
1011
|
}
|
|
951
1012
|
|
|
@@ -955,7 +1016,8 @@ function validateRequiredNamedArgs(
|
|
|
955
1016
|
): void {
|
|
956
1017
|
for (const [key, spec] of Object.entries(definitions)) {
|
|
957
1018
|
if (schemaAcceptsUndefined(spec.schema)) continue;
|
|
958
|
-
const flagName =
|
|
1019
|
+
const flagName =
|
|
1020
|
+
spec.source === "--" ? "--" : buildNamedArgFlagName(key, spec);
|
|
959
1021
|
if (Object.prototype.hasOwnProperty.call(named, flagName)) continue;
|
|
960
1022
|
if (spec.source === "--") {
|
|
961
1023
|
throw new Error(`Missing required passthrough arguments after --.`);
|
|
@@ -988,11 +1050,10 @@ function resolveRouteTree(
|
|
|
988
1050
|
path: groupPath,
|
|
989
1051
|
});
|
|
990
1052
|
|
|
991
|
-
const nested = resolveRouteTree(
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
);
|
|
1053
|
+
const nested = resolveRouteTree(routeValue.routes, groupPath, [
|
|
1054
|
+
...parentMiddlewares,
|
|
1055
|
+
...routeValue.middlewares,
|
|
1056
|
+
]);
|
|
996
1057
|
resolved.commands.push(...nested.commands);
|
|
997
1058
|
resolved.groups.push(...nested.groups);
|
|
998
1059
|
resolved.routeEntries.push(...nested.routeEntries);
|
|
@@ -1001,7 +1062,9 @@ function resolveRouteTree(
|
|
|
1001
1062
|
|
|
1002
1063
|
const command = routeValue.getDefinition();
|
|
1003
1064
|
if (!command.handler) {
|
|
1004
|
-
throw new Error(
|
|
1065
|
+
throw new Error(
|
|
1066
|
+
`Command "${[...parentPath, token].join(" ")}" is missing a handler.`,
|
|
1067
|
+
);
|
|
1005
1068
|
}
|
|
1006
1069
|
|
|
1007
1070
|
const path = [...parentPath, token];
|
|
@@ -1010,7 +1073,10 @@ function resolveRouteTree(
|
|
|
1010
1073
|
path,
|
|
1011
1074
|
description: command.config.description,
|
|
1012
1075
|
input: command.input,
|
|
1013
|
-
middlewares: mergeInheritedMiddlewares(
|
|
1076
|
+
middlewares: mergeInheritedMiddlewares(
|
|
1077
|
+
parentMiddlewares,
|
|
1078
|
+
command.middlewares,
|
|
1079
|
+
),
|
|
1014
1080
|
handler: command.handler as unknown as SimpleCLIHandler<
|
|
1015
1081
|
unknown,
|
|
1016
1082
|
SimpleCLIContext,
|
|
@@ -1035,8 +1101,10 @@ function mergeInheritedMiddlewares(
|
|
|
1035
1101
|
}
|
|
1036
1102
|
|
|
1037
1103
|
if (
|
|
1038
|
-
commandMiddlewares.length >= parentMiddlewares.length
|
|
1039
|
-
|
|
1104
|
+
commandMiddlewares.length >= parentMiddlewares.length &&
|
|
1105
|
+
parentMiddlewares.every(
|
|
1106
|
+
(middleware, index) => commandMiddlewares[index] === middleware,
|
|
1107
|
+
)
|
|
1040
1108
|
) {
|
|
1041
1109
|
return [...commandMiddlewares];
|
|
1042
1110
|
}
|
|
@@ -1053,12 +1121,10 @@ function isGroup(
|
|
|
1053
1121
|
function buildInputNormalizer<
|
|
1054
1122
|
TPositionals extends SimpleCLIPositionalsDefinition,
|
|
1055
1123
|
TNamed extends SimpleCLINamedDefinition,
|
|
1056
|
-
>(
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
},
|
|
1061
|
-
): (raw: SimpleCLIInputRaw) => InputObjectFor<TPositionals, TNamed> {
|
|
1124
|
+
>(definition: {
|
|
1125
|
+
positionals: TPositionals;
|
|
1126
|
+
named: TNamed;
|
|
1127
|
+
}): (raw: SimpleCLIInputRaw) => InputObjectFor<TPositionals, TNamed> {
|
|
1062
1128
|
return (raw) => {
|
|
1063
1129
|
const output: RecordUnknown = {};
|
|
1064
1130
|
const positionals = raw.positionals ?? [];
|
|
@@ -1077,10 +1143,7 @@ function buildInputNormalizer<
|
|
|
1077
1143
|
spec.name ? toCamelCase(spec.name) : "",
|
|
1078
1144
|
...(spec.aliases ?? []).flatMap((alias) => {
|
|
1079
1145
|
const normalizedAlias = normalizeNamedArgToken(alias);
|
|
1080
|
-
return [
|
|
1081
|
-
normalizedAlias,
|
|
1082
|
-
toCamelCase(normalizedAlias),
|
|
1083
|
-
];
|
|
1146
|
+
return [normalizedAlias, toCamelCase(normalizedAlias)];
|
|
1084
1147
|
}),
|
|
1085
1148
|
toKebabCase(key),
|
|
1086
1149
|
key,
|
|
@@ -1103,12 +1166,10 @@ function buildInputNormalizer<
|
|
|
1103
1166
|
function buildInputSchema<
|
|
1104
1167
|
TPositionals extends SimpleCLIPositionalsDefinition,
|
|
1105
1168
|
TNamed extends SimpleCLINamedDefinition,
|
|
1106
|
-
>(
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
},
|
|
1111
|
-
): z.ZodType<InputObjectFor<TPositionals, TNamed>, unknown> {
|
|
1169
|
+
>(definition: {
|
|
1170
|
+
positionals: TPositionals;
|
|
1171
|
+
named: TNamed;
|
|
1172
|
+
}): z.ZodType<InputObjectFor<TPositionals, TNamed>, unknown> {
|
|
1112
1173
|
const shape: Record<string, ZodTypeAny> = {};
|
|
1113
1174
|
|
|
1114
1175
|
for (const positional of definition.positionals) {
|
|
@@ -1140,7 +1201,12 @@ function positional<TKey extends string, TSchema extends ZodTypeAny>(
|
|
|
1140
1201
|
|
|
1141
1202
|
function option<TSchema extends ZodTypeAny>(
|
|
1142
1203
|
schema: TSchema,
|
|
1143
|
-
options?: {
|
|
1204
|
+
options?: {
|
|
1205
|
+
help?: string;
|
|
1206
|
+
name?: string;
|
|
1207
|
+
aliases?: readonly string[];
|
|
1208
|
+
source?: "--";
|
|
1209
|
+
},
|
|
1144
1210
|
): SimpleCLINamedArgDefinition<TSchema> {
|
|
1145
1211
|
return {
|
|
1146
1212
|
kind: "option",
|
|
@@ -1152,9 +1218,11 @@ function option<TSchema extends ZodTypeAny>(
|
|
|
1152
1218
|
};
|
|
1153
1219
|
}
|
|
1154
1220
|
|
|
1155
|
-
function flag(
|
|
1156
|
-
|
|
1157
|
-
|
|
1221
|
+
function flag(options?: {
|
|
1222
|
+
help?: string;
|
|
1223
|
+
name?: string;
|
|
1224
|
+
aliases?: readonly string[];
|
|
1225
|
+
}): SimpleCLINamedArgDefinition<z.ZodDefault<z.ZodBoolean>> {
|
|
1158
1226
|
return {
|
|
1159
1227
|
kind: "flag",
|
|
1160
1228
|
schema: z.boolean().default(false),
|
|
@@ -1184,9 +1252,11 @@ type SimpleCLIScope<
|
|
|
1184
1252
|
TContext extends SimpleCLIContext,
|
|
1185
1253
|
> = {
|
|
1186
1254
|
use<TContextOut extends SimpleCLIContext>(
|
|
1187
|
-
middleware: SimpleCLIMiddleware<unknown, TContext, TContextOut
|
|
1255
|
+
middleware: SimpleCLIMiddleware<unknown, TContext, TContextOut>,
|
|
1188
1256
|
): SimpleCLIScope<TParentContext, TContextOut>;
|
|
1189
|
-
group(
|
|
1257
|
+
group(
|
|
1258
|
+
config: SimpleCLIGroupConfig<TContext>,
|
|
1259
|
+
): SimpleCLIGroup<TParentContext, TContext>;
|
|
1190
1260
|
command(
|
|
1191
1261
|
config: SimpleCLICommandConfig,
|
|
1192
1262
|
): SimpleCLICommandBuilder<unknown, TParentContext, TContext, unknown>;
|
|
@@ -1201,9 +1271,7 @@ function command(
|
|
|
1201
1271
|
});
|
|
1202
1272
|
}
|
|
1203
1273
|
|
|
1204
|
-
function group(
|
|
1205
|
-
config: SimpleCLIGroupConfig<{}>,
|
|
1206
|
-
): SimpleCLIGroup<{}, {}> {
|
|
1274
|
+
function group(config: SimpleCLIGroupConfig<{}>): SimpleCLIGroup<{}, {}> {
|
|
1207
1275
|
return createScope<{}, {}>([]).group(config);
|
|
1208
1276
|
}
|
|
1209
1277
|
|
|
@@ -1222,7 +1290,9 @@ function createScope<
|
|
|
1222
1290
|
middleware,
|
|
1223
1291
|
]);
|
|
1224
1292
|
},
|
|
1225
|
-
group(
|
|
1293
|
+
group(
|
|
1294
|
+
config: SimpleCLIGroupConfig<TContext>,
|
|
1295
|
+
): SimpleCLIGroup<TParentContext, TContext> {
|
|
1226
1296
|
return {
|
|
1227
1297
|
kind: "group",
|
|
1228
1298
|
description: config.description,
|
|
@@ -1255,11 +1325,8 @@ function define(
|
|
|
1255
1325
|
return new SimpleCLIApp(name, routes, config);
|
|
1256
1326
|
}
|
|
1257
1327
|
|
|
1258
|
-
export type InferInput<TInput extends SimpleCLIInput<unknown>> =
|
|
1259
|
-
infer TOutput
|
|
1260
|
-
>
|
|
1261
|
-
? TOutput
|
|
1262
|
-
: never;
|
|
1328
|
+
export type InferInput<TInput extends SimpleCLIInput<unknown>> =
|
|
1329
|
+
TInput extends SimpleCLIInput<infer TOutput> ? TOutput : never;
|
|
1263
1330
|
|
|
1264
1331
|
export const SimpleCLI = {
|
|
1265
1332
|
define,
|
package/src/cli/router.ts
CHANGED
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
import type { Logger } from "../shared/logger/index.js";
|
|
2
1
|
import { aiCommands } from "./commands/ai.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { browserCommands } from "./commands/browser.js";
|
|
3
|
+
import { executionCommands } from "./commands/execution.js";
|
|
5
4
|
import { initCommand } from "./commands/init.js";
|
|
6
5
|
import { logCommands } from "./commands/logs.js";
|
|
7
|
-
import {
|
|
8
|
-
import { createSnapshotCommand } from "./commands/snapshot.js";
|
|
6
|
+
import { snapshotCommand } from "./commands/snapshot.js";
|
|
9
7
|
import { SimpleCLI } from "./framework/simple-cli.js";
|
|
10
8
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
-
}
|
|
9
|
+
export const cliRoutes = {
|
|
10
|
+
...browserCommands,
|
|
11
|
+
...executionCommands,
|
|
12
|
+
...logCommands,
|
|
13
|
+
ai: aiCommands,
|
|
14
|
+
init: initCommand,
|
|
15
|
+
snapshot: snapshotCommand,
|
|
16
|
+
};
|
|
21
17
|
|
|
22
|
-
export function createCLIApp(
|
|
23
|
-
return SimpleCLI.define("libretto",
|
|
24
|
-
globalNamed: {
|
|
25
|
-
session: sessionOption(),
|
|
26
|
-
},
|
|
27
|
-
});
|
|
18
|
+
export function createCLIApp() {
|
|
19
|
+
return SimpleCLI.define("libretto", cliRoutes);
|
|
28
20
|
}
|