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
package/dist/cli/core/browser.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
chromium
|
|
3
|
+
} from "playwright";
|
|
2
4
|
import { openSync, existsSync } from "node:fs";
|
|
3
5
|
import { dirname, join, resolve } from "node:path";
|
|
4
6
|
import { fileURLToPath } from "node:url";
|
|
5
7
|
import { createServer } from "node:net";
|
|
6
8
|
import { spawn } from "node:child_process";
|
|
9
|
+
import {
|
|
10
|
+
filterSemanticClasses,
|
|
11
|
+
INTERACTIVE_ROLE_NAMES,
|
|
12
|
+
INTERACTIVE_TAG_NAMES,
|
|
13
|
+
isObfuscatedClass,
|
|
14
|
+
TEST_ATTRIBUTE_NAMES,
|
|
15
|
+
TRUSTED_ATTRIBUTE_NAMES
|
|
16
|
+
} from "../../shared/dom-semantics.js";
|
|
7
17
|
import {
|
|
8
18
|
getSessionActionsLogPath,
|
|
9
19
|
getSessionNetworkLogPath,
|
|
@@ -57,9 +67,8 @@ function getProfilePath(domain) {
|
|
|
57
67
|
function hasProfile(domain) {
|
|
58
68
|
return existsSync(getProfilePath(domain));
|
|
59
69
|
}
|
|
60
|
-
async function
|
|
61
|
-
|
|
62
|
-
logger.info("cdp-connect-attempt", { port, endpoint, timeoutMs });
|
|
70
|
+
async function tryConnectToCDP(endpoint, logger, timeoutMs = 5e3) {
|
|
71
|
+
logger.info("cdp-connect-attempt", { endpoint, timeoutMs });
|
|
63
72
|
try {
|
|
64
73
|
const connectPromise = chromium.connectOverCDP(endpoint);
|
|
65
74
|
const timeoutPromise = new Promise(
|
|
@@ -68,16 +77,15 @@ async function tryConnectToPort(port, logger, timeoutMs = 5e3) {
|
|
|
68
77
|
const browser = await Promise.race([connectPromise, timeoutPromise]);
|
|
69
78
|
if (browser) {
|
|
70
79
|
logger.info("cdp-connect-success", {
|
|
71
|
-
port,
|
|
72
80
|
endpoint,
|
|
73
81
|
contexts: browser.contexts().length
|
|
74
82
|
});
|
|
75
83
|
} else {
|
|
76
|
-
logger.warn("cdp-connect-timeout", {
|
|
84
|
+
logger.warn("cdp-connect-timeout", { endpoint, timeoutMs });
|
|
77
85
|
}
|
|
78
86
|
return browser;
|
|
79
87
|
} catch (err) {
|
|
80
|
-
logger.error("cdp-connect-error", { error: err,
|
|
88
|
+
logger.error("cdp-connect-error", { error: err, endpoint });
|
|
81
89
|
return null;
|
|
82
90
|
}
|
|
83
91
|
}
|
|
@@ -102,7 +110,9 @@ async function resolvePageId(page) {
|
|
|
102
110
|
const targetInfo = await cdpSession.send("Target.getTargetInfo");
|
|
103
111
|
const targetId = targetInfo?.targetInfo?.targetId;
|
|
104
112
|
if (typeof targetId !== "string" || targetId.length === 0) {
|
|
105
|
-
throw new Error(
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Could not resolve target id for page at URL "${page.url()}".`
|
|
115
|
+
);
|
|
106
116
|
}
|
|
107
117
|
return targetId;
|
|
108
118
|
} finally {
|
|
@@ -135,21 +145,22 @@ async function listOpenPages(session, logger) {
|
|
|
135
145
|
async function connect(session, logger, timeoutMs = 1e4, options) {
|
|
136
146
|
logger.info("connect", { session, timeoutMs });
|
|
137
147
|
const state = readSessionStateOrThrow(session);
|
|
138
|
-
const
|
|
148
|
+
const endpoint = state.cdpEndpoint ?? `http://localhost:${state.port}`;
|
|
149
|
+
const browser = await tryConnectToCDP(endpoint, logger, timeoutMs);
|
|
139
150
|
if (!browser) {
|
|
140
151
|
logger.error("connect-no-browser", {
|
|
141
152
|
session,
|
|
142
|
-
|
|
153
|
+
endpoint,
|
|
143
154
|
pid: state.pid
|
|
144
155
|
});
|
|
145
|
-
if (!isPidRunning(state.pid)) {
|
|
156
|
+
if (state.pid == null || !isPidRunning(state.pid)) {
|
|
146
157
|
clearSessionState(session, logger);
|
|
147
158
|
throw new Error(
|
|
148
159
|
`No browser running for session "${session}". Run 'libretto open <url> --session ${session}' first.`
|
|
149
160
|
);
|
|
150
161
|
}
|
|
151
162
|
throw new Error(
|
|
152
|
-
`Could not connect to the browser for session "${session}" at
|
|
163
|
+
`Could not connect to the browser for session "${session}" at ${endpoint}, but the session process (pid ${state.pid}) is still running. Try the command again, or close and reopen the session if it stays stuck.`
|
|
153
164
|
);
|
|
154
165
|
}
|
|
155
166
|
const contexts = browser.contexts();
|
|
@@ -230,16 +241,34 @@ function resolveViewport(cliViewport, logger) {
|
|
|
230
241
|
}
|
|
231
242
|
const config = readLibrettoConfig();
|
|
232
243
|
if (config.viewport) {
|
|
233
|
-
logger.info("viewport-source", {
|
|
244
|
+
logger.info("viewport-source", {
|
|
245
|
+
source: "config",
|
|
246
|
+
viewport: config.viewport
|
|
247
|
+
});
|
|
234
248
|
return config.viewport;
|
|
235
249
|
}
|
|
236
|
-
logger.info("viewport-source", {
|
|
250
|
+
logger.info("viewport-source", {
|
|
251
|
+
source: "default",
|
|
252
|
+
viewport: DEFAULT_VIEWPORT
|
|
253
|
+
});
|
|
237
254
|
return DEFAULT_VIEWPORT;
|
|
238
255
|
}
|
|
256
|
+
function resolveWindowPosition(logger) {
|
|
257
|
+
const config = readLibrettoConfig();
|
|
258
|
+
if (config.windowPosition) {
|
|
259
|
+
logger.info("window-position-source", {
|
|
260
|
+
source: "config",
|
|
261
|
+
windowPosition: config.windowPosition
|
|
262
|
+
});
|
|
263
|
+
return config.windowPosition;
|
|
264
|
+
}
|
|
265
|
+
return void 0;
|
|
266
|
+
}
|
|
239
267
|
async function runOpen(rawUrl, headed, session, logger, options) {
|
|
240
268
|
const url = normalizeUrl(rawUrl);
|
|
241
269
|
const viewport = resolveViewport(options?.viewport, logger);
|
|
242
|
-
|
|
270
|
+
const windowPosition = headed ? resolveWindowPosition(logger) : void 0;
|
|
271
|
+
logger.info("open-start", { url, headed, session, viewport, windowPosition });
|
|
243
272
|
assertSessionAvailableForStart(session, logger);
|
|
244
273
|
const port = await pickFreePort();
|
|
245
274
|
const runLogPath = logFileForSession(session);
|
|
@@ -268,6 +297,45 @@ async function runOpen(rawUrl, headed, session, logger, options) {
|
|
|
268
297
|
const escapedLogPath = runLogPath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
269
298
|
const escapedNetworkLogPath = networkLogPath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
270
299
|
const escapedActionsLogPath = actionsLogPath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
300
|
+
const windowPositionArg = windowPosition ? `, '--window-position=${windowPosition.x},${windowPosition.y}'` : "";
|
|
301
|
+
const windowBoundsSetupCode = windowPosition ? `
|
|
302
|
+
const requestedWindowBounds = { left: ${windowPosition.x}, top: ${windowPosition.y}, windowState: 'normal' };
|
|
303
|
+
const pageCdp = await context.newCDPSession(page);
|
|
304
|
+
let browserCdp;
|
|
305
|
+
try {
|
|
306
|
+
const targetInfo = await pageCdp.send('Target.getTargetInfo');
|
|
307
|
+
const targetId = targetInfo?.targetInfo?.targetId;
|
|
308
|
+
browserCdp = await browser.newBrowserCDPSession();
|
|
309
|
+
const windowResult = await browserCdp.send(
|
|
310
|
+
'Browser.getWindowForTarget',
|
|
311
|
+
targetId ? { targetId } : {},
|
|
312
|
+
);
|
|
313
|
+
await browserCdp.send('Browser.setWindowBounds', {
|
|
314
|
+
windowId: windowResult.windowId,
|
|
315
|
+
bounds: requestedWindowBounds,
|
|
316
|
+
});
|
|
317
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
318
|
+
const actualWindow = await browserCdp.send('Browser.getWindowBounds', {
|
|
319
|
+
windowId: windowResult.windowId,
|
|
320
|
+
});
|
|
321
|
+
childLog('info', 'window-bounds-set', {
|
|
322
|
+
windowId: windowResult.windowId,
|
|
323
|
+
requestedBounds: requestedWindowBounds,
|
|
324
|
+
actualBounds: actualWindow.bounds,
|
|
325
|
+
});
|
|
326
|
+
} catch (error) {
|
|
327
|
+
childLog('warn', 'window-bounds-set-failed', {
|
|
328
|
+
requestedBounds: requestedWindowBounds,
|
|
329
|
+
message: error instanceof Error ? error.message : String(error),
|
|
330
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
331
|
+
});
|
|
332
|
+
} finally {
|
|
333
|
+
await pageCdp.detach().catch(() => {});
|
|
334
|
+
if (browserCdp) {
|
|
335
|
+
await browserCdp.detach().catch(() => {});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
` : "";
|
|
271
339
|
const launcherCode = `
|
|
272
340
|
import { chromium } from 'playwright';
|
|
273
341
|
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
@@ -279,14 +347,21 @@ const ACTIONS_LOG = '${escapedActionsLogPath}';
|
|
|
279
347
|
mkdirSync(dirname(NETWORK_LOG), { recursive: true });
|
|
280
348
|
|
|
281
349
|
// tsx/esbuild may emit __name() wrappers in Function#toString output.
|
|
282
|
-
const __name = (target, value) =>
|
|
283
|
-
|
|
350
|
+
const __name = (target, value) =>
|
|
351
|
+
Object.defineProperty(target, 'name', { value, configurable: true });
|
|
284
352
|
|
|
285
|
-
${
|
|
353
|
+
const TEST_ATTRIBUTE_NAMES = ${JSON.stringify([...TEST_ATTRIBUTE_NAMES])};
|
|
354
|
+
const TRUSTED_ATTRIBUTE_NAMES = ${JSON.stringify([...TRUSTED_ATTRIBUTE_NAMES])};
|
|
355
|
+
const INTERACTIVE_TAG_NAMES = ${JSON.stringify([...INTERACTIVE_TAG_NAMES])};
|
|
356
|
+
const INTERACTIVE_ROLE_NAMES = ${JSON.stringify([...INTERACTIVE_ROLE_NAMES])};
|
|
357
|
+
const filterSemanticClasses = ${filterSemanticClasses.toString()};
|
|
358
|
+
const isObfuscatedClass = ${isObfuscatedClass.toString()};
|
|
286
359
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
360
|
+
${installSessionTelemetry.toString()}
|
|
361
|
+
|
|
362
|
+
function logAction(entry) {
|
|
363
|
+
appendFileSync(ACTIONS_LOG, JSON.stringify(entry) + '\\n');
|
|
364
|
+
}
|
|
290
365
|
|
|
291
366
|
function logNetwork(entry) {
|
|
292
367
|
appendFileSync(NETWORK_LOG, JSON.stringify(entry) + '\\n');
|
|
@@ -308,7 +383,7 @@ function childLog(level, event, data = {}) {
|
|
|
308
383
|
|
|
309
384
|
const browser = await chromium.launch({
|
|
310
385
|
headless: ${!headed},
|
|
311
|
-
args: ['--disable-blink-features=AutomationControlled', '--remote-debugging-port=${port}', '--remote-debugging-address=127.0.0.1', '--no-focus-on-check'],
|
|
386
|
+
args: ['--disable-blink-features=AutomationControlled', '--remote-debugging-port=${port}', '--remote-debugging-address=127.0.0.1', '--no-focus-on-check'${windowPositionArg}],
|
|
312
387
|
});
|
|
313
388
|
|
|
314
389
|
browser.on('disconnected', () => {
|
|
@@ -322,6 +397,7 @@ const context = await browser.newContext({
|
|
|
322
397
|
});
|
|
323
398
|
|
|
324
399
|
const page = await context.newPage();
|
|
400
|
+
${windowBoundsSetupCode}
|
|
325
401
|
page.setDefaultTimeout(30000);
|
|
326
402
|
page.setDefaultNavigationTimeout(45000);
|
|
327
403
|
|
|
@@ -414,14 +490,17 @@ await new Promise(() => {});
|
|
|
414
490
|
logger.info("open-waiting-for-cdp", { attempt: i, port, session });
|
|
415
491
|
}
|
|
416
492
|
if (ready) {
|
|
417
|
-
writeSessionState(
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
493
|
+
writeSessionState(
|
|
494
|
+
{
|
|
495
|
+
port,
|
|
496
|
+
pid: child.pid,
|
|
497
|
+
session,
|
|
498
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
499
|
+
status: "active",
|
|
500
|
+
viewport
|
|
501
|
+
},
|
|
502
|
+
logger
|
|
503
|
+
);
|
|
425
504
|
logger.info("open-success", {
|
|
426
505
|
url,
|
|
427
506
|
mode: browserMode,
|
|
@@ -517,8 +596,10 @@ async function runClose(session, logger) {
|
|
|
517
596
|
return;
|
|
518
597
|
}
|
|
519
598
|
logger.info("close-killing", { session, pid: state.pid, port: state.port });
|
|
520
|
-
|
|
521
|
-
|
|
599
|
+
if (state.pid != null) {
|
|
600
|
+
sendSignalToProcessGroupOrPid(state.pid, "SIGTERM", logger, session);
|
|
601
|
+
await waitForCloseSignalWindow(CLOSE_WAIT_MS);
|
|
602
|
+
}
|
|
522
603
|
clearSessionState(session, logger);
|
|
523
604
|
logger.info("close-success", { session });
|
|
524
605
|
console.log(`Browser closed (session: ${session}).`);
|
|
@@ -575,7 +656,7 @@ function resolveClosableSessions(logger) {
|
|
|
575
656
|
function clearStoppedSessionStates(sessions, logger) {
|
|
576
657
|
let cleared = 0;
|
|
577
658
|
for (const session of sessions) {
|
|
578
|
-
if (!isPidRunning(session.pid)) {
|
|
659
|
+
if (session.pid == null || !isPidRunning(session.pid)) {
|
|
579
660
|
clearSessionState(session.session, logger);
|
|
580
661
|
cleared += 1;
|
|
581
662
|
}
|
|
@@ -601,10 +682,19 @@ async function runCloseAll(logger, options) {
|
|
|
601
682
|
pid: target.pid,
|
|
602
683
|
port: target.port
|
|
603
684
|
});
|
|
604
|
-
|
|
685
|
+
if (target.pid != null) {
|
|
686
|
+
sendSignalToProcessGroupOrPid(
|
|
687
|
+
target.pid,
|
|
688
|
+
"SIGTERM",
|
|
689
|
+
logger,
|
|
690
|
+
target.session
|
|
691
|
+
);
|
|
692
|
+
}
|
|
605
693
|
}
|
|
606
694
|
await waitForCloseSignalWindow(CLOSE_WAIT_MS);
|
|
607
|
-
let survivors = closable.filter(
|
|
695
|
+
let survivors = closable.filter(
|
|
696
|
+
(target) => target.pid != null && isPidRunning(target.pid)
|
|
697
|
+
);
|
|
608
698
|
if (survivors.length > 0 && !force) {
|
|
609
699
|
const closed = clearStoppedSessionStates(closable, logger);
|
|
610
700
|
throw new Error(
|
|
@@ -622,11 +712,20 @@ async function runCloseAll(logger, options) {
|
|
|
622
712
|
session: survivor.session,
|
|
623
713
|
pid: survivor.pid
|
|
624
714
|
});
|
|
625
|
-
|
|
715
|
+
if (survivor.pid != null) {
|
|
716
|
+
sendSignalToProcessGroupOrPid(
|
|
717
|
+
survivor.pid,
|
|
718
|
+
"SIGKILL",
|
|
719
|
+
logger,
|
|
720
|
+
survivor.session
|
|
721
|
+
);
|
|
722
|
+
}
|
|
626
723
|
forceKilled += 1;
|
|
627
724
|
}
|
|
628
725
|
await waitForCloseSignalWindow(FORCE_CLOSE_WAIT_MS);
|
|
629
|
-
survivors = survivors.filter(
|
|
726
|
+
survivors = survivors.filter(
|
|
727
|
+
(target) => target.pid != null && isPidRunning(target.pid)
|
|
728
|
+
);
|
|
630
729
|
if (survivors.length > 0) {
|
|
631
730
|
const closed = clearStoppedSessionStates(closable, logger);
|
|
632
731
|
throw new Error(
|
|
@@ -648,6 +747,75 @@ async function runCloseAll(logger, options) {
|
|
|
648
747
|
console.log(`Force-killed ${forceKilled} session(s).`);
|
|
649
748
|
}
|
|
650
749
|
}
|
|
750
|
+
async function runConnect(cdpUrl, session, logger) {
|
|
751
|
+
logger.info("connect-start", { cdpUrl, session });
|
|
752
|
+
assertSessionAvailableForStart(session, logger);
|
|
753
|
+
let parsedUrl;
|
|
754
|
+
try {
|
|
755
|
+
parsedUrl = new URL(cdpUrl);
|
|
756
|
+
} catch {
|
|
757
|
+
throw new Error(
|
|
758
|
+
[
|
|
759
|
+
`Invalid CDP URL: ${cdpUrl}`,
|
|
760
|
+
``,
|
|
761
|
+
`Expected an HTTP URL pointing to a Chrome DevTools Protocol endpoint, for example:`,
|
|
762
|
+
` libretto connect http://127.0.0.1:9222`,
|
|
763
|
+
` libretto connect http://remote-host:9222`,
|
|
764
|
+
` libretto connect http://remote-host:9222/devtools/browser/<id>`
|
|
765
|
+
].join("\n")
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
const endpoint = parsedUrl.href;
|
|
769
|
+
const port = parsedUrl.port ? Number(parsedUrl.port) : parsedUrl.protocol === "https:" ? 443 : 80;
|
|
770
|
+
console.log(
|
|
771
|
+
`Connecting to CDP endpoint at ${endpoint} (session: ${session})...`
|
|
772
|
+
);
|
|
773
|
+
const versionUrl = `${parsedUrl.protocol}//${parsedUrl.host}/json/version`;
|
|
774
|
+
try {
|
|
775
|
+
const resp = await fetch(versionUrl);
|
|
776
|
+
const versionInfo = await resp.json();
|
|
777
|
+
logger.info("connect-version-ok", { versionUrl, versionInfo });
|
|
778
|
+
} catch (err) {
|
|
779
|
+
logger.error("connect-version-failed", { versionUrl, error: err });
|
|
780
|
+
throw new Error(
|
|
781
|
+
`Cannot reach CDP endpoint at ${versionUrl}. Make sure the target is running and accessible at ${parsedUrl.host}.`
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
const browser = await tryConnectToCDP(endpoint, logger, 1e4);
|
|
785
|
+
if (!browser) {
|
|
786
|
+
throw new Error(
|
|
787
|
+
`CDP endpoint at ${endpoint} is reachable but Playwright could not connect. Check that the URL is a Chrome DevTools Protocol endpoint.`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
const pages = resolveOperationalPages(browser);
|
|
791
|
+
logger.info("connect-pages", {
|
|
792
|
+
session,
|
|
793
|
+
pageCount: pages.length,
|
|
794
|
+
urls: pages.map((p) => p.url())
|
|
795
|
+
});
|
|
796
|
+
disconnectBrowser(browser, logger, session);
|
|
797
|
+
writeSessionState(
|
|
798
|
+
{
|
|
799
|
+
port,
|
|
800
|
+
cdpEndpoint: endpoint,
|
|
801
|
+
session,
|
|
802
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
803
|
+
status: "active"
|
|
804
|
+
},
|
|
805
|
+
logger
|
|
806
|
+
);
|
|
807
|
+
logger.info("connect-success", { cdpUrl: endpoint, session, port });
|
|
808
|
+
console.log(`Connected to ${endpoint} (session: ${session})`);
|
|
809
|
+
console.log(` Pages found: ${pages.length}`);
|
|
810
|
+
if (pages.length > 0) {
|
|
811
|
+
for (const p of pages.slice(0, 5)) {
|
|
812
|
+
console.log(` ${p.url()}`);
|
|
813
|
+
}
|
|
814
|
+
if (pages.length > 5) {
|
|
815
|
+
console.log(` ... and ${pages.length - 5} more`);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
651
819
|
function resolvePath(filePath) {
|
|
652
820
|
return join(process.cwd(), filePath);
|
|
653
821
|
}
|
|
@@ -666,8 +834,10 @@ export {
|
|
|
666
834
|
normalizeDomain,
|
|
667
835
|
normalizeUrl,
|
|
668
836
|
resolvePath,
|
|
837
|
+
resolveViewport,
|
|
669
838
|
runClose,
|
|
670
839
|
runCloseAll,
|
|
840
|
+
runConnect,
|
|
671
841
|
runOpen,
|
|
672
842
|
runPages,
|
|
673
843
|
runSave
|
package/dist/cli/core/context.js
CHANGED
|
@@ -49,7 +49,10 @@ function createLoggerForSession(session) {
|
|
|
49
49
|
const sessionDir = getSessionDir(session);
|
|
50
50
|
mkdirSync(sessionDir, { recursive: true });
|
|
51
51
|
const logFilePath = getSessionLogsPath(session);
|
|
52
|
-
return new Logger(
|
|
52
|
+
return new Logger(
|
|
53
|
+
["libretto"],
|
|
54
|
+
[createFileLogSink({ filePath: logFilePath })]
|
|
55
|
+
);
|
|
53
56
|
}
|
|
54
57
|
async function closeLogger(logger) {
|
|
55
58
|
if (!logger) return;
|