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
|
@@ -8,7 +8,9 @@ import type { LoggerApi } from "../../shared/logger/index.js";
|
|
|
8
8
|
import {
|
|
9
9
|
connect,
|
|
10
10
|
disconnectBrowser,
|
|
11
|
+
resolveViewport,
|
|
11
12
|
} from "../core/browser.js";
|
|
13
|
+
import { parseViewportArg } from "./browser.js";
|
|
12
14
|
import { getPauseSignalPaths } from "../core/pause-signals.js";
|
|
13
15
|
import {
|
|
14
16
|
assertSessionAvailableForStart,
|
|
@@ -22,15 +24,13 @@ import {
|
|
|
22
24
|
readNetworkLog,
|
|
23
25
|
wrapPageForActionLogging,
|
|
24
26
|
} from "../core/telemetry.js";
|
|
25
|
-
import type {
|
|
26
|
-
RunIntegrationWorkerRequest,
|
|
27
|
-
} from "../workers/run-integration-worker-protocol.js";
|
|
27
|
+
import type { RunIntegrationWorkerRequest } from "../workers/run-integration-worker-protocol.js";
|
|
28
28
|
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
29
29
|
import {
|
|
30
|
-
loadSessionStateMiddleware,
|
|
31
30
|
pageOption,
|
|
32
|
-
resolveSessionMiddleware,
|
|
33
31
|
sessionOption,
|
|
32
|
+
withAutoSession,
|
|
33
|
+
withRequiredSession,
|
|
34
34
|
} from "./shared.js";
|
|
35
35
|
|
|
36
36
|
type ExecFunction = (...args: unknown[]) => Promise<unknown>;
|
|
@@ -117,6 +117,87 @@ function compileExecFunction(
|
|
|
117
117
|
return new AsyncFunction(...helperNames, code);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Strip `.catch(() => {})` / `?.catch(() => {})` from executable code,
|
|
122
|
+
* skipping occurrences inside string literals (single, double, backtick)
|
|
123
|
+
* and single-line / multi-line comments so we never corrupt non-code text.
|
|
124
|
+
*/
|
|
125
|
+
function stripEmptyCatchHandlers(code: string): {
|
|
126
|
+
cleaned: string;
|
|
127
|
+
strippedCount: number;
|
|
128
|
+
} {
|
|
129
|
+
const catchRe = /\??\s*\.catch\(\s*\(\)\s*=>\s*\{\s*\}\s*\)/g;
|
|
130
|
+
let strippedCount = 0;
|
|
131
|
+
let result = "";
|
|
132
|
+
let i = 0;
|
|
133
|
+
|
|
134
|
+
while (i < code.length) {
|
|
135
|
+
// Single-line comment
|
|
136
|
+
if (code[i] === "/" && code[i + 1] === "/") {
|
|
137
|
+
const end = code.indexOf("\n", i);
|
|
138
|
+
const slice = end === -1 ? code.slice(i) : code.slice(i, end + 1);
|
|
139
|
+
result += slice;
|
|
140
|
+
i += slice.length;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Multi-line comment
|
|
144
|
+
if (code[i] === "/" && code[i + 1] === "*") {
|
|
145
|
+
const end = code.indexOf("*/", i + 2);
|
|
146
|
+
const slice = end === -1 ? code.slice(i) : code.slice(i, end + 2);
|
|
147
|
+
result += slice;
|
|
148
|
+
i += slice.length;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
// String literals
|
|
152
|
+
if (code[i] === '"' || code[i] === "'" || code[i] === "`") {
|
|
153
|
+
const quote = code[i];
|
|
154
|
+
let j = i + 1;
|
|
155
|
+
while (j < code.length) {
|
|
156
|
+
if (code[j] === "\\" && quote !== "`") {
|
|
157
|
+
j += 2;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (code[j] === "\\" && quote === "`") {
|
|
161
|
+
j += 2;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (code[j] === quote) {
|
|
165
|
+
j++;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
// Template literal interpolation — skip nested braces
|
|
169
|
+
if (quote === "`" && code[j] === "$" && code[j + 1] === "{") {
|
|
170
|
+
let depth = 1;
|
|
171
|
+
j += 2;
|
|
172
|
+
while (j < code.length && depth > 0) {
|
|
173
|
+
if (code[j] === "{") depth++;
|
|
174
|
+
else if (code[j] === "}") depth--;
|
|
175
|
+
j++;
|
|
176
|
+
}
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
j++;
|
|
180
|
+
}
|
|
181
|
+
result += code.slice(i, j);
|
|
182
|
+
i = j;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// Try to match the catch pattern at the current position
|
|
186
|
+
catchRe.lastIndex = i;
|
|
187
|
+
const match = catchRe.exec(code);
|
|
188
|
+
if (match && match.index === i) {
|
|
189
|
+
strippedCount++;
|
|
190
|
+
i += match[0].length;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// Regular character
|
|
194
|
+
result += code[i];
|
|
195
|
+
i++;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return { cleaned: result, strippedCount };
|
|
199
|
+
}
|
|
200
|
+
|
|
120
201
|
async function runExec(
|
|
121
202
|
code: string,
|
|
122
203
|
session: string,
|
|
@@ -124,22 +205,26 @@ async function runExec(
|
|
|
124
205
|
visualize = false,
|
|
125
206
|
pageId?: string,
|
|
126
207
|
): Promise<void> {
|
|
208
|
+
const { cleaned: cleanedCode, strippedCount } = stripEmptyCatchHandlers(code);
|
|
209
|
+
if (strippedCount > 0) {
|
|
210
|
+
console.log("(Stripped `.catch(() => {})` — letting errors bubble up)");
|
|
211
|
+
}
|
|
127
212
|
logger.info("exec-start", {
|
|
128
213
|
session,
|
|
129
|
-
codeLength:
|
|
130
|
-
codePreview:
|
|
214
|
+
codeLength: cleanedCode.length,
|
|
215
|
+
codePreview: cleanedCode.slice(0, 200),
|
|
131
216
|
visualize,
|
|
132
217
|
pageId,
|
|
133
218
|
});
|
|
134
|
-
const {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
);
|
|
219
|
+
const {
|
|
220
|
+
browser,
|
|
221
|
+
context,
|
|
222
|
+
page,
|
|
223
|
+
pageId: resolvedPageId,
|
|
224
|
+
} = await connect(session, logger, 10000, {
|
|
225
|
+
pageId,
|
|
226
|
+
requireSinglePage: true,
|
|
227
|
+
});
|
|
143
228
|
|
|
144
229
|
const STALL_THRESHOLD_MS = 60_000;
|
|
145
230
|
let lastActivityTs = Date.now();
|
|
@@ -153,10 +238,10 @@ async function runExec(
|
|
|
153
238
|
logger.warn("exec-stall-warning", {
|
|
154
239
|
session,
|
|
155
240
|
silenceMs,
|
|
156
|
-
codePreview:
|
|
241
|
+
codePreview: cleanedCode.slice(0, 200),
|
|
157
242
|
});
|
|
158
243
|
console.warn(
|
|
159
|
-
`[stall-warning] No Playwright activity for ${Math.round(silenceMs / 1000)}s — exec may be hung (code: ${
|
|
244
|
+
`[stall-warning] No Playwright activity for ${Math.round(silenceMs / 1000)}s — exec may be hung (code: ${cleanedCode.slice(0, 100)}...)`,
|
|
160
245
|
);
|
|
161
246
|
}
|
|
162
247
|
}, STALL_THRESHOLD_MS);
|
|
@@ -166,7 +251,7 @@ async function runExec(
|
|
|
166
251
|
logger.info("exec-interrupted", {
|
|
167
252
|
session,
|
|
168
253
|
duration: Date.now() - execStartTs,
|
|
169
|
-
codePreview:
|
|
254
|
+
codePreview: cleanedCode.slice(0, 200),
|
|
170
255
|
});
|
|
171
256
|
};
|
|
172
257
|
process.on("SIGINT", sigintHandler);
|
|
@@ -181,7 +266,12 @@ async function runExec(
|
|
|
181
266
|
const execState: Record<string, unknown> = {};
|
|
182
267
|
|
|
183
268
|
const networkLog = (
|
|
184
|
-
opts: {
|
|
269
|
+
opts: {
|
|
270
|
+
last?: number;
|
|
271
|
+
filter?: string;
|
|
272
|
+
method?: string;
|
|
273
|
+
pageId?: string;
|
|
274
|
+
} = {},
|
|
185
275
|
) => {
|
|
186
276
|
return readNetworkLog(session, opts);
|
|
187
277
|
};
|
|
@@ -216,7 +306,7 @@ async function runExec(
|
|
|
216
306
|
};
|
|
217
307
|
|
|
218
308
|
const helperNames = Object.keys(helpers);
|
|
219
|
-
const fn = compileExecFunction(
|
|
309
|
+
const fn = compileExecFunction(cleanedCode, helperNames);
|
|
220
310
|
|
|
221
311
|
const result = await fn(...Object.values(helpers));
|
|
222
312
|
logger.info("exec-success", { session, hasResult: result !== undefined });
|
|
@@ -224,12 +314,14 @@ async function runExec(
|
|
|
224
314
|
console.log(
|
|
225
315
|
typeof result === "string" ? result : JSON.stringify(result, null, 2),
|
|
226
316
|
);
|
|
317
|
+
} else {
|
|
318
|
+
console.log("Executed successfully");
|
|
227
319
|
}
|
|
228
320
|
} catch (err) {
|
|
229
321
|
logger.error("exec-error", {
|
|
230
322
|
error: err,
|
|
231
323
|
session,
|
|
232
|
-
codePreview:
|
|
324
|
+
codePreview: cleanedCode.slice(0, 200),
|
|
233
325
|
});
|
|
234
326
|
throw err;
|
|
235
327
|
} finally {
|
|
@@ -273,6 +365,8 @@ async function stopExistingFailedRunSession(
|
|
|
273
365
|
});
|
|
274
366
|
clearSessionState(session, logger);
|
|
275
367
|
|
|
368
|
+
if (existingState.pid == null) return;
|
|
369
|
+
|
|
276
370
|
const stopDeadline = Date.now() + 3_000;
|
|
277
371
|
while (isProcessRunning(existingState.pid) && Date.now() < stopDeadline) {
|
|
278
372
|
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
@@ -371,11 +465,19 @@ async function waitForWorkflowOutcome(
|
|
|
371
465
|
let outputOffset = 0;
|
|
372
466
|
|
|
373
467
|
while (true) {
|
|
374
|
-
outputOffset = streamOutputSince(
|
|
468
|
+
outputOffset = streamOutputSince(
|
|
469
|
+
signalPaths.outputSignalPath,
|
|
470
|
+
outputOffset,
|
|
471
|
+
);
|
|
375
472
|
|
|
376
473
|
if (existsSync(signalPaths.failedSignalPath)) {
|
|
377
|
-
outputOffset = streamOutputSince(
|
|
378
|
-
|
|
474
|
+
outputOffset = streamOutputSince(
|
|
475
|
+
signalPaths.outputSignalPath,
|
|
476
|
+
outputOffset,
|
|
477
|
+
);
|
|
478
|
+
const failureDetails = await waitForFailureDetails(
|
|
479
|
+
signalPaths.failedSignalPath,
|
|
480
|
+
);
|
|
379
481
|
return {
|
|
380
482
|
status: "failed",
|
|
381
483
|
message: failureDetails?.message,
|
|
@@ -384,17 +486,26 @@ async function waitForWorkflowOutcome(
|
|
|
384
486
|
}
|
|
385
487
|
|
|
386
488
|
if (existsSync(signalPaths.completedSignalPath)) {
|
|
387
|
-
outputOffset = streamOutputSince(
|
|
489
|
+
outputOffset = streamOutputSince(
|
|
490
|
+
signalPaths.outputSignalPath,
|
|
491
|
+
outputOffset,
|
|
492
|
+
);
|
|
388
493
|
return { status: "completed" };
|
|
389
494
|
}
|
|
390
495
|
|
|
391
496
|
if (existsSync(signalPaths.pausedSignalPath)) {
|
|
392
|
-
outputOffset = streamOutputSince(
|
|
497
|
+
outputOffset = streamOutputSince(
|
|
498
|
+
signalPaths.outputSignalPath,
|
|
499
|
+
outputOffset,
|
|
500
|
+
);
|
|
393
501
|
return { status: "paused" };
|
|
394
502
|
}
|
|
395
503
|
|
|
396
504
|
if (!isProcessRunning(args.pid)) {
|
|
397
|
-
outputOffset = streamOutputSince(
|
|
505
|
+
outputOffset = streamOutputSince(
|
|
506
|
+
signalPaths.outputSignalPath,
|
|
507
|
+
outputOffset,
|
|
508
|
+
);
|
|
398
509
|
return { status: "exited" };
|
|
399
510
|
}
|
|
400
511
|
|
|
@@ -421,9 +532,9 @@ async function runResume(
|
|
|
421
532
|
);
|
|
422
533
|
}
|
|
423
534
|
|
|
424
|
-
if (!isProcessRunning(sessionState.pid)) {
|
|
535
|
+
if (sessionState.pid == null || !isProcessRunning(sessionState.pid)) {
|
|
425
536
|
throw new Error(
|
|
426
|
-
`No active paused workflow found for session "${session}" (worker pid ${sessionState.pid} is not running).`,
|
|
537
|
+
`No active paused workflow found for session "${session}" (worker pid ${sessionState.pid ?? "unknown"} is not running).`,
|
|
427
538
|
);
|
|
428
539
|
}
|
|
429
540
|
|
|
@@ -451,7 +562,7 @@ async function runResume(
|
|
|
451
562
|
|
|
452
563
|
const outcome = await waitForWorkflowOutcome({
|
|
453
564
|
session,
|
|
454
|
-
pid: sessionState.pid
|
|
565
|
+
pid: sessionState.pid!,
|
|
455
566
|
});
|
|
456
567
|
|
|
457
568
|
if (outcome.status === "completed") {
|
|
@@ -494,23 +605,28 @@ async function runIntegrationFromFile(
|
|
|
494
605
|
);
|
|
495
606
|
const payload = JSON.stringify({
|
|
496
607
|
integrationPath: args.integrationPath,
|
|
497
|
-
|
|
608
|
+
workflowName: args.workflowName,
|
|
498
609
|
session: args.session,
|
|
499
610
|
params: args.params,
|
|
500
611
|
headless: args.headless,
|
|
501
612
|
visualize: args.visualize,
|
|
502
613
|
authProfileDomain: args.authProfileDomain,
|
|
614
|
+
viewport: args.viewport,
|
|
503
615
|
} satisfies RunIntegrationWorkerRequest);
|
|
504
|
-
const worker = spawn(
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
616
|
+
const worker = spawn(
|
|
617
|
+
process.execPath,
|
|
618
|
+
[
|
|
619
|
+
tsxCliPath,
|
|
620
|
+
...(args.tsconfigPath ? ["--tsconfig", args.tsconfigPath] : []),
|
|
621
|
+
workerEntryPath,
|
|
622
|
+
payload,
|
|
623
|
+
],
|
|
624
|
+
{
|
|
625
|
+
detached: true,
|
|
626
|
+
stdio: "ignore",
|
|
627
|
+
env: process.env,
|
|
628
|
+
},
|
|
629
|
+
);
|
|
514
630
|
worker.unref();
|
|
515
631
|
const outcome = await waitForWorkflowOutcome({
|
|
516
632
|
session: args.session,
|
|
@@ -540,51 +656,65 @@ async function runIntegrationFromFile(
|
|
|
540
656
|
console.log("Integration completed.");
|
|
541
657
|
}
|
|
542
658
|
|
|
659
|
+
function readStdinSync(): string | null {
|
|
660
|
+
if (process.stdin.isTTY === true) return null;
|
|
661
|
+
try {
|
|
662
|
+
const content = readFileSync(0, "utf8");
|
|
663
|
+
return content.trim().length > 0 ? content : null;
|
|
664
|
+
} catch {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
543
669
|
export const execInput = SimpleCLI.input({
|
|
544
670
|
positionals: [
|
|
545
|
-
SimpleCLI.positional("
|
|
671
|
+
SimpleCLI.positional("code", z.string().optional(), {
|
|
546
672
|
help: "Playwright TypeScript code to execute",
|
|
547
|
-
variadic: true,
|
|
548
673
|
}),
|
|
549
674
|
],
|
|
550
675
|
named: {
|
|
551
676
|
session: sessionOption(),
|
|
552
|
-
visualize: SimpleCLI.flag({
|
|
677
|
+
visualize: SimpleCLI.flag({
|
|
678
|
+
help: "Enable ghost cursor + highlight visualization",
|
|
679
|
+
}),
|
|
553
680
|
page: pageOption(),
|
|
554
681
|
},
|
|
555
682
|
}).refine(
|
|
556
|
-
(input) => input.
|
|
557
|
-
`Usage: libretto exec <code> [--session <name>] [--visualize]`,
|
|
683
|
+
(input) => input.code !== undefined,
|
|
684
|
+
`Usage: libretto exec <code|-> [--session <name>] [--visualize]\n echo '<code>' | libretto exec - [--session <name>] [--visualize]`,
|
|
558
685
|
);
|
|
559
686
|
|
|
560
|
-
export
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
.
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
logger,
|
|
572
|
-
input.visualize,
|
|
573
|
-
input.page,
|
|
687
|
+
export const execCommand = SimpleCLI.command({
|
|
688
|
+
description: "Execute Playwright TypeScript code",
|
|
689
|
+
})
|
|
690
|
+
.input(execInput)
|
|
691
|
+
.use(withRequiredSession())
|
|
692
|
+
.handle(async ({ input, ctx }) => {
|
|
693
|
+
const code = input.code!;
|
|
694
|
+
const codeFromArgsOrStdin = code === "-" ? readStdinSync() : code;
|
|
695
|
+
if (codeFromArgsOrStdin === null) {
|
|
696
|
+
throw new Error(
|
|
697
|
+
"Missing stdin input for `exec -`. Pipe Playwright code into stdin.",
|
|
574
698
|
);
|
|
575
|
-
}
|
|
576
|
-
|
|
699
|
+
}
|
|
700
|
+
await runExec(
|
|
701
|
+
codeFromArgsOrStdin,
|
|
702
|
+
ctx.session,
|
|
703
|
+
ctx.logger,
|
|
704
|
+
input.visualize,
|
|
705
|
+
input.page,
|
|
706
|
+
);
|
|
707
|
+
});
|
|
577
708
|
|
|
578
|
-
const runUsage =
|
|
579
|
-
`Usage: libretto run <integrationFile> <integrationExport> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--no-visualize]`;
|
|
709
|
+
const runUsage = `Usage: libretto run <integrationFile> <workflowName> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--no-visualize] [--viewport WxH]`;
|
|
580
710
|
|
|
581
711
|
export const runInput = SimpleCLI.input({
|
|
582
712
|
positionals: [
|
|
583
713
|
SimpleCLI.positional("integrationFile", z.string().optional(), {
|
|
584
714
|
help: "Path to the integration file",
|
|
585
715
|
}),
|
|
586
|
-
SimpleCLI.positional("
|
|
587
|
-
help: "
|
|
716
|
+
SimpleCLI.positional("workflowName", z.string().optional(), {
|
|
717
|
+
help: "Workflow name to run (from workflow(name, handler))",
|
|
588
718
|
}),
|
|
589
719
|
],
|
|
590
720
|
named: {
|
|
@@ -609,14 +739,23 @@ export const runInput = SimpleCLI.input({
|
|
|
609
739
|
name: "auth-profile",
|
|
610
740
|
help: "Domain for local auth profile (e.g. apps.example.com)",
|
|
611
741
|
}),
|
|
742
|
+
viewport: SimpleCLI.option(z.string().optional(), {
|
|
743
|
+
help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)",
|
|
744
|
+
}),
|
|
612
745
|
},
|
|
613
746
|
})
|
|
614
747
|
.refine(
|
|
615
|
-
(input) => Boolean(input.integrationFile && input.
|
|
748
|
+
(input) => Boolean(input.integrationFile && input.workflowName),
|
|
616
749
|
runUsage,
|
|
617
750
|
)
|
|
618
|
-
.refine(
|
|
619
|
-
|
|
751
|
+
.refine(
|
|
752
|
+
(input) => !(input.params && input.paramsFile),
|
|
753
|
+
"Pass either --params or --params-file, not both.",
|
|
754
|
+
)
|
|
755
|
+
.refine(
|
|
756
|
+
(input) => !(input.headed && input.headless),
|
|
757
|
+
"Cannot pass both --headed and --headless.",
|
|
758
|
+
);
|
|
620
759
|
|
|
621
760
|
function resolveRunParams(
|
|
622
761
|
rawInlineParams: string | undefined,
|
|
@@ -639,36 +778,42 @@ function resolveRunParams(
|
|
|
639
778
|
return {};
|
|
640
779
|
}
|
|
641
780
|
|
|
642
|
-
export
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
781
|
+
export const runCommand = SimpleCLI.command({
|
|
782
|
+
description: "Run an exported Libretto workflow from a file",
|
|
783
|
+
})
|
|
784
|
+
.input(runInput)
|
|
785
|
+
.use(withAutoSession())
|
|
786
|
+
.handle(async ({ input, ctx }) => {
|
|
787
|
+
await stopExistingFailedRunSession(ctx.session, ctx.logger);
|
|
788
|
+
assertSessionAvailableForStart(ctx.session, ctx.logger);
|
|
789
|
+
|
|
790
|
+
const params = resolveRunParams(input.params, input.paramsFile);
|
|
791
|
+
const headlessMode = input.headed
|
|
792
|
+
? false
|
|
793
|
+
: input.headless
|
|
794
|
+
? true
|
|
795
|
+
: undefined;
|
|
796
|
+
const visualize = !input.noVisualize;
|
|
797
|
+
const viewport = resolveViewport(
|
|
798
|
+
parseViewportArg(input.viewport),
|
|
799
|
+
ctx.logger,
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
await runIntegrationFromFile(
|
|
803
|
+
{
|
|
661
804
|
integrationPath: input.integrationFile!,
|
|
662
|
-
|
|
805
|
+
workflowName: input.workflowName!,
|
|
663
806
|
session: ctx.session,
|
|
664
807
|
params,
|
|
665
808
|
tsconfigPath: input.tsconfig,
|
|
666
809
|
headless: headlessMode ?? false,
|
|
667
810
|
visualize,
|
|
668
811
|
authProfileDomain: input.authProfile,
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
812
|
+
viewport,
|
|
813
|
+
},
|
|
814
|
+
ctx.logger,
|
|
815
|
+
);
|
|
816
|
+
});
|
|
672
817
|
|
|
673
818
|
export const resumeInput = SimpleCLI.input({
|
|
674
819
|
positionals: [],
|
|
@@ -677,22 +822,17 @@ export const resumeInput = SimpleCLI.input({
|
|
|
677
822
|
},
|
|
678
823
|
});
|
|
679
824
|
|
|
680
|
-
export
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
.
|
|
687
|
-
|
|
688
|
-
await runResume(ctx.session, logger, ctx.sessionState);
|
|
689
|
-
});
|
|
690
|
-
}
|
|
825
|
+
export const resumeCommand = SimpleCLI.command({
|
|
826
|
+
description: "Resume a paused workflow for the current session",
|
|
827
|
+
})
|
|
828
|
+
.input(resumeInput)
|
|
829
|
+
.use(withRequiredSession())
|
|
830
|
+
.handle(async ({ ctx }) => {
|
|
831
|
+
await runResume(ctx.session, ctx.logger, ctx.sessionState);
|
|
832
|
+
});
|
|
691
833
|
|
|
692
|
-
export
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
};
|
|
698
|
-
}
|
|
834
|
+
export const executionCommands = {
|
|
835
|
+
exec: execCommand,
|
|
836
|
+
run: runCommand,
|
|
837
|
+
resume: resumeCommand,
|
|
838
|
+
};
|