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
|
@@ -6,8 +6,10 @@ import { z } from "zod";
|
|
|
6
6
|
import { installInstrumentation } from "../../shared/instrumentation/index.js";
|
|
7
7
|
import {
|
|
8
8
|
connect,
|
|
9
|
-
disconnectBrowser
|
|
9
|
+
disconnectBrowser,
|
|
10
|
+
resolveViewport
|
|
10
11
|
} from "../core/browser.js";
|
|
12
|
+
import { parseViewportArg } from "./browser.js";
|
|
11
13
|
import { getPauseSignalPaths } from "../core/pause-signals.js";
|
|
12
14
|
import {
|
|
13
15
|
assertSessionAvailableForStart,
|
|
@@ -22,10 +24,10 @@ import {
|
|
|
22
24
|
} from "../core/telemetry.js";
|
|
23
25
|
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
24
26
|
import {
|
|
25
|
-
loadSessionStateMiddleware,
|
|
26
27
|
pageOption,
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
sessionOption,
|
|
29
|
+
withAutoSession,
|
|
30
|
+
withRequiredSession
|
|
29
31
|
} from "./shared.js";
|
|
30
32
|
const stripTypeScriptTypes = moduleBuiltin.stripTypeScriptTypes;
|
|
31
33
|
const require2 = moduleBuiltin.createRequire(import.meta.url);
|
|
@@ -69,23 +71,91 @@ function compileExecFunction(code, helperNames) {
|
|
|
69
71
|
}).constructor;
|
|
70
72
|
return new AsyncFunction(...helperNames, code);
|
|
71
73
|
}
|
|
74
|
+
function stripEmptyCatchHandlers(code) {
|
|
75
|
+
const catchRe = /\??\s*\.catch\(\s*\(\)\s*=>\s*\{\s*\}\s*\)/g;
|
|
76
|
+
let strippedCount = 0;
|
|
77
|
+
let result = "";
|
|
78
|
+
let i = 0;
|
|
79
|
+
while (i < code.length) {
|
|
80
|
+
if (code[i] === "/" && code[i + 1] === "/") {
|
|
81
|
+
const end = code.indexOf("\n", i);
|
|
82
|
+
const slice = end === -1 ? code.slice(i) : code.slice(i, end + 1);
|
|
83
|
+
result += slice;
|
|
84
|
+
i += slice.length;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (code[i] === "/" && code[i + 1] === "*") {
|
|
88
|
+
const end = code.indexOf("*/", i + 2);
|
|
89
|
+
const slice = end === -1 ? code.slice(i) : code.slice(i, end + 2);
|
|
90
|
+
result += slice;
|
|
91
|
+
i += slice.length;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (code[i] === '"' || code[i] === "'" || code[i] === "`") {
|
|
95
|
+
const quote = code[i];
|
|
96
|
+
let j = i + 1;
|
|
97
|
+
while (j < code.length) {
|
|
98
|
+
if (code[j] === "\\" && quote !== "`") {
|
|
99
|
+
j += 2;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (code[j] === "\\" && quote === "`") {
|
|
103
|
+
j += 2;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (code[j] === quote) {
|
|
107
|
+
j++;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
if (quote === "`" && code[j] === "$" && code[j + 1] === "{") {
|
|
111
|
+
let depth = 1;
|
|
112
|
+
j += 2;
|
|
113
|
+
while (j < code.length && depth > 0) {
|
|
114
|
+
if (code[j] === "{") depth++;
|
|
115
|
+
else if (code[j] === "}") depth--;
|
|
116
|
+
j++;
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
j++;
|
|
121
|
+
}
|
|
122
|
+
result += code.slice(i, j);
|
|
123
|
+
i = j;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
catchRe.lastIndex = i;
|
|
127
|
+
const match = catchRe.exec(code);
|
|
128
|
+
if (match && match.index === i) {
|
|
129
|
+
strippedCount++;
|
|
130
|
+
i += match[0].length;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
result += code[i];
|
|
134
|
+
i++;
|
|
135
|
+
}
|
|
136
|
+
return { cleaned: result, strippedCount };
|
|
137
|
+
}
|
|
72
138
|
async function runExec(code, session, logger, visualize = false, pageId) {
|
|
139
|
+
const { cleaned: cleanedCode, strippedCount } = stripEmptyCatchHandlers(code);
|
|
140
|
+
if (strippedCount > 0) {
|
|
141
|
+
console.log("(Stripped `.catch(() => {})` \u2014 letting errors bubble up)");
|
|
142
|
+
}
|
|
73
143
|
logger.info("exec-start", {
|
|
74
144
|
session,
|
|
75
|
-
codeLength:
|
|
76
|
-
codePreview:
|
|
145
|
+
codeLength: cleanedCode.length,
|
|
146
|
+
codePreview: cleanedCode.slice(0, 200),
|
|
77
147
|
visualize,
|
|
78
148
|
pageId
|
|
79
149
|
});
|
|
80
|
-
const {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
);
|
|
150
|
+
const {
|
|
151
|
+
browser,
|
|
152
|
+
context,
|
|
153
|
+
page,
|
|
154
|
+
pageId: resolvedPageId
|
|
155
|
+
} = await connect(session, logger, 1e4, {
|
|
156
|
+
pageId,
|
|
157
|
+
requireSinglePage: true
|
|
158
|
+
});
|
|
89
159
|
const STALL_THRESHOLD_MS = 6e4;
|
|
90
160
|
let lastActivityTs = Date.now();
|
|
91
161
|
const onActivity = () => {
|
|
@@ -97,10 +167,10 @@ async function runExec(code, session, logger, visualize = false, pageId) {
|
|
|
97
167
|
logger.warn("exec-stall-warning", {
|
|
98
168
|
session,
|
|
99
169
|
silenceMs,
|
|
100
|
-
codePreview:
|
|
170
|
+
codePreview: cleanedCode.slice(0, 200)
|
|
101
171
|
});
|
|
102
172
|
console.warn(
|
|
103
|
-
`[stall-warning] No Playwright activity for ${Math.round(silenceMs / 1e3)}s \u2014 exec may be hung (code: ${
|
|
173
|
+
`[stall-warning] No Playwright activity for ${Math.round(silenceMs / 1e3)}s \u2014 exec may be hung (code: ${cleanedCode.slice(0, 100)}...)`
|
|
104
174
|
);
|
|
105
175
|
}
|
|
106
176
|
}, STALL_THRESHOLD_MS);
|
|
@@ -109,7 +179,7 @@ async function runExec(code, session, logger, visualize = false, pageId) {
|
|
|
109
179
|
logger.info("exec-interrupted", {
|
|
110
180
|
session,
|
|
111
181
|
duration: Date.now() - execStartTs,
|
|
112
|
-
codePreview:
|
|
182
|
+
codePreview: cleanedCode.slice(0, 200)
|
|
113
183
|
});
|
|
114
184
|
};
|
|
115
185
|
process.on("SIGINT", sigintHandler);
|
|
@@ -142,19 +212,21 @@ async function runExec(code, session, logger, visualize = false, pageId) {
|
|
|
142
212
|
Buffer
|
|
143
213
|
};
|
|
144
214
|
const helperNames = Object.keys(helpers);
|
|
145
|
-
const fn = compileExecFunction(
|
|
215
|
+
const fn = compileExecFunction(cleanedCode, helperNames);
|
|
146
216
|
const result = await fn(...Object.values(helpers));
|
|
147
217
|
logger.info("exec-success", { session, hasResult: result !== void 0 });
|
|
148
218
|
if (result !== void 0) {
|
|
149
219
|
console.log(
|
|
150
220
|
typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
151
221
|
);
|
|
222
|
+
} else {
|
|
223
|
+
console.log("Executed successfully");
|
|
152
224
|
}
|
|
153
225
|
} catch (err) {
|
|
154
226
|
logger.error("exec-error", {
|
|
155
227
|
error: err,
|
|
156
228
|
session,
|
|
157
|
-
codePreview:
|
|
229
|
+
codePreview: cleanedCode.slice(0, 200)
|
|
158
230
|
});
|
|
159
231
|
throw err;
|
|
160
232
|
} finally {
|
|
@@ -191,6 +263,7 @@ async function stopExistingFailedRunSession(session, logger) {
|
|
|
191
263
|
port: existingState.port
|
|
192
264
|
});
|
|
193
265
|
clearSessionState(session, logger);
|
|
266
|
+
if (existingState.pid == null) return;
|
|
194
267
|
const stopDeadline = Date.now() + 3e3;
|
|
195
268
|
while (isProcessRunning(existingState.pid) && Date.now() < stopDeadline) {
|
|
196
269
|
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
@@ -257,10 +330,18 @@ async function waitForWorkflowOutcome(args) {
|
|
|
257
330
|
}
|
|
258
331
|
let outputOffset = 0;
|
|
259
332
|
while (true) {
|
|
260
|
-
outputOffset = streamOutputSince(
|
|
333
|
+
outputOffset = streamOutputSince(
|
|
334
|
+
signalPaths.outputSignalPath,
|
|
335
|
+
outputOffset
|
|
336
|
+
);
|
|
261
337
|
if (existsSync(signalPaths.failedSignalPath)) {
|
|
262
|
-
outputOffset = streamOutputSince(
|
|
263
|
-
|
|
338
|
+
outputOffset = streamOutputSince(
|
|
339
|
+
signalPaths.outputSignalPath,
|
|
340
|
+
outputOffset
|
|
341
|
+
);
|
|
342
|
+
const failureDetails = await waitForFailureDetails(
|
|
343
|
+
signalPaths.failedSignalPath
|
|
344
|
+
);
|
|
264
345
|
return {
|
|
265
346
|
status: "failed",
|
|
266
347
|
message: failureDetails?.message,
|
|
@@ -268,15 +349,24 @@ async function waitForWorkflowOutcome(args) {
|
|
|
268
349
|
};
|
|
269
350
|
}
|
|
270
351
|
if (existsSync(signalPaths.completedSignalPath)) {
|
|
271
|
-
outputOffset = streamOutputSince(
|
|
352
|
+
outputOffset = streamOutputSince(
|
|
353
|
+
signalPaths.outputSignalPath,
|
|
354
|
+
outputOffset
|
|
355
|
+
);
|
|
272
356
|
return { status: "completed" };
|
|
273
357
|
}
|
|
274
358
|
if (existsSync(signalPaths.pausedSignalPath)) {
|
|
275
|
-
outputOffset = streamOutputSince(
|
|
359
|
+
outputOffset = streamOutputSince(
|
|
360
|
+
signalPaths.outputSignalPath,
|
|
361
|
+
outputOffset
|
|
362
|
+
);
|
|
276
363
|
return { status: "paused" };
|
|
277
364
|
}
|
|
278
365
|
if (!isProcessRunning(args.pid)) {
|
|
279
|
-
outputOffset = streamOutputSince(
|
|
366
|
+
outputOffset = streamOutputSince(
|
|
367
|
+
signalPaths.outputSignalPath,
|
|
368
|
+
outputOffset
|
|
369
|
+
);
|
|
280
370
|
return { status: "exited" };
|
|
281
371
|
}
|
|
282
372
|
await new Promise((resolveWait) => setTimeout(resolveWait, 250));
|
|
@@ -295,9 +385,9 @@ async function runResume(session, logger, sessionState) {
|
|
|
295
385
|
`Session "${session}" is not paused. Run "libretto run ... --session ${session}" and call pause("${session}") first.`
|
|
296
386
|
);
|
|
297
387
|
}
|
|
298
|
-
if (!isProcessRunning(sessionState.pid)) {
|
|
388
|
+
if (sessionState.pid == null || !isProcessRunning(sessionState.pid)) {
|
|
299
389
|
throw new Error(
|
|
300
|
-
`No active paused workflow found for session "${session}" (worker pid ${sessionState.pid} is not running).`
|
|
390
|
+
`No active paused workflow found for session "${session}" (worker pid ${sessionState.pid ?? "unknown"} is not running).`
|
|
301
391
|
);
|
|
302
392
|
}
|
|
303
393
|
clearSignalIfExists(pausedSignalPath);
|
|
@@ -359,18 +449,24 @@ async function runIntegrationFromFile(args, logger) {
|
|
|
359
449
|
session: args.session,
|
|
360
450
|
params: args.params,
|
|
361
451
|
headless: args.headless,
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
tsxCliPath,
|
|
366
|
-
...args.tsconfigPath ? ["--tsconfig", args.tsconfigPath] : [],
|
|
367
|
-
workerEntryPath,
|
|
368
|
-
payload
|
|
369
|
-
], {
|
|
370
|
-
detached: true,
|
|
371
|
-
stdio: "ignore",
|
|
372
|
-
env: process.env
|
|
452
|
+
visualize: args.visualize,
|
|
453
|
+
authProfileDomain: args.authProfileDomain,
|
|
454
|
+
viewport: args.viewport
|
|
373
455
|
});
|
|
456
|
+
const worker = spawn(
|
|
457
|
+
process.execPath,
|
|
458
|
+
[
|
|
459
|
+
tsxCliPath,
|
|
460
|
+
...args.tsconfigPath ? ["--tsconfig", args.tsconfigPath] : [],
|
|
461
|
+
workerEntryPath,
|
|
462
|
+
payload
|
|
463
|
+
],
|
|
464
|
+
{
|
|
465
|
+
detached: true,
|
|
466
|
+
stdio: "ignore",
|
|
467
|
+
env: process.env
|
|
468
|
+
}
|
|
469
|
+
);
|
|
374
470
|
worker.unref();
|
|
375
471
|
const outcome = await waitForWorkflowOutcome({
|
|
376
472
|
session: args.session,
|
|
@@ -409,27 +505,27 @@ const execInput = SimpleCLI.input({
|
|
|
409
505
|
],
|
|
410
506
|
named: {
|
|
411
507
|
session: sessionOption(),
|
|
412
|
-
visualize: SimpleCLI.flag({
|
|
508
|
+
visualize: SimpleCLI.flag({
|
|
509
|
+
help: "Enable ghost cursor + highlight visualization"
|
|
510
|
+
}),
|
|
413
511
|
page: pageOption()
|
|
414
512
|
}
|
|
415
513
|
}).refine(
|
|
416
514
|
(input) => input.codeParts.length > 0,
|
|
417
515
|
`Usage: libretto exec <code> [--session <name>] [--visualize]`
|
|
418
516
|
);
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
const runUsage = `Usage: libretto run <integrationFile> <integrationExport> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless]`;
|
|
517
|
+
const execCommand = SimpleCLI.command({
|
|
518
|
+
description: "Execute Playwright TypeScript code"
|
|
519
|
+
}).input(execInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
|
|
520
|
+
await runExec(
|
|
521
|
+
input.codeParts.join(" "),
|
|
522
|
+
ctx.session,
|
|
523
|
+
ctx.logger,
|
|
524
|
+
input.visualize,
|
|
525
|
+
input.page
|
|
526
|
+
);
|
|
527
|
+
});
|
|
528
|
+
const runUsage = `Usage: libretto run <integrationFile> <integrationExport> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--no-visualize] [--viewport WxH]`;
|
|
433
529
|
const runInput = SimpleCLI.input({
|
|
434
530
|
positionals: [
|
|
435
531
|
SimpleCLI.positional("integrationFile", z.string().optional(), {
|
|
@@ -453,15 +549,28 @@ const runInput = SimpleCLI.input({
|
|
|
453
549
|
}),
|
|
454
550
|
headed: SimpleCLI.flag({ help: "Run in headed mode" }),
|
|
455
551
|
headless: SimpleCLI.flag({ help: "Run in headless mode" }),
|
|
552
|
+
noVisualize: SimpleCLI.flag({
|
|
553
|
+
name: "no-visualize",
|
|
554
|
+
help: "Disable ghost cursor + highlight visualization in headed mode"
|
|
555
|
+
}),
|
|
456
556
|
authProfile: SimpleCLI.option(z.string().optional(), {
|
|
457
557
|
name: "auth-profile",
|
|
458
558
|
help: "Domain for local auth profile (e.g. apps.example.com)"
|
|
559
|
+
}),
|
|
560
|
+
viewport: SimpleCLI.option(z.string().optional(), {
|
|
561
|
+
help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)"
|
|
459
562
|
})
|
|
460
563
|
}
|
|
461
564
|
}).refine(
|
|
462
565
|
(input) => Boolean(input.integrationFile && input.integrationExport),
|
|
463
566
|
runUsage
|
|
464
|
-
).refine(
|
|
567
|
+
).refine(
|
|
568
|
+
(input) => !(input.params && input.paramsFile),
|
|
569
|
+
"Pass either --params or --params-file, not both."
|
|
570
|
+
).refine(
|
|
571
|
+
(input) => !(input.headed && input.headless),
|
|
572
|
+
"Cannot pass both --headed and --headless."
|
|
573
|
+
);
|
|
465
574
|
function resolveRunParams(rawInlineParams, paramsFile) {
|
|
466
575
|
if (paramsFile) {
|
|
467
576
|
let content;
|
|
@@ -479,51 +588,55 @@ function resolveRunParams(rawInlineParams, paramsFile) {
|
|
|
479
588
|
}
|
|
480
589
|
return {};
|
|
481
590
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
591
|
+
const runCommand = SimpleCLI.command({
|
|
592
|
+
description: "Run an exported Libretto workflow from a file"
|
|
593
|
+
}).input(runInput).use(withAutoSession()).handle(async ({ input, ctx }) => {
|
|
594
|
+
await stopExistingFailedRunSession(ctx.session, ctx.logger);
|
|
595
|
+
assertSessionAvailableForStart(ctx.session, ctx.logger);
|
|
596
|
+
const params = resolveRunParams(input.params, input.paramsFile);
|
|
597
|
+
const headlessMode = input.headed ? false : input.headless ? true : void 0;
|
|
598
|
+
const visualize = !input.noVisualize;
|
|
599
|
+
const viewport = resolveViewport(
|
|
600
|
+
parseViewportArg(input.viewport),
|
|
601
|
+
ctx.logger
|
|
602
|
+
);
|
|
603
|
+
await runIntegrationFromFile(
|
|
604
|
+
{
|
|
491
605
|
integrationPath: input.integrationFile,
|
|
492
606
|
exportName: input.integrationExport,
|
|
493
607
|
session: ctx.session,
|
|
494
608
|
params,
|
|
495
609
|
tsconfigPath: input.tsconfig,
|
|
496
610
|
headless: headlessMode ?? false,
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
611
|
+
visualize,
|
|
612
|
+
authProfileDomain: input.authProfile,
|
|
613
|
+
viewport
|
|
614
|
+
},
|
|
615
|
+
ctx.logger
|
|
616
|
+
);
|
|
617
|
+
});
|
|
501
618
|
const resumeInput = SimpleCLI.input({
|
|
502
619
|
positionals: [],
|
|
503
620
|
named: {
|
|
504
621
|
session: sessionOption()
|
|
505
622
|
}
|
|
506
623
|
});
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
run: createRunCommand(logger),
|
|
518
|
-
resume: createResumeCommand(logger)
|
|
519
|
-
};
|
|
520
|
-
}
|
|
624
|
+
const resumeCommand = SimpleCLI.command({
|
|
625
|
+
description: "Resume a paused workflow for the current session"
|
|
626
|
+
}).input(resumeInput).use(withRequiredSession()).handle(async ({ ctx }) => {
|
|
627
|
+
await runResume(ctx.session, ctx.logger, ctx.sessionState);
|
|
628
|
+
});
|
|
629
|
+
const executionCommands = {
|
|
630
|
+
exec: execCommand,
|
|
631
|
+
run: runCommand,
|
|
632
|
+
resume: resumeCommand
|
|
633
|
+
};
|
|
521
634
|
export {
|
|
522
|
-
|
|
523
|
-
createExecutionCommands,
|
|
524
|
-
createResumeCommand,
|
|
525
|
-
createRunCommand,
|
|
635
|
+
execCommand,
|
|
526
636
|
execInput,
|
|
637
|
+
executionCommands,
|
|
638
|
+
resumeCommand,
|
|
527
639
|
resumeInput,
|
|
640
|
+
runCommand,
|
|
528
641
|
runInput
|
|
529
642
|
};
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
appendFileSync,
|
|
4
|
+
cpSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
writeFileSync
|
|
10
|
+
} from "node:fs";
|
|
3
11
|
import { spawnSync } from "node:child_process";
|
|
4
12
|
import { basename, dirname, join } from "node:path";
|
|
5
13
|
import { fileURLToPath } from "node:url";
|
|
@@ -9,8 +17,8 @@ import {
|
|
|
9
17
|
loadSnapshotEnv,
|
|
10
18
|
resolveSnapshotApiModel
|
|
11
19
|
} from "../core/snapshot-api-config.js";
|
|
12
|
-
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
13
20
|
import { hasProviderCredentials } from "../../shared/llm/client.js";
|
|
21
|
+
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
14
22
|
const PROVIDER_CHOICES = [
|
|
15
23
|
{
|
|
16
24
|
key: "1",
|
|
@@ -44,15 +52,6 @@ function promptUser(rl, question) {
|
|
|
44
52
|
});
|
|
45
53
|
});
|
|
46
54
|
}
|
|
47
|
-
function askYesNo(question) {
|
|
48
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
49
|
-
return new Promise((resolve) => {
|
|
50
|
-
rl.question(`${question} (y/N) `, (answer) => {
|
|
51
|
-
rl.close();
|
|
52
|
-
resolve(answer.trim().toLowerCase() === "y");
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
55
|
function safeReadAiConfig() {
|
|
57
56
|
try {
|
|
58
57
|
return readAiConfig();
|
|
@@ -83,7 +82,9 @@ function printSnapshotApiStatus() {
|
|
|
83
82
|
printInvalidAiConfigWarning();
|
|
84
83
|
if (selection && hasProviderCredentials(selection.provider)) {
|
|
85
84
|
console.log(` \u2713 Ready: ${selection.model} (${selection.source})`);
|
|
86
|
-
console.log(
|
|
85
|
+
console.log(
|
|
86
|
+
" Snapshot objectives will use the API analyzer by default."
|
|
87
|
+
);
|
|
87
88
|
console.log(" No further action required.");
|
|
88
89
|
return;
|
|
89
90
|
}
|
|
@@ -98,7 +99,9 @@ function printSnapshotApiStatus() {
|
|
|
98
99
|
console.log(
|
|
99
100
|
" Or run `npx libretto ai configure openai | anthropic | gemini | vertex` to set a specific model."
|
|
100
101
|
);
|
|
101
|
-
console.log(
|
|
102
|
+
console.log(
|
|
103
|
+
" Run `npx libretto init` interactively to set up credentials."
|
|
104
|
+
);
|
|
102
105
|
}
|
|
103
106
|
async function runInteractiveApiSetup() {
|
|
104
107
|
const config = safeReadAiConfig();
|
|
@@ -110,7 +113,9 @@ async function runInteractiveApiSetup() {
|
|
|
110
113
|
printInvalidAiConfigWarning();
|
|
111
114
|
if (selection && hasProviderCredentials(selection.provider)) {
|
|
112
115
|
console.log(` \u2713 Ready: ${selection.model} (${selection.source})`);
|
|
113
|
-
console.log(
|
|
116
|
+
console.log(
|
|
117
|
+
" Snapshot objectives will use the API analyzer by default."
|
|
118
|
+
);
|
|
114
119
|
return;
|
|
115
120
|
}
|
|
116
121
|
console.log(" \u2717 No snapshot API credentials detected.\n");
|
|
@@ -119,14 +124,18 @@ async function runInteractiveApiSetup() {
|
|
|
119
124
|
output: process.stdout
|
|
120
125
|
});
|
|
121
126
|
try {
|
|
122
|
-
console.log(
|
|
127
|
+
console.log(
|
|
128
|
+
" Which API provider would you like to use for snapshot analysis?\n"
|
|
129
|
+
);
|
|
123
130
|
for (const choice of PROVIDER_CHOICES) {
|
|
124
131
|
console.log(` ${choice.key}) ${choice.label}`);
|
|
125
132
|
}
|
|
126
133
|
console.log(" s) Skip for now\n");
|
|
127
134
|
const answer = await promptUser(rl, " Choice: ");
|
|
128
135
|
if (answer.toLowerCase() === "s" || !answer) {
|
|
129
|
-
console.log(
|
|
136
|
+
console.log(
|
|
137
|
+
"\n Skipped. You can set up API credentials later by rerunning `npx libretto init`."
|
|
138
|
+
);
|
|
130
139
|
console.log(" Or add credentials directly to your .env file:");
|
|
131
140
|
console.log(" OPENAI_API_KEY=...");
|
|
132
141
|
console.log(" ANTHROPIC_API_KEY=...");
|
|
@@ -215,22 +224,15 @@ function detectAgentDirs(root) {
|
|
|
215
224
|
if (existsSync(join(root, ".claude"))) dirs.push(join(root, ".claude"));
|
|
216
225
|
return dirs;
|
|
217
226
|
}
|
|
218
|
-
|
|
227
|
+
function copySkills() {
|
|
219
228
|
const agentDirs = detectAgentDirs(REPO_ROOT);
|
|
220
229
|
if (agentDirs.length === 0) {
|
|
221
|
-
console.log(
|
|
230
|
+
console.log(
|
|
231
|
+
"\nSkills: No .agents/ or .claude/ directory found in repo root \u2014 skipping."
|
|
232
|
+
);
|
|
222
233
|
return;
|
|
223
234
|
}
|
|
224
235
|
const destinations = agentDirs.map((d) => join(d, "skills", "libretto"));
|
|
225
|
-
const dirNames = agentDirs.map((d) => basename(d)).join(" and ");
|
|
226
|
-
const existing = destinations.filter((d) => existsSync(d));
|
|
227
|
-
const verb = existing.length > 0 ? "Overwrite" : "Install";
|
|
228
|
-
const proceed = await askYesNo(`
|
|
229
|
-
${verb} libretto skills in ${dirNames}?`);
|
|
230
|
-
if (!proceed) {
|
|
231
|
-
console.log(" Skipping skill copy.");
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
236
|
let sourceDir;
|
|
235
237
|
try {
|
|
236
238
|
sourceDir = getPackageSkillsDir();
|
|
@@ -246,7 +248,9 @@ ${verb} libretto skills in ${dirNames}?`);
|
|
|
246
248
|
}
|
|
247
249
|
cpSync(sourceDir, skillDest, { recursive: true });
|
|
248
250
|
const fileCount = readdirSync(skillDest).length;
|
|
249
|
-
console.log(
|
|
251
|
+
console.log(
|
|
252
|
+
` \u2713 Copied ${fileCount} skill files to ${name}/skills/libretto/`
|
|
253
|
+
);
|
|
250
254
|
}
|
|
251
255
|
}
|
|
252
256
|
const initInput = SimpleCLI.input({
|
|
@@ -267,10 +271,11 @@ const initCommand = SimpleCLI.command({
|
|
|
267
271
|
} else {
|
|
268
272
|
console.log("\nSkipping browser installation (--skip-browsers)");
|
|
269
273
|
}
|
|
274
|
+
copySkills();
|
|
270
275
|
if (process.stdin.isTTY) {
|
|
271
|
-
await copySkills();
|
|
272
276
|
await runInteractiveApiSetup();
|
|
273
277
|
} else {
|
|
278
|
+
loadSnapshotEnv();
|
|
274
279
|
printSnapshotApiStatus();
|
|
275
280
|
}
|
|
276
281
|
console.log("\n\u2713 libretto init complete");
|
|
@@ -12,10 +12,9 @@ import {
|
|
|
12
12
|
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
13
13
|
import {
|
|
14
14
|
integerOption,
|
|
15
|
-
loadSessionStateMiddleware,
|
|
16
15
|
pageOption,
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
sessionOption,
|
|
17
|
+
withRequiredSession
|
|
19
18
|
} from "./shared.js";
|
|
20
19
|
async function resolvePageId(session, pageId) {
|
|
21
20
|
if (!pageId) return void 0;
|
|
@@ -44,7 +43,7 @@ const networkInput = SimpleCLI.input({
|
|
|
44
43
|
});
|
|
45
44
|
const networkCommand = SimpleCLI.command({
|
|
46
45
|
description: "View captured network requests"
|
|
47
|
-
}).input(networkInput).use(
|
|
46
|
+
}).input(networkInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
|
|
48
47
|
if (input.clear) {
|
|
49
48
|
clearNetworkLog(ctx.session);
|
|
50
49
|
console.log("Network log cleared.");
|
|
@@ -81,7 +80,7 @@ const actionsInput = SimpleCLI.input({
|
|
|
81
80
|
});
|
|
82
81
|
const actionsCommand = SimpleCLI.command({
|
|
83
82
|
description: "View captured actions"
|
|
84
|
-
}).input(actionsInput).use(
|
|
83
|
+
}).input(actionsInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
|
|
85
84
|
if (input.clear) {
|
|
86
85
|
clearActionLog(ctx.session);
|
|
87
86
|
console.log("Action log cleared.");
|