libretto 0.1.5 → 0.2.0

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.
Files changed (183) hide show
  1. package/README.md +213 -17
  2. package/bin/libretto.mjs +18 -0
  3. package/dist/cli/cli.js +201 -0
  4. package/dist/cli/commands/ai.js +21 -0
  5. package/dist/cli/commands/browser.js +56 -0
  6. package/dist/cli/commands/execution.js +407 -0
  7. package/dist/cli/commands/logs.js +65 -0
  8. package/dist/cli/commands/snapshot.js +99 -0
  9. package/dist/cli/core/ai-config.js +149 -0
  10. package/dist/cli/core/browser.js +687 -0
  11. package/dist/cli/core/context.js +113 -0
  12. package/dist/cli/core/pause-signals.js +29 -0
  13. package/dist/cli/core/session.js +183 -0
  14. package/dist/cli/core/snapshot-analyzer.js +492 -0
  15. package/dist/cli/core/telemetry.js +350 -0
  16. package/dist/cli/index.js +13 -0
  17. package/dist/cli/workers/run-integration-runtime.js +204 -0
  18. package/dist/cli/workers/run-integration-worker-protocol.js +0 -0
  19. package/dist/cli/workers/run-integration-worker.js +83 -0
  20. package/dist/index.cjs +127 -0
  21. package/dist/index.d.cts +22 -0
  22. package/dist/index.d.ts +22 -0
  23. package/dist/index.js +110 -0
  24. package/dist/runtime/download/download.cjs +70 -0
  25. package/dist/runtime/download/download.d.cts +35 -0
  26. package/dist/runtime/download/download.d.ts +35 -0
  27. package/dist/runtime/download/download.js +45 -0
  28. package/dist/runtime/download/index.cjs +30 -0
  29. package/dist/runtime/download/index.d.cts +3 -0
  30. package/dist/runtime/download/index.d.ts +3 -0
  31. package/dist/runtime/download/index.js +8 -0
  32. package/dist/runtime/extract/extract.cjs +87 -0
  33. package/dist/runtime/extract/extract.d.cts +23 -0
  34. package/dist/runtime/extract/extract.d.ts +23 -0
  35. package/dist/runtime/extract/extract.js +63 -0
  36. package/dist/runtime/extract/index.cjs +28 -0
  37. package/dist/runtime/extract/index.d.cts +5 -0
  38. package/dist/runtime/extract/index.d.ts +5 -0
  39. package/dist/runtime/extract/index.js +4 -0
  40. package/dist/runtime/network/index.cjs +28 -0
  41. package/dist/runtime/network/index.d.cts +4 -0
  42. package/dist/runtime/network/index.d.ts +4 -0
  43. package/dist/runtime/network/index.js +6 -0
  44. package/dist/runtime/network/network.cjs +91 -0
  45. package/dist/runtime/network/network.d.cts +28 -0
  46. package/dist/runtime/network/network.d.ts +28 -0
  47. package/dist/runtime/network/network.js +67 -0
  48. package/dist/runtime/recovery/agent.cjs +218 -0
  49. package/dist/runtime/recovery/agent.d.cts +13 -0
  50. package/dist/runtime/recovery/agent.d.ts +13 -0
  51. package/dist/runtime/recovery/agent.js +194 -0
  52. package/dist/runtime/recovery/errors.cjs +122 -0
  53. package/dist/runtime/recovery/errors.d.cts +31 -0
  54. package/dist/runtime/recovery/errors.d.ts +31 -0
  55. package/dist/runtime/recovery/errors.js +98 -0
  56. package/dist/runtime/recovery/index.cjs +34 -0
  57. package/dist/runtime/recovery/index.d.cts +7 -0
  58. package/dist/runtime/recovery/index.d.ts +7 -0
  59. package/dist/runtime/recovery/index.js +10 -0
  60. package/dist/runtime/recovery/recovery.cjs +53 -0
  61. package/dist/runtime/recovery/recovery.d.cts +12 -0
  62. package/dist/runtime/recovery/recovery.d.ts +12 -0
  63. package/dist/runtime/recovery/recovery.js +29 -0
  64. package/dist/runtime/step/index.cjs +31 -0
  65. package/dist/runtime/step/index.d.cts +7 -0
  66. package/dist/runtime/step/index.d.ts +7 -0
  67. package/dist/runtime/step/index.js +6 -0
  68. package/dist/runtime/step/runner.cjs +208 -0
  69. package/dist/runtime/step/runner.d.cts +16 -0
  70. package/dist/runtime/step/runner.d.ts +16 -0
  71. package/dist/runtime/step/runner.js +187 -0
  72. package/dist/runtime/step/step.cjs +67 -0
  73. package/dist/runtime/step/step.d.cts +23 -0
  74. package/dist/runtime/step/step.d.ts +23 -0
  75. package/dist/runtime/step/step.js +43 -0
  76. package/dist/runtime/step/types.cjs +16 -0
  77. package/dist/runtime/step/types.d.cts +72 -0
  78. package/dist/runtime/step/types.d.ts +72 -0
  79. package/dist/runtime/step/types.js +0 -0
  80. package/dist/shared/config/config.cjs +44 -0
  81. package/dist/shared/config/config.d.cts +10 -0
  82. package/dist/shared/config/config.d.ts +10 -0
  83. package/dist/shared/config/config.js +18 -0
  84. package/dist/shared/config/index.cjs +32 -0
  85. package/dist/shared/config/index.d.cts +1 -0
  86. package/dist/shared/config/index.d.ts +1 -0
  87. package/dist/shared/config/index.js +10 -0
  88. package/dist/shared/debug/index.cjs +32 -0
  89. package/dist/shared/debug/index.d.cts +2 -0
  90. package/dist/shared/debug/index.d.ts +2 -0
  91. package/dist/shared/debug/index.js +10 -0
  92. package/dist/shared/debug/pause.cjs +56 -0
  93. package/dist/shared/debug/pause.d.cts +23 -0
  94. package/dist/shared/debug/pause.d.ts +23 -0
  95. package/dist/shared/debug/pause.js +30 -0
  96. package/dist/shared/instrumentation/errors.cjs +81 -0
  97. package/dist/shared/instrumentation/errors.d.cts +12 -0
  98. package/dist/shared/instrumentation/errors.d.ts +12 -0
  99. package/dist/shared/instrumentation/errors.js +57 -0
  100. package/dist/shared/instrumentation/index.cjs +35 -0
  101. package/dist/shared/instrumentation/index.d.cts +6 -0
  102. package/dist/shared/instrumentation/index.d.ts +6 -0
  103. package/dist/shared/instrumentation/index.js +12 -0
  104. package/dist/shared/instrumentation/instrument.cjs +206 -0
  105. package/dist/shared/instrumentation/instrument.d.cts +32 -0
  106. package/dist/shared/instrumentation/instrument.d.ts +32 -0
  107. package/dist/shared/instrumentation/instrument.js +190 -0
  108. package/dist/shared/llm/client.cjs +139 -0
  109. package/dist/shared/llm/client.d.cts +6 -0
  110. package/dist/shared/llm/client.d.ts +6 -0
  111. package/dist/shared/llm/client.js +115 -0
  112. package/dist/shared/llm/index.cjs +28 -0
  113. package/dist/shared/llm/index.d.cts +3 -0
  114. package/dist/shared/llm/index.d.ts +3 -0
  115. package/dist/shared/llm/index.js +4 -0
  116. package/dist/shared/llm/types.cjs +16 -0
  117. package/dist/shared/llm/types.d.cts +34 -0
  118. package/dist/shared/llm/types.d.ts +34 -0
  119. package/dist/shared/llm/types.js +0 -0
  120. package/dist/shared/logger/index.cjs +35 -0
  121. package/dist/shared/logger/index.d.cts +2 -0
  122. package/dist/shared/logger/index.d.ts +2 -0
  123. package/dist/shared/logger/index.js +12 -0
  124. package/dist/shared/logger/logger.cjs +200 -0
  125. package/dist/shared/logger/logger.d.cts +70 -0
  126. package/dist/shared/logger/logger.d.ts +70 -0
  127. package/dist/shared/logger/logger.js +176 -0
  128. package/dist/shared/logger/sinks.cjs +160 -0
  129. package/dist/shared/logger/sinks.d.cts +9 -0
  130. package/dist/shared/logger/sinks.d.ts +9 -0
  131. package/dist/shared/logger/sinks.js +124 -0
  132. package/dist/shared/paths/paths.cjs +104 -0
  133. package/dist/shared/paths/paths.d.cts +10 -0
  134. package/dist/shared/paths/paths.d.ts +10 -0
  135. package/dist/shared/paths/paths.js +73 -0
  136. package/dist/shared/run/api.cjs +35 -0
  137. package/dist/shared/run/api.d.cts +3 -0
  138. package/dist/shared/run/api.d.ts +3 -0
  139. package/dist/shared/run/api.js +12 -0
  140. package/dist/shared/run/browser.cjs +98 -0
  141. package/dist/shared/run/browser.d.cts +22 -0
  142. package/dist/shared/run/browser.d.ts +22 -0
  143. package/dist/shared/run/browser.js +74 -0
  144. package/dist/shared/state/index.cjs +38 -0
  145. package/dist/shared/state/index.d.cts +2 -0
  146. package/dist/shared/state/index.d.ts +2 -0
  147. package/dist/shared/state/index.js +16 -0
  148. package/dist/shared/state/session-state.cjs +85 -0
  149. package/dist/shared/state/session-state.d.cts +34 -0
  150. package/dist/shared/state/session-state.d.ts +34 -0
  151. package/dist/shared/state/session-state.js +56 -0
  152. package/dist/shared/visualization/ghost-cursor.cjs +174 -0
  153. package/dist/shared/visualization/ghost-cursor.d.cts +37 -0
  154. package/dist/shared/visualization/ghost-cursor.d.ts +37 -0
  155. package/dist/shared/visualization/ghost-cursor.js +145 -0
  156. package/dist/shared/visualization/highlight.cjs +134 -0
  157. package/dist/shared/visualization/highlight.d.cts +22 -0
  158. package/dist/shared/visualization/highlight.d.ts +22 -0
  159. package/dist/shared/visualization/highlight.js +108 -0
  160. package/dist/shared/visualization/index.cjs +45 -0
  161. package/dist/shared/visualization/index.d.cts +3 -0
  162. package/dist/shared/visualization/index.d.ts +3 -0
  163. package/dist/shared/visualization/index.js +24 -0
  164. package/dist/shared/workflow/workflow.cjs +47 -0
  165. package/dist/shared/workflow/workflow.d.cts +33 -0
  166. package/dist/shared/workflow/workflow.d.ts +33 -0
  167. package/dist/shared/workflow/workflow.js +21 -0
  168. package/package.json +123 -26
  169. package/.npmignore +0 -2
  170. package/bin/libretto +0 -31
  171. package/lib/connect.js +0 -34
  172. package/lib/export.js +0 -224
  173. package/lib/import.js +0 -166
  174. package/lib/index.js +0 -8
  175. package/lib/log.js +0 -9
  176. package/lib/validate.js +0 -20
  177. package/makefile +0 -8
  178. package/src/connect.coffee +0 -25
  179. package/src/export.coffee +0 -222
  180. package/src/import.coffee +0 -166
  181. package/src/index.coffee +0 -3
  182. package/src/log.coffee +0 -3
  183. package/src/validate.coffee +0 -10
@@ -0,0 +1,687 @@
1
+ import { chromium } from "playwright";
2
+ import { openSync, existsSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { createServer } from "node:net";
6
+ import { spawn } from "node:child_process";
7
+ import {
8
+ getSessionActionsLogPath,
9
+ getSessionNetworkLogPath,
10
+ PROFILES_DIR
11
+ } from "./context.js";
12
+ import {
13
+ assertSessionAvailableForStart,
14
+ clearSessionState,
15
+ readSessionStateOrThrow,
16
+ logFileForSession,
17
+ readSessionState,
18
+ writeSessionState
19
+ } from "./session.js";
20
+ async function pickFreePort() {
21
+ return await new Promise((resolve2, reject) => {
22
+ const server = createServer();
23
+ server.listen(0, "127.0.0.1", () => {
24
+ const address = server.address();
25
+ if (!address || typeof address === "string") {
26
+ reject(new Error("Failed to pick free port"));
27
+ return;
28
+ }
29
+ const port = address.port;
30
+ server.close(() => resolve2(port));
31
+ });
32
+ server.on("error", reject);
33
+ });
34
+ }
35
+ function normalizeUrl(url) {
36
+ if (!/^https?:\/\//i.test(url)) {
37
+ return `https://${url}`;
38
+ }
39
+ return url;
40
+ }
41
+ function normalizeDomain(url) {
42
+ try {
43
+ const u = new URL(normalizeUrl(url));
44
+ return u.hostname.replace(/^www\./, "");
45
+ } catch {
46
+ return url.replace(/^www\./, "");
47
+ }
48
+ }
49
+ function getProfilePath(domain) {
50
+ return join(PROFILES_DIR, `${domain}.json`);
51
+ }
52
+ function hasProfile(domain) {
53
+ return existsSync(getProfilePath(domain));
54
+ }
55
+ async function tryConnectToPort(port, logger, timeoutMs = 5e3) {
56
+ const endpoint = `http://localhost:${port}`;
57
+ logger.info("cdp-connect-attempt", { port, endpoint, timeoutMs });
58
+ try {
59
+ const connectPromise = chromium.connectOverCDP(endpoint);
60
+ const timeoutPromise = new Promise(
61
+ (resolve2) => setTimeout(() => resolve2(null), timeoutMs)
62
+ );
63
+ const browser = await Promise.race([connectPromise, timeoutPromise]);
64
+ if (browser) {
65
+ logger.info("cdp-connect-success", {
66
+ port,
67
+ endpoint,
68
+ contexts: browser.contexts().length
69
+ });
70
+ } else {
71
+ logger.warn("cdp-connect-timeout", { port, endpoint, timeoutMs });
72
+ }
73
+ return browser;
74
+ } catch (err) {
75
+ logger.error("cdp-connect-error", { error: err, port, endpoint });
76
+ return null;
77
+ }
78
+ }
79
+ function disconnectBrowser(browser, logger, session) {
80
+ logger.info("cdp-disconnect", { session });
81
+ try {
82
+ browser._connection?.close();
83
+ } catch (err) {
84
+ logger.warn("cdp-disconnect-already-closed", { error: err });
85
+ }
86
+ }
87
+ async function connect(session, logger, timeoutMs = 1e4) {
88
+ logger.info("connect", { session, timeoutMs });
89
+ const state = readSessionStateOrThrow(session);
90
+ const browser = await tryConnectToPort(state.port, logger, timeoutMs);
91
+ if (!browser) {
92
+ logger.error("connect-no-browser", {
93
+ session,
94
+ port: state.port,
95
+ pid: state.pid
96
+ });
97
+ clearSessionState(session, logger);
98
+ throw new Error(
99
+ `No browser running for session "${session}". Run 'libretto-cli open <url> --session ${session}' first.`
100
+ );
101
+ }
102
+ const contexts = browser.contexts();
103
+ logger.info("connect-contexts", { session, contextCount: contexts.length });
104
+ if (contexts.length === 0) {
105
+ logger.error("connect-no-contexts", { session });
106
+ throw new Error("No browser context found.");
107
+ }
108
+ const allPages = contexts.flatMap((c) => c.pages());
109
+ const pages = allPages.filter((p) => {
110
+ const url = p.url();
111
+ return !url.startsWith("devtools://") && !url.startsWith("chrome-error://");
112
+ });
113
+ logger.info("connect-pages", {
114
+ session,
115
+ totalPages: allPages.length,
116
+ filteredPages: pages.length,
117
+ urls: allPages.map((p) => p.url())
118
+ });
119
+ if (pages.length === 0) {
120
+ logger.error("connect-no-pages", {
121
+ session,
122
+ allPageUrls: allPages.map((p) => p.url())
123
+ });
124
+ throw new Error("No pages found.");
125
+ }
126
+ const page = pages[pages.length - 1];
127
+ const context = page.context();
128
+ page.on("close", () => {
129
+ logger.error("page-closed-during-command", {
130
+ session,
131
+ url: page.url(),
132
+ trace: new Error("page-closed-trace").stack
133
+ });
134
+ });
135
+ page.on("crash", () => {
136
+ logger.error("page-crashed-during-command", {
137
+ session,
138
+ url: page.url()
139
+ });
140
+ });
141
+ browser.on("disconnected", () => {
142
+ logger.error("browser-disconnected-during-command", {
143
+ session,
144
+ trace: new Error("browser-disconnected-trace").stack
145
+ });
146
+ });
147
+ logger.info("connect-success", { session, pageUrl: page.url() });
148
+ return { browser, context, page };
149
+ }
150
+ async function runOpen(rawUrl, headed, session, logger) {
151
+ const url = normalizeUrl(rawUrl);
152
+ logger.info("open-start", { url, headed, session });
153
+ assertSessionAvailableForStart(session, logger);
154
+ const port = await pickFreePort();
155
+ const runLogPath = logFileForSession(session);
156
+ const networkLogPath = getSessionNetworkLogPath(session);
157
+ const actionsLogPath = getSessionActionsLogPath(session);
158
+ const browserMode = headed ? "headed" : "headless";
159
+ const domain = normalizeDomain(url);
160
+ const profilePath = getProfilePath(domain);
161
+ const useProfile = hasProfile(domain);
162
+ logger.info("open-launching", {
163
+ url,
164
+ mode: browserMode,
165
+ session,
166
+ port,
167
+ domain,
168
+ useProfile,
169
+ profilePath: useProfile ? profilePath : void 0
170
+ });
171
+ if (useProfile) {
172
+ console.log(`Loading saved profile for ${domain}`);
173
+ }
174
+ console.log(`Launching ${browserMode} browser (session: ${session})...`);
175
+ const escapedProfilePath = profilePath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
176
+ const escapedUrl = url.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
177
+ const storageStateCode = useProfile ? `storageState: '${escapedProfilePath}',` : "";
178
+ const escapedLogPath = runLogPath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
179
+ const escapedNetworkLogPath = networkLogPath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
180
+ const escapedActionsLogPath = actionsLogPath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
181
+ const launcherCode = `
182
+ import { chromium } from 'playwright';
183
+ import { appendFileSync, mkdirSync } from 'node:fs';
184
+
185
+ const LOG_FILE = '${escapedLogPath}';
186
+ const NETWORK_LOG = '${escapedNetworkLogPath}';
187
+ const ACTIONS_LOG = '${escapedActionsLogPath}';
188
+ mkdirSync(NETWORK_LOG.replace(/\\/[^\\/]+$/, ''), { recursive: true });
189
+
190
+ const STATIC_EXT_RE = /\\.(css|js|png|jpg|jpeg|gif|woff|woff2|ttf|ico|svg)(\\?|$)/i;
191
+ async function logNetworkResponse(response) {
192
+ try {
193
+ const req = response.request();
194
+ const url = req.url();
195
+ if (STATIC_EXT_RE.test(url) || url.startsWith('chrome-extension://')) return;
196
+ let responseBody = null;
197
+ try {
198
+ const buf = await response.body();
199
+ responseBody = buf.toString('utf-8');
200
+ } catch {}
201
+ const entry = JSON.stringify({
202
+ ts: new Date().toISOString(),
203
+ method: req.method(),
204
+ url,
205
+ status: response.status(),
206
+ contentType: response.headers()['content-type'] || null,
207
+ postData: req.method() === 'POST' || req.method() === 'PUT' || req.method() === 'PATCH'
208
+ ? (req.postData() || '').substring(0, 2000)
209
+ : undefined,
210
+ responseBody,
211
+ });
212
+ appendFileSync(NETWORK_LOG, entry + '\\n');
213
+ } catch {}
214
+ }
215
+
216
+ function logAction(entry) {
217
+ try {
218
+ const record = { ts: new Date().toISOString(), ...entry };
219
+ appendFileSync(ACTIONS_LOG, JSON.stringify(record) + '\\n');
220
+ } catch {}
221
+ }
222
+
223
+ function childLog(level, event, data = {}) {
224
+ try {
225
+ const entry = JSON.stringify({
226
+ timestamp: new Date().toISOString(),
227
+ id: Math.random().toString(36).slice(2, 10),
228
+ level,
229
+ scope: 'libretto-cli.child',
230
+ event,
231
+ data,
232
+ });
233
+ appendFileSync(LOG_FILE, entry + '\\n');
234
+ } catch {}
235
+ }
236
+
237
+ async function setupActionTracking(p) {
238
+ await p.exposeFunction('__btActionLog', (jsonStr) => {
239
+ try { logAction({ ...JSON.parse(jsonStr), source: 'user' }); } catch {}
240
+ });
241
+
242
+ await p.addInitScript(() => {
243
+ if (window.__btDomListenersInstalled) return;
244
+ window.__btDomListenersInstalled = true;
245
+
246
+ function identify(el) {
247
+ if (!el || !el.tagName) return '';
248
+ var tid = el.getAttribute('data-testid');
249
+ if (tid) return '[data-testid="' + tid + '"]';
250
+ var role = el.getAttribute('role') || '';
251
+ var id = el.id;
252
+ if (role && id) return role + '#' + id;
253
+ var name = el.getAttribute('aria-label') || (el.textContent || '').trim().slice(0, 30) || '';
254
+ if (role && name) return role + ' "' + name + '"';
255
+ var tag = el.tagName.toLowerCase();
256
+ var cls = el.className && typeof el.className === 'string' ? '.' + el.className.trim().split(/\\s+/).slice(0, 2).join('.') : '';
257
+ return tag + cls;
258
+ }
259
+
260
+ var clickTimer = null;
261
+ var pendingClick = null;
262
+
263
+ document.addEventListener('click', function(e) {
264
+ if (window.__btApiActionInProgress) return;
265
+ var target = e.target;
266
+ var sel = identify(target);
267
+ if (target.type === 'checkbox') {
268
+ if (typeof window.__btActionLog === 'function') {
269
+ window.__btActionLog(JSON.stringify({ action: target.checked ? 'check' : 'uncheck', selector: sel, success: true }));
270
+ }
271
+ return;
272
+ }
273
+ pendingClick = { selector: sel };
274
+ if (clickTimer) clearTimeout(clickTimer);
275
+ clickTimer = setTimeout(function() {
276
+ if (pendingClick && typeof window.__btActionLog === 'function') {
277
+ window.__btActionLog(JSON.stringify({ action: 'click', selector: pendingClick.selector, success: true }));
278
+ }
279
+ pendingClick = null;
280
+ clickTimer = null;
281
+ }, 200);
282
+ }, true);
283
+
284
+ document.addEventListener('dblclick', function(e) {
285
+ if (window.__btApiActionInProgress) return;
286
+ if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; pendingClick = null; }
287
+ var sel = identify(e.target);
288
+ if (typeof window.__btActionLog === 'function') {
289
+ window.__btActionLog(JSON.stringify({ action: 'dblclick', selector: sel, success: true }));
290
+ }
291
+ }, true);
292
+
293
+ var inputTimers = new WeakMap();
294
+ document.addEventListener('input', function(e) {
295
+ if (window.__btApiActionInProgress) return;
296
+ var target = e.target;
297
+ var sel = identify(target);
298
+ if (target.tagName === 'SELECT') {
299
+ if (typeof window.__btActionLog === 'function') {
300
+ window.__btActionLog(JSON.stringify({ action: 'selectOption', selector: sel, value: target.value, success: true }));
301
+ }
302
+ return;
303
+ }
304
+ var existing = inputTimers.get(target);
305
+ if (existing) clearTimeout(existing);
306
+ inputTimers.set(target, setTimeout(function() {
307
+ inputTimers.delete(target);
308
+ if (typeof window.__btActionLog === 'function') {
309
+ window.__btActionLog(JSON.stringify({ action: 'fill', selector: sel, value: (target.value || '').slice(0, 100), success: true }));
310
+ }
311
+ }, 500));
312
+ }, true);
313
+
314
+ var SPECIAL_KEYS = ['Enter','Escape','Tab','Backspace','Delete','ArrowUp','ArrowDown','ArrowLeft','ArrowRight','Home','End','PageUp','PageDown','F1','F2','F3','F4','F5','F6','F7','F8','F9','F10','F11','F12'];
315
+ document.addEventListener('keydown', function(e) {
316
+ if (window.__btApiActionInProgress) return;
317
+ var isShortcut = e.ctrlKey || e.metaKey || e.altKey;
318
+ if (!isShortcut && SPECIAL_KEYS.indexOf(e.key) === -1) return;
319
+ var sel = identify(e.target);
320
+ var keyDesc = (e.ctrlKey ? 'Ctrl+' : '') + (e.metaKey ? 'Meta+' : '') + (e.altKey ? 'Alt+' : '') + (e.shiftKey ? 'Shift+' : '') + e.key;
321
+ if (typeof window.__btActionLog === 'function') {
322
+ window.__btActionLog(JSON.stringify({ action: 'press', selector: sel, value: keyDesc, success: true }));
323
+ }
324
+ }, true);
325
+
326
+ var scrollTimer = null;
327
+ document.addEventListener('scroll', function() {
328
+ if (window.__btApiActionInProgress) return;
329
+ if (scrollTimer) clearTimeout(scrollTimer);
330
+ scrollTimer = setTimeout(function() {
331
+ scrollTimer = null;
332
+ if (typeof window.__btActionLog === 'function') {
333
+ window.__btActionLog(JSON.stringify({ action: 'scroll', selector: 'document', value: 'y=' + window.scrollY, success: true }));
334
+ }
335
+ }, 300);
336
+ }, true);
337
+ });
338
+
339
+ var PAGE_ACTIONS = ['click', 'dblclick', 'fill', 'type', 'press', 'check', 'uncheck', 'selectOption', 'hover', 'focus'];
340
+ var NAV_ACTIONS = ['goto', 'reload', 'goBack', 'goForward'];
341
+
342
+ for (var m of PAGE_ACTIONS) {
343
+ (function(method) {
344
+ var orig = p[method].bind(p);
345
+ p[method] = async function() {
346
+ var args = Array.from(arguments);
347
+ var start = Date.now();
348
+ try { await p.evaluate(function() { window.__btApiActionInProgress = true; }); } catch {}
349
+ try {
350
+ var result = await orig.apply(null, args);
351
+ logAction({ action: method, source: 'agent', selector: typeof args[0] === 'string' ? args[0] : undefined, value: args[1] !== undefined ? String(args[1]).slice(0, 100) : undefined, duration: Date.now() - start, success: true });
352
+ return result;
353
+ } catch (err) {
354
+ logAction({ action: method, source: 'agent', selector: typeof args[0] === 'string' ? args[0] : undefined, duration: Date.now() - start, success: false, error: err.message });
355
+ throw err;
356
+ } finally {
357
+ try { await p.evaluate(function() { window.__btApiActionInProgress = false; }); } catch {}
358
+ }
359
+ };
360
+ })(m);
361
+ }
362
+
363
+ for (var m of NAV_ACTIONS) {
364
+ (function(method) {
365
+ var orig = p[method].bind(p);
366
+ p[method] = async function() {
367
+ var args = Array.from(arguments);
368
+ var start = Date.now();
369
+ try {
370
+ var result = await orig.apply(null, args);
371
+ logAction({ action: method, source: 'agent', url: typeof args[0] === 'string' ? args[0] : p.url(), duration: Date.now() - start, success: true });
372
+ return result;
373
+ } catch (err) {
374
+ logAction({ action: method, source: 'agent', url: typeof args[0] === 'string' ? args[0] : undefined, duration: Date.now() - start, success: false, error: err.message });
375
+ throw err;
376
+ }
377
+ };
378
+ })(m);
379
+ }
380
+
381
+ var LOCATOR_FACTORIES = ['locator', 'getByRole', 'getByText', 'getByLabel', 'getByPlaceholder', 'getByAltText', 'getByTitle', 'getByTestId'];
382
+ for (var f of LOCATOR_FACTORIES) {
383
+ (function(factory) {
384
+ var orig = p[factory].bind(p);
385
+ p[factory] = function() {
386
+ var args = Array.from(arguments);
387
+ var locator = orig.apply(null, args);
388
+ var hint = factory + '(' + args.map(function(a) { return typeof a === 'string' ? a : JSON.stringify(a); }).join(', ') + ')';
389
+ for (var am of PAGE_ACTIONS) {
390
+ (function(actMethod) {
391
+ if (typeof locator[actMethod] !== 'function') return;
392
+ var origAct = locator[actMethod].bind(locator);
393
+ locator[actMethod] = async function() {
394
+ var actArgs = Array.from(arguments);
395
+ var start = Date.now();
396
+ try { await p.evaluate(function() { window.__btApiActionInProgress = true; }); } catch {}
397
+ try {
398
+ var result = await origAct.apply(null, actArgs);
399
+ logAction({ action: actMethod, source: 'agent', selector: hint, value: actArgs[0] !== undefined ? String(actArgs[0]).slice(0, 100) : undefined, duration: Date.now() - start, success: true });
400
+ return result;
401
+ } catch (err) {
402
+ logAction({ action: actMethod, source: 'agent', selector: hint, duration: Date.now() - start, success: false, error: err.message });
403
+ throw err;
404
+ } finally {
405
+ try { await p.evaluate(function() { window.__btApiActionInProgress = false; }); } catch {}
406
+ }
407
+ };
408
+ })(am);
409
+ }
410
+ return locator;
411
+ };
412
+ })(f);
413
+ }
414
+ }
415
+
416
+ const browser = await chromium.launch({
417
+ headless: ${!headed},
418
+ args: ['--disable-blink-features=AutomationControlled', '--remote-debugging-port=${port}', '--remote-debugging-address=127.0.0.1', '--no-focus-on-check'],
419
+ });
420
+
421
+ browser.on('disconnected', () => {
422
+ childLog('warn', 'browser-disconnected', { port: ${port} });
423
+ });
424
+
425
+ const context = await browser.newContext({
426
+ ${storageStateCode}
427
+ viewport: { width: 1366, height: 768 },
428
+ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
429
+ });
430
+
431
+ const page = await context.newPage();
432
+ page.setDefaultTimeout(30000);
433
+ page.setDefaultNavigationTimeout(45000);
434
+
435
+ await setupActionTracking(page);
436
+
437
+ page.on('crash', () => childLog('error', 'page-crash', { url: page.url() }));
438
+ page.on('close', () => childLog('warn', 'page-close', { url: page.url(), trace: new Error('page-close-trace').stack }));
439
+ page.on('pageerror', (err) => childLog('error', 'page-error', { message: err.message, stack: err.stack }));
440
+ page.on('console', (msg) => {
441
+ if (msg.type() === 'error' || msg.type() === 'warning') {
442
+ childLog(msg.type() === 'error' ? 'error' : 'warn', 'console-' + msg.type(), { text: msg.text(), url: page.url() });
443
+ }
444
+ });
445
+ page.on('framenavigated', (frame) => {
446
+ if (frame === page.mainFrame()) {
447
+ childLog('info', 'page-navigated', { url: frame.url() });
448
+ logAction({ action: 'navigate', source: 'agent', url: frame.url(), success: true });
449
+ }
450
+ });
451
+ page.on('requestfailed', (req) => {
452
+ const failure = req.failure();
453
+ childLog('warn', 'request-failed', { url: req.url(), method: req.method(), errorText: failure?.errorText });
454
+ });
455
+ page.on('response', logNetworkResponse);
456
+ page.on('popup', (popup) => logAction({ action: 'popup', source: 'agent', url: popup.url(), success: true }));
457
+ page.on('dialog', (dialog) => logAction({ action: 'dialog', source: 'agent', value: dialog.type() + ': ' + dialog.message().slice(0, 500), success: true }));
458
+
459
+ context.on('page', async (newPage) => {
460
+ childLog('info', 'new-page-created', { url: newPage.url() });
461
+ newPage.on('crash', () => childLog('error', 'page-crash', { url: newPage.url() }));
462
+ newPage.on('close', () => childLog('info', 'page-close', { url: newPage.url(), trace: new Error('page-close-trace').stack }));
463
+ newPage.on('response', logNetworkResponse);
464
+ newPage.on('popup', (popup) => logAction({ action: 'popup', source: 'agent', url: popup.url(), success: true }));
465
+ newPage.on('dialog', (dialog) => logAction({ action: 'dialog', source: 'agent', value: dialog.type() + ': ' + dialog.message().slice(0, 500), success: true }));
466
+ newPage.on('framenavigated', (frame) => {
467
+ if (frame === newPage.mainFrame()) logAction({ action: 'navigate', source: 'agent', url: frame.url(), success: true });
468
+ });
469
+ try { await setupActionTracking(newPage); } catch (err) {
470
+ childLog('warn', 'action-tracking-setup-failed', { url: newPage.url(), error: err.message });
471
+ }
472
+ });
473
+
474
+
475
+ await page.goto('${escapedUrl}');
476
+
477
+ process.on('SIGTERM', async () => {
478
+ childLog('info', 'child-sigterm');
479
+ await browser.close();
480
+ process.exit(0);
481
+ });
482
+
483
+ process.on('SIGINT', async () => {
484
+ childLog('info', 'child-sigint');
485
+ await browser.close();
486
+ process.exit(0);
487
+ });
488
+
489
+ process.on('uncaughtException', (err) => {
490
+ childLog('error', 'uncaught-exception', { message: err.message, stack: err.stack });
491
+ process.exit(1);
492
+ });
493
+
494
+ process.on('unhandledRejection', (reason) => {
495
+ childLog('warn', 'unhandled-rejection', { reason: String(reason) });
496
+ });
497
+
498
+ process.on('exit', (code) => {
499
+ childLog('info', 'child-exit', { code, pid: process.pid, port: ${port} });
500
+ });
501
+
502
+ childLog('info', 'child-launched', { port: ${port}, pid: process.pid, session: '${session}' });
503
+
504
+ await new Promise(() => {});
505
+ `;
506
+ const childStderrFd = openSync(runLogPath, "a");
507
+ const child = spawn("node", ["--input-type=module", "-e", launcherCode], {
508
+ detached: true,
509
+ stdio: ["ignore", "ignore", childStderrFd],
510
+ cwd: resolve(dirname(fileURLToPath(import.meta.url)), "../../..")
511
+ });
512
+ child.unref();
513
+ logger.info("open-child-spawned", { pid: child.pid, port, session });
514
+ let childSpawnError = null;
515
+ let childEarlyExit = null;
516
+ child.on("error", (err) => {
517
+ childSpawnError = err;
518
+ logger.error("open-child-spawn-error", { error: err, session, port });
519
+ });
520
+ child.on("exit", (code, signal) => {
521
+ childEarlyExit = { code, signal };
522
+ logger.warn("open-child-exited", {
523
+ code,
524
+ signal,
525
+ session,
526
+ port,
527
+ pid: child.pid
528
+ });
529
+ });
530
+ const cdpPollIntervalMs = 500;
531
+ const cdpMaxAttempts = 30;
532
+ const cdpStartupTimeoutMs = cdpPollIntervalMs * cdpMaxAttempts;
533
+ for (let i = 0; i < cdpMaxAttempts; i++) {
534
+ const spawnError = childSpawnError;
535
+ if (spawnError !== null) {
536
+ const errWithCode = spawnError;
537
+ const hint = errWithCode.code === "ENOENT" ? " Ensure Node.js is available in PATH for child processes." : "";
538
+ throw new Error(
539
+ `Failed to launch browser child process: ${spawnError.message}.${hint} Check logs: ${runLogPath}`
540
+ );
541
+ }
542
+ const earlyExit = childEarlyExit;
543
+ if (earlyExit !== null) {
544
+ const status = earlyExit.code ?? earlyExit.signal ?? "unknown";
545
+ throw new Error(
546
+ `Browser child process exited before startup (status: ${status}). Check logs: ${runLogPath}`
547
+ );
548
+ }
549
+ await new Promise((r) => setTimeout(r, cdpPollIntervalMs));
550
+ const ready = await fetch(`http://127.0.0.1:${port}/json/version`).then(() => true).catch(() => false);
551
+ if (i > 0 && i % 5 === 0) {
552
+ logger.info("open-waiting-for-cdp", { attempt: i, port, session });
553
+ }
554
+ if (ready) {
555
+ writeSessionState({
556
+ port,
557
+ pid: child.pid,
558
+ session,
559
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
560
+ status: "active"
561
+ }, logger);
562
+ logger.info("open-success", {
563
+ url,
564
+ mode: browserMode,
565
+ session,
566
+ port,
567
+ pid: child.pid
568
+ });
569
+ console.log(`Browser open (${browserMode}): ${url}`);
570
+ await new Promise((r) => setTimeout(r, 2e3));
571
+ return;
572
+ }
573
+ }
574
+ logger.error("open-timeout", {
575
+ session,
576
+ port,
577
+ pid: child.pid,
578
+ attempts: cdpMaxAttempts
579
+ });
580
+ throw new Error(
581
+ `Failed to connect to browser after ${Math.ceil(cdpStartupTimeoutMs / 1e3)}s. Check startup logs: ${runLogPath}`
582
+ );
583
+ }
584
+ async function runSave(urlOrDomain, session, logger) {
585
+ logger.info("save-start", { urlOrDomain, session });
586
+ const { browser, context, page } = await connect(session, logger);
587
+ try {
588
+ await new Promise((r) => setTimeout(r, 500));
589
+ const domain = normalizeDomain(urlOrDomain);
590
+ const profilePath = getProfilePath(domain);
591
+ const cdpSession = await context.newCDPSession(page);
592
+ const { cookies: rawCookies } = await cdpSession.send(
593
+ "Network.getAllCookies"
594
+ );
595
+ const cookies = rawCookies.map((c) => {
596
+ const cookie = { ...c };
597
+ if (cookie.partitionKey && typeof cookie.partitionKey === "object") {
598
+ delete cookie.partitionKey;
599
+ }
600
+ return cookie;
601
+ });
602
+ await cdpSession.detach();
603
+ const origins = [];
604
+ for (const ctx of browser.contexts()) {
605
+ for (const pg of ctx.pages()) {
606
+ try {
607
+ const origin = new URL(pg.url()).origin;
608
+ const localStorage = await pg.evaluate(() => {
609
+ const items = [];
610
+ for (let i = 0; i < window.localStorage.length; i++) {
611
+ const key = window.localStorage.key(i);
612
+ if (key) {
613
+ items.push({
614
+ name: key,
615
+ value: window.localStorage.getItem(key) || ""
616
+ });
617
+ }
618
+ }
619
+ return items;
620
+ });
621
+ if (localStorage.length > 0) {
622
+ origins.push({ origin, localStorage });
623
+ }
624
+ } catch {
625
+ }
626
+ }
627
+ }
628
+ const state = { cookies, origins };
629
+ const fs = await import("node:fs/promises");
630
+ await fs.mkdir(dirname(profilePath), { recursive: true });
631
+ await fs.writeFile(profilePath, JSON.stringify(state, null, 2));
632
+ logger.info("save-success", {
633
+ domain,
634
+ profilePath,
635
+ cookieCount: cookies.length,
636
+ originCount: origins.length
637
+ });
638
+ console.log(`Profile saved for ${domain}`);
639
+ console.log(` Location: ${profilePath}`);
640
+ console.log(` Cookies: ${cookies.length}, Origins: ${origins.length}`);
641
+ } catch (err) {
642
+ logger.error("save-error", { error: err, urlOrDomain, session });
643
+ throw err;
644
+ } finally {
645
+ disconnectBrowser(browser, logger, session);
646
+ }
647
+ }
648
+ async function runClose(session, logger) {
649
+ logger.info("close-start", { session });
650
+ const state = readSessionState(session, logger);
651
+ if (!state) {
652
+ logger.info("close-no-session", { session });
653
+ console.log(`No browser running for session "${session}".`);
654
+ return;
655
+ }
656
+ logger.info("close-killing", { session, pid: state.pid, port: state.port });
657
+ try {
658
+ process.kill(state.pid, "SIGTERM");
659
+ } catch (err) {
660
+ logger.warn("close-kill-failed", { error: err, session, pid: state.pid });
661
+ }
662
+ await new Promise((r) => setTimeout(r, 1500));
663
+ clearSessionState(session, logger);
664
+ logger.info("close-success", { session });
665
+ console.log(`Browser closed (session: ${session}).`);
666
+ }
667
+ function resolvePath(filePath) {
668
+ return join(process.cwd(), filePath);
669
+ }
670
+ function getScreenshotBaseName(title) {
671
+ const sanitizedTitle = title.replace(/[^a-zA-Z0-9\s-]/g, "").replace(/\s+/g, "-").toLowerCase().slice(0, 50);
672
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
673
+ return `${sanitizedTitle}-${timestamp}`;
674
+ }
675
+ export {
676
+ connect,
677
+ disconnectBrowser,
678
+ getProfilePath,
679
+ getScreenshotBaseName,
680
+ hasProfile,
681
+ normalizeDomain,
682
+ normalizeUrl,
683
+ resolvePath,
684
+ runClose,
685
+ runOpen,
686
+ runSave
687
+ };