browsirai 0.1.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.
package/dist/bin.js ADDED
@@ -0,0 +1,4929 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/version.ts
13
+ import { createRequire } from "module";
14
+ var require2, pkg, VERSION;
15
+ var init_version = __esm({
16
+ "src/version.ts"() {
17
+ "use strict";
18
+ require2 = createRequire(import.meta.url);
19
+ pkg = require2("../package.json");
20
+ VERSION = pkg.version;
21
+ }
22
+ });
23
+
24
+ // src/cdp/wait-ready.ts
25
+ var init_wait_ready = __esm({
26
+ "src/cdp/wait-ready.ts"() {
27
+ "use strict";
28
+ }
29
+ });
30
+
31
+ // src/cdp/connection.ts
32
+ var TIMEOUT, DAEMON_CONNECT_RETRIES, DAEMON_CONNECT_DELAY, CDPConnection;
33
+ var init_connection = __esm({
34
+ "src/cdp/connection.ts"() {
35
+ "use strict";
36
+ init_wait_ready();
37
+ TIMEOUT = 15e3;
38
+ DAEMON_CONNECT_RETRIES = 20;
39
+ DAEMON_CONNECT_DELAY = 300;
40
+ CDPConnection = class {
41
+ wsUrl;
42
+ ws = null;
43
+ nextId = 1;
44
+ pending = /* @__PURE__ */ new Map();
45
+ eventHandlers = /* @__PURE__ */ new Map();
46
+ closed = false;
47
+ reconnecting = false;
48
+ _connected = false;
49
+ // Bound listener references for cleanup
50
+ boundOnMessage = null;
51
+ boundOnClose = null;
52
+ boundOnError = null;
53
+ constructor(wsUrl) {
54
+ this.wsUrl = wsUrl;
55
+ }
56
+ // -----------------------------------------------------------------------
57
+ // Public API
58
+ // -----------------------------------------------------------------------
59
+ /** Whether the underlying WebSocket is currently open. */
60
+ get isConnected() {
61
+ return this._connected;
62
+ }
63
+ /**
64
+ * Open the WebSocket connection.
65
+ * Resolves on the `open` event; rejects on `error`.
66
+ */
67
+ async connect() {
68
+ let WS = globalThis.WebSocket;
69
+ if (!WS) {
70
+ try {
71
+ const wsModule = await import("ws");
72
+ WS = wsModule.default ?? wsModule.WebSocket ?? wsModule;
73
+ } catch {
74
+ throw new Error(
75
+ "No WebSocket implementation found. Install the `ws` package or use Node 22+."
76
+ );
77
+ }
78
+ }
79
+ const ws = new WS(this.wsUrl);
80
+ this.ws = ws;
81
+ await new Promise((resolve2, reject) => {
82
+ const onOpen = () => {
83
+ ws.removeEventListener("open", onOpen);
84
+ ws.removeEventListener("error", onError);
85
+ resolve2();
86
+ };
87
+ const onError = (ev) => {
88
+ ws.removeEventListener("open", onOpen);
89
+ ws.removeEventListener("error", onError);
90
+ const msg = ev?.message ?? "WebSocket error";
91
+ reject(new Error(msg));
92
+ };
93
+ ws.addEventListener("open", onOpen);
94
+ ws.addEventListener("error", onError);
95
+ });
96
+ this._connected = true;
97
+ this.attachListeners(ws);
98
+ }
99
+ /**
100
+ * Send a CDP command and await its result.
101
+ *
102
+ * @param method CDP method (e.g. `"Runtime.evaluate"`)
103
+ * @param params Optional method parameters
104
+ * @param options Optional timeout / sessionId overrides
105
+ */
106
+ send(method, params, options) {
107
+ if (this.closed || !this._connected || !this.ws) {
108
+ return Promise.reject(
109
+ new Error(`Connection closed \u2014 cannot send ${method}`)
110
+ );
111
+ }
112
+ const id = this.nextId++;
113
+ const timeout = options?.timeout ?? TIMEOUT;
114
+ const message = { id, method };
115
+ if (params !== void 0) {
116
+ message.params = params;
117
+ }
118
+ if (options?.sessionId !== void 0) {
119
+ message.sessionId = options.sessionId;
120
+ }
121
+ this.ws.send(JSON.stringify(message));
122
+ return new Promise((resolve2, reject) => {
123
+ const timer = setTimeout(() => {
124
+ this.pending.delete(id);
125
+ reject(new Error(`CDP command timeout: ${method} (${timeout}ms)`));
126
+ }, timeout);
127
+ this.pending.set(id, { resolve: resolve2, reject, method, timer });
128
+ });
129
+ }
130
+ /**
131
+ * Register an event handler for a CDP event or lifecycle event.
132
+ *
133
+ * CDP events are dispatched as `handler(params, { method })`.
134
+ * Lifecycle events: `disconnected`, `browserCrashed`, `reconnected`,
135
+ * `reconnectionFailed`.
136
+ */
137
+ on(event, handler) {
138
+ let handlers = this.eventHandlers.get(event);
139
+ if (!handlers) {
140
+ handlers = /* @__PURE__ */ new Set();
141
+ this.eventHandlers.set(event, handlers);
142
+ }
143
+ handlers.add(handler);
144
+ }
145
+ /** Remove a previously registered event handler. */
146
+ off(event, handler) {
147
+ this.eventHandlers.get(event)?.delete(handler);
148
+ }
149
+ /**
150
+ * Close the connection. Suppresses reconnection.
151
+ * Safe to call multiple times.
152
+ */
153
+ close() {
154
+ this.closed = true;
155
+ this._connected = false;
156
+ this.rejectAllPending(new Error("Connection closed"));
157
+ if (this.ws) {
158
+ this.detachListeners(this.ws);
159
+ try {
160
+ this.ws.close();
161
+ } catch {
162
+ }
163
+ this.ws = null;
164
+ }
165
+ }
166
+ // -----------------------------------------------------------------------
167
+ // Private
168
+ // -----------------------------------------------------------------------
169
+ /** Attach message / close / error listeners to the WebSocket. */
170
+ attachListeners(ws) {
171
+ this.boundOnMessage = (event) => {
172
+ const data = event?.data;
173
+ if (typeof data !== "string") return;
174
+ let msg;
175
+ try {
176
+ msg = JSON.parse(data);
177
+ } catch {
178
+ return;
179
+ }
180
+ if (msg.id !== void 0) {
181
+ const entry = this.pending.get(msg.id);
182
+ if (entry) {
183
+ this.pending.delete(msg.id);
184
+ clearTimeout(entry.timer);
185
+ if (msg.error) {
186
+ entry.reject(new Error(msg.error.message));
187
+ } else {
188
+ entry.resolve(msg.result);
189
+ }
190
+ }
191
+ return;
192
+ }
193
+ if (msg.method) {
194
+ this.emit(msg.method, msg.params ?? {}, { method: msg.method });
195
+ }
196
+ };
197
+ this.boundOnClose = (event) => {
198
+ const code = event?.code ?? 1006;
199
+ this._connected = false;
200
+ this.rejectAllPending(new Error("WebSocket disconnected \u2014 connection closed"));
201
+ if (code !== 1e3) {
202
+ this.emit("browserCrashed");
203
+ }
204
+ this.emit("disconnected");
205
+ if (!this.closed && code !== 1e3) {
206
+ this.attemptReconnection().catch(() => {
207
+ });
208
+ }
209
+ };
210
+ this.boundOnError = () => {
211
+ };
212
+ ws.addEventListener("message", this.boundOnMessage);
213
+ ws.addEventListener("close", this.boundOnClose);
214
+ ws.addEventListener("error", this.boundOnError);
215
+ }
216
+ /** Detach WebSocket listeners. */
217
+ detachListeners(ws) {
218
+ if (this.boundOnMessage) ws.removeEventListener("message", this.boundOnMessage);
219
+ if (this.boundOnClose) ws.removeEventListener("close", this.boundOnClose);
220
+ if (this.boundOnError) ws.removeEventListener("error", this.boundOnError);
221
+ }
222
+ /** Reject every pending command. */
223
+ rejectAllPending(error) {
224
+ for (const [id, entry] of this.pending) {
225
+ clearTimeout(entry.timer);
226
+ entry.reject(error);
227
+ this.pending.delete(id);
228
+ }
229
+ }
230
+ /** Dispatch an event to all registered handlers. */
231
+ emit(event, ...args) {
232
+ const handlers = this.eventHandlers.get(event);
233
+ if (!handlers) return;
234
+ for (const handler of handlers) {
235
+ try {
236
+ handler(...args);
237
+ } catch {
238
+ }
239
+ }
240
+ }
241
+ /** Attempt reconnection with retries after abnormal close. */
242
+ async attemptReconnection() {
243
+ if (this.reconnecting || this.closed) return;
244
+ this.reconnecting = true;
245
+ for (let attempt = 0; attempt < DAEMON_CONNECT_RETRIES; attempt++) {
246
+ if (this.closed) {
247
+ this.reconnecting = false;
248
+ return;
249
+ }
250
+ await this.delay(DAEMON_CONNECT_DELAY);
251
+ if (this.closed) {
252
+ this.reconnecting = false;
253
+ return;
254
+ }
255
+ try {
256
+ await this.connect();
257
+ this.reconnecting = false;
258
+ this.emit("reconnected");
259
+ return;
260
+ } catch {
261
+ }
262
+ }
263
+ this.reconnecting = false;
264
+ this.emit("reconnectionFailed");
265
+ }
266
+ /** Promise-based delay. */
267
+ delay(ms) {
268
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
269
+ }
270
+ };
271
+ }
272
+ });
273
+
274
+ // src/chrome-launcher.ts
275
+ var chrome_launcher_exports = {};
276
+ __export(chrome_launcher_exports, {
277
+ connectChrome: () => connectChrome,
278
+ findChrome: () => findChrome,
279
+ getCookieSyncState: () => getCookieSyncState,
280
+ getDefaultChromeDataDir: () => getDefaultChromeDataDir,
281
+ isCDPHealthy: () => isCDPHealthy,
282
+ isChromeRunning: () => isChromeRunning,
283
+ isPortReachable: () => isPortReachable,
284
+ launchChromeWithDebugging: () => launchChromeWithDebugging,
285
+ launchHeadlessChrome: () => launchHeadlessChrome,
286
+ needsCookieResync: () => needsCookieResync,
287
+ openChromeInspect: () => openChromeInspect,
288
+ quitChrome: () => quitChrome,
289
+ readDevToolsActivePort: () => readDevToolsActivePort,
290
+ syncCookiesAndTrack: () => syncCookiesAndTrack
291
+ });
292
+ import { execSync, spawn } from "child_process";
293
+ import { existsSync, readFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
294
+ import http from "http";
295
+ import { join } from "path";
296
+ import { homedir, tmpdir } from "os";
297
+ import { createConnection } from "net";
298
+ function getDefaultChromeDataDir() {
299
+ const home = homedir();
300
+ switch (process.platform) {
301
+ case "darwin":
302
+ return join(home, "Library", "Application Support", "Google", "Chrome");
303
+ case "win32":
304
+ return join(home, "AppData", "Local", "Google", "Chrome", "User Data");
305
+ default:
306
+ return join(home, ".config", "google-chrome");
307
+ }
308
+ }
309
+ function findChrome() {
310
+ const platform = process.platform;
311
+ const candidates = CHROME_PATHS[platform] ?? [];
312
+ for (const candidate of candidates) {
313
+ if (platform === "darwin" || platform === "win32") {
314
+ if (existsSync(candidate)) return candidate;
315
+ } else {
316
+ try {
317
+ const result = execSync(`which ${candidate}`, { stdio: "pipe" });
318
+ const path = result.toString().trim();
319
+ if (path) return path;
320
+ } catch {
321
+ }
322
+ }
323
+ }
324
+ return null;
325
+ }
326
+ function isPortReachable(port, host = "127.0.0.1") {
327
+ return new Promise((resolve2) => {
328
+ const socket = createConnection({ port, host });
329
+ socket.setTimeout(2e3);
330
+ socket.on("connect", () => {
331
+ socket.end();
332
+ resolve2(true);
333
+ });
334
+ socket.on("error", () => {
335
+ socket.destroy();
336
+ resolve2(false);
337
+ });
338
+ socket.on("timeout", () => {
339
+ socket.destroy();
340
+ resolve2(false);
341
+ });
342
+ });
343
+ }
344
+ function isCDPHealthy(port, host = "127.0.0.1") {
345
+ return new Promise((resolve2) => {
346
+ const req = http.get(`http://${host}:${port}/json/version`, (res) => {
347
+ resolve2(res.statusCode === 200);
348
+ res.resume();
349
+ });
350
+ req.setTimeout(3e3, () => {
351
+ req.destroy();
352
+ resolve2(false);
353
+ });
354
+ req.on("error", () => resolve2(false));
355
+ });
356
+ }
357
+ function readDevToolsActivePort(chromeDataDir) {
358
+ const dataDir = chromeDataDir ?? getDefaultChromeDataDir();
359
+ const portFile = join(dataDir, "DevToolsActivePort");
360
+ if (!existsSync(portFile)) {
361
+ return null;
362
+ }
363
+ try {
364
+ const content = readFileSync(portFile, "utf-8");
365
+ const lines = content.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
366
+ if (lines.length < 2) return null;
367
+ const port = parseInt(lines[0], 10);
368
+ const wsPath = lines[1];
369
+ if (isNaN(port) || !wsPath.startsWith("/")) return null;
370
+ return {
371
+ port,
372
+ wsPath,
373
+ wsEndpoint: `ws://127.0.0.1:${port}${wsPath}`
374
+ };
375
+ } catch {
376
+ return null;
377
+ }
378
+ }
379
+ function openChromeInspect() {
380
+ const url = "chrome://inspect/#remote-debugging";
381
+ try {
382
+ if (process.platform === "darwin") {
383
+ execSync(`open -a "Google Chrome" "${url}"`, { stdio: "pipe", timeout: 5e3 });
384
+ } else if (process.platform === "win32") {
385
+ execSync(`start chrome "${url}"`, { stdio: "pipe", timeout: 5e3 });
386
+ } else {
387
+ execSync(`google-chrome "${url}" || chromium "${url}"`, { stdio: "pipe", timeout: 5e3 });
388
+ }
389
+ return true;
390
+ } catch {
391
+ return false;
392
+ }
393
+ }
394
+ function isChromeRunning() {
395
+ try {
396
+ if (process.platform === "win32") {
397
+ const r2 = execSync('tasklist /FI "IMAGENAME eq chrome.exe" /NH', { stdio: "pipe" }).toString();
398
+ return r2.includes("chrome.exe");
399
+ }
400
+ const r = execSync("pgrep -x 'Google Chrome' || pgrep -x chrome || pgrep -x chromium", { stdio: "pipe" }).toString().trim();
401
+ return r.length > 0;
402
+ } catch {
403
+ return false;
404
+ }
405
+ }
406
+ async function quitChrome() {
407
+ try {
408
+ if (process.platform === "darwin") {
409
+ execSync(`osascript -e 'tell application "Google Chrome" to quit'`, { stdio: "pipe", timeout: 5e3 });
410
+ } else if (process.platform === "win32") {
411
+ execSync("taskkill /IM chrome.exe", { stdio: "pipe", timeout: 5e3 });
412
+ } else {
413
+ execSync("pkill -TERM chrome || pkill -TERM chromium", { stdio: "pipe", timeout: 5e3 });
414
+ }
415
+ } catch {
416
+ }
417
+ for (let i = 0; i < 15; i++) {
418
+ if (!isChromeRunning()) break;
419
+ await new Promise((r) => setTimeout(r, 200));
420
+ }
421
+ if (isChromeRunning()) {
422
+ try {
423
+ if (process.platform === "win32") {
424
+ execSync("taskkill /F /IM chrome.exe", { stdio: "pipe", timeout: 5e3 });
425
+ } else {
426
+ execSync("pkill -9 'Google Chrome' || pkill -9 chrome || pkill -9 chromium", { stdio: "pipe", timeout: 5e3 });
427
+ }
428
+ } catch {
429
+ }
430
+ for (let i = 0; i < 15; i++) {
431
+ if (!isChromeRunning()) break;
432
+ await new Promise((r) => setTimeout(r, 200));
433
+ }
434
+ }
435
+ await new Promise((r) => setTimeout(r, 500));
436
+ }
437
+ async function launchChromeWithDebugging(port = 9222, headless = false) {
438
+ const healthy = await isCDPHealthy(port);
439
+ if (healthy) {
440
+ const ws = await getWsEndpoint(port);
441
+ return { success: true, port, wsEndpoint: ws };
442
+ }
443
+ const sepHealthy = await isCDPHealthy(SEPARATE_PORT);
444
+ if (sepHealthy) {
445
+ const ws = await getWsEndpoint(SEPARATE_PORT);
446
+ return { success: true, port: SEPARATE_PORT, wsEndpoint: ws };
447
+ }
448
+ const chromePath = findChrome();
449
+ if (!chromePath) {
450
+ return { success: false, port, error: "Chrome not found. Install Chrome and try again." };
451
+ }
452
+ const usesSeparateInstance = isChromeRunning();
453
+ const targetPort = usesSeparateInstance ? SEPARATE_PORT : port;
454
+ const dataDir = usesSeparateInstance ? join(tmpdir(), "browsirai-normal") : void 0;
455
+ if (dataDir) {
456
+ mkdirSync(dataDir, { recursive: true });
457
+ syncCookiesToHeadless(dataDir);
458
+ }
459
+ const args = [
460
+ `--remote-debugging-port=${targetPort}`,
461
+ "--remote-allow-origins=*"
462
+ ];
463
+ if (dataDir) {
464
+ args.push(`--user-data-dir=${dataDir}`, "--no-first-run", "--no-default-browser-check", "--disable-extensions");
465
+ }
466
+ if (headless) {
467
+ args.push("--headless=new");
468
+ }
469
+ const child = spawn(chromePath, args, {
470
+ detached: true,
471
+ stdio: "ignore"
472
+ });
473
+ child.unref();
474
+ for (let i = 0; i < 75; i++) {
475
+ await new Promise((r) => setTimeout(r, 200));
476
+ const ok = await isCDPHealthy(targetPort);
477
+ if (ok) {
478
+ const ws = await getWsEndpoint(targetPort);
479
+ return { success: true, port: targetPort, wsEndpoint: ws };
480
+ }
481
+ }
482
+ return {
483
+ success: false,
484
+ port: targetPort,
485
+ error: "Chrome launched but CDP port not reachable after 15s. Check if another Chrome instance is blocking the profile."
486
+ };
487
+ }
488
+ async function getWsEndpoint(port) {
489
+ return new Promise((resolve2) => {
490
+ const req = http.get(`http://127.0.0.1:${port}/json/version`, (res) => {
491
+ let body = "";
492
+ res.on("data", (c) => {
493
+ body += c.toString();
494
+ });
495
+ res.on("end", () => {
496
+ try {
497
+ const data = JSON.parse(body);
498
+ resolve2(data.webSocketDebuggerUrl);
499
+ } catch {
500
+ resolve2(void 0);
501
+ }
502
+ });
503
+ });
504
+ req.setTimeout(3e3, () => {
505
+ req.destroy();
506
+ resolve2(void 0);
507
+ });
508
+ req.on("error", () => resolve2(void 0));
509
+ });
510
+ }
511
+ function getCookieSyncState() {
512
+ return cookieSyncState;
513
+ }
514
+ function needsCookieResync(chromeDataDir) {
515
+ if (!cookieSyncState) return false;
516
+ const dataDir = chromeDataDir ?? getDefaultChromeDataDir();
517
+ const localStatePath = join(dataDir, "Local State");
518
+ if (!existsSync(localStatePath)) return false;
519
+ try {
520
+ const localState = JSON.parse(readFileSync(localStatePath, "utf-8"));
521
+ const profileName = localState.profile?.last_used ?? "Default";
522
+ if (profileName !== cookieSyncState.profileName) return true;
523
+ const cookiePath = join(dataDir, profileName, "Cookies");
524
+ if (!existsSync(cookiePath)) return false;
525
+ const mtime = statSync(cookiePath).mtimeMs;
526
+ return mtime !== cookieSyncState.cookieMtime;
527
+ } catch {
528
+ return false;
529
+ }
530
+ }
531
+ function syncCookiesAndTrack(destDataDir, chromeDataDir) {
532
+ const dataDir = chromeDataDir ?? getDefaultChromeDataDir();
533
+ try {
534
+ const localStatePath = join(dataDir, "Local State");
535
+ if (!existsSync(localStatePath)) return;
536
+ const localState = JSON.parse(readFileSync(localStatePath, "utf-8"));
537
+ const profileName = localState.profile?.last_used ?? "Default";
538
+ const srcProfileDir = join(dataDir, profileName);
539
+ if (!existsSync(join(srcProfileDir, "Cookies"))) return;
540
+ const destProfileDir = join(destDataDir, "Default");
541
+ mkdirSync(destProfileDir, { recursive: true });
542
+ const files = readdirSync(srcProfileDir).filter((f) => f.startsWith("Cookies"));
543
+ for (const file of files) {
544
+ copyFileSync(join(srcProfileDir, file), join(destProfileDir, file));
545
+ }
546
+ const mtime = statSync(join(srcProfileDir, "Cookies")).mtimeMs;
547
+ cookieSyncState = { profileName, cookieMtime: mtime };
548
+ } catch {
549
+ }
550
+ }
551
+ function syncCookiesToHeadless(headlessDataDir) {
552
+ syncCookiesAndTrack(headlessDataDir);
553
+ }
554
+ async function launchHeadlessChrome() {
555
+ const healthy = await isCDPHealthy(HEADLESS_PORT);
556
+ if (healthy) {
557
+ const ws = await getWsEndpoint(HEADLESS_PORT);
558
+ return { success: true, port: HEADLESS_PORT, wsEndpoint: ws };
559
+ }
560
+ const chromePath = findChrome();
561
+ if (!chromePath) {
562
+ return { success: false, port: HEADLESS_PORT, error: "Chrome not found." };
563
+ }
564
+ const dataDir = join(tmpdir(), "browsirai-headless");
565
+ mkdirSync(dataDir, { recursive: true });
566
+ syncCookiesToHeadless(dataDir);
567
+ const child = spawn(chromePath, [
568
+ "--headless=new",
569
+ `--remote-debugging-port=${HEADLESS_PORT}`,
570
+ "--remote-allow-origins=*",
571
+ `--user-data-dir=${dataDir}`,
572
+ "--no-first-run",
573
+ "--no-default-browser-check",
574
+ "--disable-extensions",
575
+ "--disable-gpu"
576
+ ], {
577
+ detached: true,
578
+ stdio: "ignore"
579
+ });
580
+ child.unref();
581
+ for (let i = 0; i < 75; i++) {
582
+ await new Promise((r) => setTimeout(r, 200));
583
+ if (await isCDPHealthy(HEADLESS_PORT)) {
584
+ const ws = await getWsEndpoint(HEADLESS_PORT);
585
+ return { success: true, port: HEADLESS_PORT, wsEndpoint: ws };
586
+ }
587
+ }
588
+ return { success: false, port: HEADLESS_PORT, error: "Headless Chrome did not start in 15s." };
589
+ }
590
+ async function connectChrome(options = {}) {
591
+ const targetPort = options.port ?? 9222;
592
+ const activePort = readDevToolsActivePort();
593
+ if (activePort) {
594
+ const healthy2 = await isCDPHealthy(activePort.port);
595
+ if (healthy2) {
596
+ return {
597
+ success: true,
598
+ port: activePort.port,
599
+ wsEndpoint: activePort.wsEndpoint,
600
+ activePortFound: true
601
+ };
602
+ }
603
+ }
604
+ const healthy = await isCDPHealthy(targetPort);
605
+ if (healthy) {
606
+ return {
607
+ success: true,
608
+ port: targetPort,
609
+ activePortFound: false
610
+ };
611
+ }
612
+ if (options.autoLaunch) {
613
+ const launch = await launchChromeWithDebugging(targetPort, options.headless);
614
+ if (launch.success) {
615
+ return {
616
+ success: true,
617
+ port: launch.port,
618
+ wsEndpoint: launch.wsEndpoint,
619
+ activePortFound: false
620
+ };
621
+ }
622
+ return {
623
+ success: false,
624
+ port: targetPort,
625
+ activePortFound: false,
626
+ error: launch.error
627
+ };
628
+ }
629
+ return {
630
+ success: false,
631
+ port: targetPort,
632
+ activePortFound: activePort !== null,
633
+ error: "Chrome remote debugging is not enabled. Enable it at chrome://inspect/#remote-debugging"
634
+ };
635
+ }
636
+ var CHROME_PATHS, SEPARATE_PORT, HEADLESS_PORT, cookieSyncState;
637
+ var init_chrome_launcher = __esm({
638
+ "src/chrome-launcher.ts"() {
639
+ "use strict";
640
+ CHROME_PATHS = {
641
+ darwin: [
642
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
643
+ "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
644
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
645
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
646
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
647
+ ],
648
+ linux: [
649
+ "google-chrome",
650
+ "google-chrome-stable",
651
+ "chromium",
652
+ "chromium-browser",
653
+ "microsoft-edge",
654
+ "brave-browser"
655
+ ],
656
+ win32: [
657
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
658
+ "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
659
+ "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
660
+ "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
661
+ ]
662
+ };
663
+ SEPARATE_PORT = 9444;
664
+ HEADLESS_PORT = 9333;
665
+ cookieSyncState = null;
666
+ }
667
+ });
668
+
669
+ // src/tools/browser-navigate.ts
670
+ async function browserNavigate(cdp, params) {
671
+ const { url, timeout = 8 } = params;
672
+ const timeoutMs = timeout * 1e3;
673
+ await cdp.send("Page.enable");
674
+ const result = await Promise.race([
675
+ performNavigation(cdp, url, params.waitUntil),
676
+ createTimeout(timeoutMs)
677
+ ]);
678
+ return result;
679
+ }
680
+ async function performNavigation(cdp, url, waitUntil) {
681
+ const navResponse = await cdp.send("Page.navigate", { url });
682
+ if (navResponse.errorText) {
683
+ throw new Error(`Navigation failed: ${navResponse.errorText}`);
684
+ }
685
+ const hasCrossDocNavigation = Boolean(navResponse.loaderId);
686
+ if (hasCrossDocNavigation) {
687
+ await waitForLoadCompletion(cdp, waitUntil);
688
+ }
689
+ return getPageInfo(cdp);
690
+ }
691
+ function waitForLoadCompletion(cdp, waitUntil) {
692
+ const eventName = waitUntil === "domcontentloaded" ? "Page.domContentEventFired" : "Page.loadEventFired";
693
+ return new Promise((resolve2) => {
694
+ let settled = false;
695
+ const handler = () => {
696
+ if (settled) return;
697
+ settled = true;
698
+ cdp.off(eventName, handler);
699
+ resolve2();
700
+ };
701
+ cdp.on(eventName, handler);
702
+ const poll = async () => {
703
+ while (!settled) {
704
+ try {
705
+ const response = await cdp.send("Runtime.evaluate", {
706
+ expression: "document.readyState",
707
+ returnByValue: true
708
+ });
709
+ const readyState = response.result.value;
710
+ const isLoadingState = readyState === "loading" || readyState === "interactive";
711
+ if (readyState === "complete" || !isLoadingState) {
712
+ if (!settled) {
713
+ settled = true;
714
+ cdp.off(eventName, handler);
715
+ resolve2();
716
+ }
717
+ return;
718
+ }
719
+ } catch {
720
+ }
721
+ if (!settled) {
722
+ await delay(POLL_INTERVAL_MS);
723
+ }
724
+ }
725
+ };
726
+ poll();
727
+ });
728
+ }
729
+ async function getPageInfo(cdp) {
730
+ const [titleResponse, urlResponse] = await Promise.all([
731
+ cdp.send("Runtime.evaluate", {
732
+ expression: "document.title"
733
+ }),
734
+ cdp.send("Runtime.evaluate", {
735
+ expression: "location.href"
736
+ })
737
+ ]);
738
+ return {
739
+ title: titleResponse.result.value ?? "",
740
+ url: urlResponse.result.value ?? ""
741
+ };
742
+ }
743
+ function createTimeout(ms) {
744
+ return new Promise((_resolve, reject) => {
745
+ setTimeout(() => {
746
+ reject(new Error(`Navigation timeout after ${ms}ms`));
747
+ }, ms);
748
+ });
749
+ }
750
+ function delay(ms) {
751
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
752
+ }
753
+ var POLL_INTERVAL_MS;
754
+ var init_browser_navigate = __esm({
755
+ "src/tools/browser-navigate.ts"() {
756
+ "use strict";
757
+ POLL_INTERVAL_MS = 100;
758
+ }
759
+ });
760
+
761
+ // src/tools/browser-screenshot.ts
762
+ async function detectDPR(cdp) {
763
+ try {
764
+ const metrics = await cdp.send("Page.getLayoutMetrics", {});
765
+ if (metrics.visualViewport && metrics.cssVisualViewport) {
766
+ const physicalWidth = metrics.visualViewport.clientWidth;
767
+ const cssWidth = metrics.cssVisualViewport.clientWidth;
768
+ if (cssWidth > 0 && physicalWidth > 0) {
769
+ const dpr = physicalWidth / cssWidth;
770
+ if (dpr >= 1) {
771
+ return dpr;
772
+ }
773
+ }
774
+ }
775
+ } catch {
776
+ }
777
+ try {
778
+ const evalResult = await cdp.send("Runtime.evaluate", {
779
+ expression: "window.devicePixelRatio",
780
+ returnByValue: true
781
+ });
782
+ if (evalResult.result.type === "number" && typeof evalResult.result.value === "number") {
783
+ return evalResult.result.value;
784
+ }
785
+ } catch {
786
+ }
787
+ return 1;
788
+ }
789
+ async function getElementBoxBySelector(cdp, selector) {
790
+ const doc = await cdp.send("DOM.getDocument", {});
791
+ const queryResult = await cdp.send("DOM.querySelector", {
792
+ nodeId: doc.root.nodeId,
793
+ selector
794
+ });
795
+ if (!queryResult.nodeId) {
796
+ throw new Error(`Element not found: ${selector}`);
797
+ }
798
+ const boxModel = await cdp.send("DOM.getBoxModel", {
799
+ nodeId: queryResult.nodeId
800
+ });
801
+ const content = boxModel.model.content;
802
+ const x = content[0];
803
+ const y = content[1];
804
+ const width = content[2] - content[0];
805
+ const height = content[5] - content[1];
806
+ return { x, y, width, height };
807
+ }
808
+ async function getElementBoxByRef(cdp, ref) {
809
+ const match = /^@e(\d+)$/.exec(ref);
810
+ if (!match) {
811
+ throw new Error(`Invalid ref format: ${ref}`);
812
+ }
813
+ const backendNodeId = parseInt(match[1], 10);
814
+ const resolved = await cdp.send("DOM.resolveNode", {
815
+ backendNodeId
816
+ });
817
+ const boxModel = await cdp.send("DOM.getBoxModel", {
818
+ backendNodeId
819
+ });
820
+ const content = boxModel.model.content;
821
+ const x = content[0];
822
+ const y = content[1];
823
+ const width = content[2] - content[0];
824
+ const height = content[5] - content[1];
825
+ return { x, y, width, height };
826
+ }
827
+ async function buildAnnotations(cdp) {
828
+ const axTree = await cdp.send("Accessibility.getFullAXTree", {}, { timeout: 1e4 });
829
+ const annotations = [];
830
+ let counter = 0;
831
+ for (const node of axTree.nodes) {
832
+ const role = node.role?.value;
833
+ if (role === "WebArea") {
834
+ continue;
835
+ }
836
+ counter++;
837
+ const ref = node.backendDOMNodeId ? `@e${node.backendDOMNodeId}` : `@e${counter}`;
838
+ const label = `[${counter}]`;
839
+ annotations.push({
840
+ ref,
841
+ label,
842
+ role: role ?? "unknown",
843
+ name: node.name?.value ?? ""
844
+ });
845
+ }
846
+ return annotations;
847
+ }
848
+ async function browserScreenshot(cdp, params) {
849
+ const format = params.format ?? "png";
850
+ const captureParams = {
851
+ format
852
+ };
853
+ if (format === "jpeg" && params.quality !== void 0) {
854
+ captureParams.quality = params.quality;
855
+ }
856
+ const _dpr = await detectDPR(cdp);
857
+ if (params.selector) {
858
+ const box = await getElementBoxBySelector(cdp, params.selector);
859
+ captureParams.clip = {
860
+ x: box.x,
861
+ y: box.y,
862
+ width: box.width,
863
+ height: box.height,
864
+ scale: 1
865
+ };
866
+ }
867
+ if (params.ref) {
868
+ const box = await getElementBoxByRef(cdp, params.ref);
869
+ captureParams.clip = {
870
+ x: box.x,
871
+ y: box.y,
872
+ width: box.width,
873
+ height: box.height,
874
+ scale: 1
875
+ };
876
+ }
877
+ if (params.fullPage) {
878
+ const metrics = await cdp.send("Page.getLayoutMetrics", {});
879
+ const contentWidth = metrics.cssContentSize?.width ?? metrics.contentSize.width;
880
+ const contentHeight = metrics.cssContentSize?.height ?? metrics.contentSize.height;
881
+ captureParams.clip = {
882
+ x: 0,
883
+ y: 0,
884
+ width: contentWidth,
885
+ height: contentHeight,
886
+ scale: 1
887
+ };
888
+ }
889
+ const screenshot = await cdp.send("Page.captureScreenshot", captureParams);
890
+ const result = {
891
+ base64: screenshot.data
892
+ };
893
+ if (params.annotate) {
894
+ result.annotations = await buildAnnotations(cdp);
895
+ }
896
+ return result;
897
+ }
898
+ var init_browser_screenshot = __esm({
899
+ "src/tools/browser-screenshot.ts"() {
900
+ "use strict";
901
+ }
902
+ });
903
+
904
+ // src/tools/browser-tabs.ts
905
+ function globToRegExp(pattern) {
906
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
907
+ const regexStr = escaped.replace(/\*/g, ".*");
908
+ return new RegExp(`^${regexStr}$`, "i");
909
+ }
910
+ async function browserTabs(cdp, params = {}) {
911
+ const response = await cdp.send("Target.getTargets");
912
+ let tabs = response.targetInfos.filter(
913
+ (target) => target.type === "page"
914
+ );
915
+ if (params.filter) {
916
+ const regex = globToRegExp(params.filter);
917
+ tabs = tabs.filter((target) => regex.test(target.url));
918
+ }
919
+ return {
920
+ tabs: tabs.map((target) => ({
921
+ id: target.targetId,
922
+ title: target.title,
923
+ url: target.url
924
+ }))
925
+ };
926
+ }
927
+ var init_browser_tabs = __esm({
928
+ "src/tools/browser-tabs.ts"() {
929
+ "use strict";
930
+ }
931
+ });
932
+
933
+ // src/tools/browser-eval.ts
934
+ function parseRemoteObject(obj) {
935
+ if (obj.type === "undefined") {
936
+ return void 0;
937
+ }
938
+ if (obj.type === "object" && obj.subtype === "null") {
939
+ return null;
940
+ }
941
+ if (obj.type === "object" && obj.subtype === "node") {
942
+ return obj.description ?? `[${obj.className ?? "Node"}]`;
943
+ }
944
+ if (obj.value !== void 0) {
945
+ return obj.value;
946
+ }
947
+ if (obj.description) {
948
+ return obj.description;
949
+ }
950
+ return void 0;
951
+ }
952
+ function formatException(details) {
953
+ if (details.exception?.description) {
954
+ return details.exception.description;
955
+ }
956
+ if (details.exception?.className) {
957
+ return `${details.exception.className}: ${details.text}`;
958
+ }
959
+ return details.text;
960
+ }
961
+ async function browserEval(cdp, params) {
962
+ let expression = params.expression;
963
+ if (params.base64) {
964
+ expression = atob(expression);
965
+ }
966
+ if (params.ref) {
967
+ return evalWithRef(cdp, expression, params.ref);
968
+ }
969
+ return evalGlobal(cdp, expression);
970
+ }
971
+ async function evalGlobal(cdp, expression) {
972
+ const response = await cdp.send("Runtime.evaluate", {
973
+ expression,
974
+ returnByValue: true,
975
+ awaitPromise: true
976
+ });
977
+ if (response.exceptionDetails) {
978
+ return {
979
+ error: formatException(response.exceptionDetails)
980
+ };
981
+ }
982
+ const value = parseRemoteObject(response.result);
983
+ return { result: value };
984
+ }
985
+ async function evalWithRef(cdp, functionDeclaration, ref) {
986
+ const match = /^@e(\d+)$/.exec(ref);
987
+ if (!match) {
988
+ return { error: `Invalid ref format: ${ref}` };
989
+ }
990
+ const backendNodeId = parseInt(match[1], 10);
991
+ const resolved = await cdp.send("DOM.resolveNode", {
992
+ backendNodeId
993
+ });
994
+ const objectId = resolved.object.objectId;
995
+ const response = await cdp.send("Runtime.callFunctionOn", {
996
+ objectId,
997
+ functionDeclaration,
998
+ returnByValue: true,
999
+ awaitPromise: true
1000
+ });
1001
+ if (response.exceptionDetails) {
1002
+ return {
1003
+ error: formatException(response.exceptionDetails)
1004
+ };
1005
+ }
1006
+ const value = parseRemoteObject(response.result);
1007
+ return { result: value };
1008
+ }
1009
+ var init_browser_eval = __esm({
1010
+ "src/tools/browser-eval.ts"() {
1011
+ "use strict";
1012
+ }
1013
+ });
1014
+
1015
+ // src/tools/browser-snapshot.ts
1016
+ function shouldShowAxNode(node, options) {
1017
+ const role = node.role?.value ?? "";
1018
+ if (role === "none") {
1019
+ return false;
1020
+ }
1021
+ if (role === "generic") {
1022
+ const name2 = node.name?.value ?? "";
1023
+ if (!name2) {
1024
+ return false;
1025
+ }
1026
+ }
1027
+ if (options?.compact && role === "InlineTextBox") {
1028
+ return false;
1029
+ }
1030
+ const name = node.name?.value ?? "";
1031
+ const value = node.value?.value ?? null;
1032
+ if (!name && (value === null || value === void 0 || value === "")) {
1033
+ return false;
1034
+ }
1035
+ return true;
1036
+ }
1037
+ function getChildNodeIds(node) {
1038
+ if (node.childIds && node.childIds.length > 0) {
1039
+ return node.childIds;
1040
+ }
1041
+ if (node.children && node.children.length > 0) {
1042
+ return node.children.map((c) => c.nodeId);
1043
+ }
1044
+ return [];
1045
+ }
1046
+ function formatNodeAttributes(node) {
1047
+ const parts = [];
1048
+ if (node.value?.value !== void 0 && node.value.value !== "") {
1049
+ parts.push(`value="${node.value.value}"`);
1050
+ }
1051
+ if (node.description?.value) {
1052
+ parts.push(`description="${node.description.value}"`);
1053
+ }
1054
+ if (node.properties) {
1055
+ for (const prop of node.properties) {
1056
+ switch (prop.name) {
1057
+ case "level":
1058
+ parts.push(`level=${prop.value.value}`);
1059
+ break;
1060
+ case "checked": {
1061
+ const val = prop.value.value;
1062
+ if (val === true || val === "true" || val === "mixed") {
1063
+ parts.push("checked");
1064
+ }
1065
+ break;
1066
+ }
1067
+ case "selected":
1068
+ if (prop.value.value === true) {
1069
+ parts.push("selected");
1070
+ }
1071
+ break;
1072
+ case "expanded":
1073
+ if (prop.value.value === true || prop.value.value === "true") {
1074
+ parts.push("expanded");
1075
+ } else {
1076
+ parts.push("collapsed");
1077
+ }
1078
+ break;
1079
+ default:
1080
+ break;
1081
+ }
1082
+ }
1083
+ }
1084
+ return parts.join(" ");
1085
+ }
1086
+ async function browserSnapshot(cdp, params) {
1087
+ const options = {
1088
+ interactive: params?.interactive,
1089
+ cursor: params?.cursor,
1090
+ compact: params?.compact,
1091
+ depth: params?.depth,
1092
+ selector: params?.selector
1093
+ };
1094
+ await cdp.send("Accessibility.enable");
1095
+ let axNodes;
1096
+ if (options.selector) {
1097
+ const docResult = await cdp.send("DOM.getDocument");
1098
+ const queryResult = await cdp.send("DOM.querySelector", {
1099
+ nodeId: docResult.root.nodeId,
1100
+ selector: options.selector
1101
+ });
1102
+ if (queryResult.nodeId === 0) {
1103
+ return { snapshot: `No element found for selector: ${options.selector}` };
1104
+ }
1105
+ const partialResult = await cdp.send("Accessibility.getPartialAXTree", {
1106
+ nodeId: queryResult.nodeId,
1107
+ fetchRelatives: true
1108
+ });
1109
+ axNodes = partialResult.nodes;
1110
+ } else {
1111
+ const fullResult = await cdp.send("Accessibility.getFullAXTree");
1112
+ axNodes = fullResult.nodes;
1113
+ }
1114
+ const totalElements = axNodes.filter(
1115
+ (n) => n.role?.value !== "WebArea"
1116
+ ).length;
1117
+ const nodeMap = /* @__PURE__ */ new Map();
1118
+ for (const node of axNodes) {
1119
+ nodeMap.set(node.nodeId, node);
1120
+ }
1121
+ let root;
1122
+ for (const node of axNodes) {
1123
+ if (!node.parentId) {
1124
+ root = node;
1125
+ break;
1126
+ }
1127
+ }
1128
+ if (!root) {
1129
+ root = axNodes[0];
1130
+ }
1131
+ if (!root) {
1132
+ return { snapshot: "" };
1133
+ }
1134
+ const lines = [];
1135
+ let refCounter = 0;
1136
+ const visited = /* @__PURE__ */ new Set();
1137
+ const maxDepth = options.depth ?? 100;
1138
+ function traverse(node, depth) {
1139
+ if (visited.has(node.nodeId)) {
1140
+ return;
1141
+ }
1142
+ visited.add(node.nodeId);
1143
+ if (depth > maxDepth) {
1144
+ return;
1145
+ }
1146
+ const role = node.role?.value ?? "";
1147
+ const isRoot = role === "WebArea";
1148
+ const showNode = isRoot || shouldShow(node, options);
1149
+ if (showNode && !isRoot) {
1150
+ refCounter++;
1151
+ const ref = node.backendDOMNodeId ? `@e${node.backendDOMNodeId}` : `@e${refCounter}`;
1152
+ const indent = " ".repeat(depth);
1153
+ const name = node.name?.value ?? "";
1154
+ const attrs = formatNodeAttributes(node);
1155
+ let line = `${indent}${ref} ${role}`;
1156
+ if (name) {
1157
+ line += ` "${name}"`;
1158
+ }
1159
+ if (attrs) {
1160
+ line += ` ${attrs}`;
1161
+ }
1162
+ lines.push(line);
1163
+ }
1164
+ const childNodeIds = getChildNodeIds(node);
1165
+ const nextDepth = isRoot ? depth : depth + 1;
1166
+ for (const childId of childNodeIds) {
1167
+ const childNode = nodeMap.get(childId);
1168
+ if (childNode) {
1169
+ traverse(childNode, nextDepth);
1170
+ }
1171
+ }
1172
+ }
1173
+ traverse(root, 0);
1174
+ const snapshot = lines.join("\n");
1175
+ const result = { snapshot };
1176
+ if (totalElements > 1e3) {
1177
+ result.truncated = true;
1178
+ result.totalElements = totalElements;
1179
+ }
1180
+ return result;
1181
+ }
1182
+ function shouldShow(node, options) {
1183
+ if (!shouldShowAxNode(node, { compact: options.compact })) {
1184
+ return false;
1185
+ }
1186
+ const role = node.role?.value ?? "";
1187
+ if (options.interactive) {
1188
+ if (INTERACTIVE_ROLES.has(role)) {
1189
+ return true;
1190
+ }
1191
+ if (options.cursor) {
1192
+ return hasCursorPointer(node);
1193
+ }
1194
+ return false;
1195
+ }
1196
+ if (options.cursor) {
1197
+ if (hasCursorPointer(node)) {
1198
+ return true;
1199
+ }
1200
+ }
1201
+ return true;
1202
+ }
1203
+ function hasCursorPointer(node) {
1204
+ if (!node.properties) return false;
1205
+ return node.properties.some(
1206
+ (p) => p.name === "cursor" && p.value.value === "pointer"
1207
+ );
1208
+ }
1209
+ var INTERACTIVE_ROLES;
1210
+ var init_browser_snapshot = __esm({
1211
+ "src/tools/browser-snapshot.ts"() {
1212
+ "use strict";
1213
+ INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
1214
+ "button",
1215
+ "link",
1216
+ "textbox",
1217
+ "checkbox",
1218
+ "radio",
1219
+ "combobox",
1220
+ "listbox",
1221
+ "menuitem",
1222
+ "menuitemcheckbox",
1223
+ "menuitemradio",
1224
+ "option",
1225
+ "searchbox",
1226
+ "slider",
1227
+ "spinbutton",
1228
+ "switch",
1229
+ "tab",
1230
+ "treeitem"
1231
+ ]);
1232
+ }
1233
+ });
1234
+
1235
+ // src/tools/browser-click.ts
1236
+ function calculateCenter(content) {
1237
+ const x = (content[0] + content[2] + content[4] + content[6]) / 4;
1238
+ const y = (content[1] + content[3] + content[5] + content[7]) / 4;
1239
+ return { x: Math.round(x), y: Math.round(y) };
1240
+ }
1241
+ async function resolveElementCoordinates(cdp, params) {
1242
+ let backendNodeId;
1243
+ let nodeId;
1244
+ if (params.ref) {
1245
+ const match = REF_PATTERN.exec(params.ref);
1246
+ if (!match) {
1247
+ throw new Error(`Invalid ref format: ${params.ref}`);
1248
+ }
1249
+ backendNodeId = parseInt(match[1], 10);
1250
+ await cdp.send("DOM.resolveNode", {
1251
+ backendNodeId
1252
+ });
1253
+ } else if (params.selector) {
1254
+ const docResponse = await cdp.send("DOM.getDocument");
1255
+ const queryResponse = await cdp.send("DOM.querySelector", {
1256
+ nodeId: docResponse.root.nodeId,
1257
+ selector: params.selector
1258
+ });
1259
+ if (!queryResponse.nodeId || queryResponse.nodeId === 0) {
1260
+ throw new Error(
1261
+ `Element not found: no element matches selector "${params.selector}"`
1262
+ );
1263
+ }
1264
+ nodeId = queryResponse.nodeId;
1265
+ } else {
1266
+ throw new Error("Either ref, selector, or coordinates (x, y) must be provided");
1267
+ }
1268
+ const scrollParams = {};
1269
+ if (backendNodeId !== void 0) {
1270
+ scrollParams.backendNodeId = backendNodeId;
1271
+ } else if (nodeId !== void 0) {
1272
+ scrollParams.nodeId = nodeId;
1273
+ }
1274
+ await cdp.send(
1275
+ "DOM.scrollIntoViewIfNeeded",
1276
+ scrollParams
1277
+ );
1278
+ const boxParams = {};
1279
+ if (backendNodeId !== void 0) {
1280
+ boxParams.backendNodeId = backendNodeId;
1281
+ } else if (nodeId !== void 0) {
1282
+ boxParams.nodeId = nodeId;
1283
+ }
1284
+ const boxResponse = await cdp.send("DOM.getBoxModel", boxParams);
1285
+ const { content, width, height } = boxResponse.model;
1286
+ if (width === 0 && height === 0) {
1287
+ throw new Error(
1288
+ "Element is not visible: zero-size box model. The element may be hidden or not rendered."
1289
+ );
1290
+ }
1291
+ return calculateCenter(content);
1292
+ }
1293
+ function computeModifiers(modifiers) {
1294
+ if (!modifiers || modifiers.length === 0) return 0;
1295
+ let bits = 0;
1296
+ for (const mod of modifiers) {
1297
+ const bit = MODIFIER_BITS[mod];
1298
+ if (bit !== void 0) {
1299
+ bits |= bit;
1300
+ }
1301
+ }
1302
+ return bits;
1303
+ }
1304
+ function delay2(ms) {
1305
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1306
+ }
1307
+ async function browserClick(cdp, params) {
1308
+ let x;
1309
+ let y;
1310
+ if (params.x !== void 0 && params.y !== void 0) {
1311
+ x = params.x;
1312
+ y = params.y;
1313
+ } else {
1314
+ const coords = await resolveElementCoordinates(cdp, params);
1315
+ x = coords.x;
1316
+ y = coords.y;
1317
+ }
1318
+ const button = params.button ?? "left";
1319
+ let effectiveModifiers = params.modifiers ? [...params.modifiers] : [];
1320
+ if (params.newTab) {
1321
+ const isMac = typeof process !== "undefined" && process.platform === "darwin";
1322
+ const newTabModifier = isMac ? "Meta" : "Control";
1323
+ if (!effectiveModifiers.includes(newTabModifier)) {
1324
+ effectiveModifiers.push(newTabModifier);
1325
+ }
1326
+ }
1327
+ const modifiers = computeModifiers(effectiveModifiers);
1328
+ if (params.doubleClick) {
1329
+ await performDoubleClick(cdp, x, y, button, modifiers);
1330
+ } else {
1331
+ await performClick(cdp, x, y, button, modifiers);
1332
+ }
1333
+ return { success: true };
1334
+ }
1335
+ async function performClick(cdp, x, y, button, modifiers, clickCount = 1) {
1336
+ await cdp.send("Input.dispatchMouseEvent", {
1337
+ type: "mouseMoved",
1338
+ x,
1339
+ y,
1340
+ modifiers
1341
+ });
1342
+ await cdp.send("Input.dispatchMouseEvent", {
1343
+ type: "mousePressed",
1344
+ x,
1345
+ y,
1346
+ button,
1347
+ clickCount,
1348
+ modifiers
1349
+ });
1350
+ await delay2(50);
1351
+ await cdp.send("Input.dispatchMouseEvent", {
1352
+ type: "mouseReleased",
1353
+ x,
1354
+ y,
1355
+ button,
1356
+ clickCount,
1357
+ modifiers
1358
+ });
1359
+ }
1360
+ async function performDoubleClick(cdp, x, y, button, modifiers) {
1361
+ await performClick(cdp, x, y, button, modifiers, 1);
1362
+ await performClick(cdp, x, y, button, modifiers, 2);
1363
+ }
1364
+ var MODIFIER_BITS, REF_PATTERN;
1365
+ var init_browser_click = __esm({
1366
+ "src/tools/browser-click.ts"() {
1367
+ "use strict";
1368
+ MODIFIER_BITS = {
1369
+ Alt: 1,
1370
+ Control: 2,
1371
+ Meta: 4,
1372
+ Shift: 8
1373
+ };
1374
+ REF_PATTERN = /^@e(\d+)$/;
1375
+ }
1376
+ });
1377
+
1378
+ // src/tools/browser-scroll.ts
1379
+ async function resolveSelector(cdp, selector) {
1380
+ const evalResult = await cdp.send("Runtime.evaluate", {
1381
+ expression: `document.querySelector(${JSON.stringify(selector)})`,
1382
+ returnByValue: false
1383
+ });
1384
+ if (evalResult.result.objectId && evalResult.result.subtype !== "null") {
1385
+ return { objectId: evalResult.result.objectId };
1386
+ }
1387
+ if (evalResult.result.subtype === "null" || evalResult.result.value === null) {
1388
+ throw new Error(`Element not found: ${selector}`);
1389
+ }
1390
+ try {
1391
+ const resolveResponse = await cdp.send("DOM.resolveNode", {
1392
+ backendNodeId: void 0
1393
+ });
1394
+ if (resolveResponse.object?.objectId) {
1395
+ return { objectId: resolveResponse.object.objectId };
1396
+ }
1397
+ } catch {
1398
+ }
1399
+ throw new Error(`Could not find element: ${selector}`);
1400
+ }
1401
+ async function browserScroll(cdp, params) {
1402
+ const { direction, selector } = params;
1403
+ const amount = params.amount ?? DEFAULT_SCROLL_AMOUNT;
1404
+ if (selector && !direction) {
1405
+ const { objectId } = await resolveSelector(cdp, selector);
1406
+ await cdp.send("Runtime.callFunctionOn", {
1407
+ objectId,
1408
+ functionDeclaration: `function() { this.scrollIntoView({ behavior: "smooth", block: "center" }); }`,
1409
+ returnByValue: true
1410
+ });
1411
+ return { success: true };
1412
+ }
1413
+ if (selector && direction) {
1414
+ const { objectId } = await resolveSelector(cdp, selector);
1415
+ const scrollX2 = direction === "left" ? -amount : direction === "right" ? amount : 0;
1416
+ const scrollY2 = direction === "up" ? -amount : direction === "down" ? amount : 0;
1417
+ await cdp.send("Runtime.callFunctionOn", {
1418
+ objectId,
1419
+ functionDeclaration: `function() { this.scrollBy(${scrollX2}, ${scrollY2}); }`,
1420
+ returnByValue: true
1421
+ });
1422
+ return { success: true };
1423
+ }
1424
+ const scrollX = direction === "left" ? -amount : direction === "right" ? amount : 0;
1425
+ const scrollY = direction === "up" ? -amount : direction === "down" ? amount : 0;
1426
+ await cdp.send("Runtime.evaluate", {
1427
+ expression: `window.scrollBy(${scrollX}, ${scrollY})`,
1428
+ returnByValue: true
1429
+ });
1430
+ return { success: true };
1431
+ }
1432
+ var DEFAULT_SCROLL_AMOUNT;
1433
+ var init_browser_scroll = __esm({
1434
+ "src/tools/browser-scroll.ts"() {
1435
+ "use strict";
1436
+ DEFAULT_SCROLL_AMOUNT = 300;
1437
+ }
1438
+ });
1439
+
1440
+ // src/tools/browser-html.ts
1441
+ async function browserHtml(cdp, params) {
1442
+ if (!params.selector) {
1443
+ const evalResult = await cdp.send("Runtime.evaluate", {
1444
+ expression: "document.documentElement.outerHTML",
1445
+ returnByValue: true
1446
+ });
1447
+ return { html: evalResult.result.value };
1448
+ }
1449
+ const doc = await cdp.send("DOM.getDocument", {});
1450
+ const queryResult = await cdp.send("DOM.querySelector", {
1451
+ nodeId: doc.root.nodeId,
1452
+ selector: params.selector
1453
+ });
1454
+ if (!queryResult.nodeId) {
1455
+ return {
1456
+ html: "",
1457
+ error: `Element not found: ${params.selector}`
1458
+ };
1459
+ }
1460
+ const outerResult = await cdp.send("DOM.getOuterHTML", {
1461
+ nodeId: queryResult.nodeId
1462
+ });
1463
+ return { html: outerResult.outerHTML };
1464
+ }
1465
+ var init_browser_html = __esm({
1466
+ "src/tools/browser-html.ts"() {
1467
+ "use strict";
1468
+ }
1469
+ });
1470
+
1471
+ // src/tools/browser-navigate-back.ts
1472
+ async function browserNavigateBack(cdp, params) {
1473
+ const direction = params.direction ?? "back";
1474
+ const history = await cdp.send("Page.getNavigationHistory");
1475
+ const targetIndex = direction === "back" ? history.currentIndex - 1 : history.currentIndex + 1;
1476
+ if (targetIndex < 0 || targetIndex >= history.entries.length) {
1477
+ return { success: false, url: history.entries[history.currentIndex]?.url };
1478
+ }
1479
+ const entry = history.entries[targetIndex];
1480
+ await cdp.send("Page.navigateToHistoryEntry", { entryId: entry.id });
1481
+ return { success: true, url: entry.url };
1482
+ }
1483
+ var init_browser_navigate_back = __esm({
1484
+ "src/tools/browser-navigate-back.ts"() {
1485
+ "use strict";
1486
+ }
1487
+ });
1488
+
1489
+ // src/tools/browser-press-key.ts
1490
+ function getKeyCode(key) {
1491
+ if (KEY_CODE_MAP[key] !== void 0) {
1492
+ return KEY_CODE_MAP[key];
1493
+ }
1494
+ if (key.length === 1) {
1495
+ return key.toUpperCase().charCodeAt(0);
1496
+ }
1497
+ return 0;
1498
+ }
1499
+ function getCode(key) {
1500
+ if (KEY_TO_CODE[key] !== void 0) {
1501
+ return KEY_TO_CODE[key];
1502
+ }
1503
+ if (key.length === 1) {
1504
+ const upper = key.toUpperCase();
1505
+ if (upper >= "A" && upper <= "Z") {
1506
+ return `Key${upper}`;
1507
+ }
1508
+ if (upper >= "0" && upper <= "9") {
1509
+ return `Digit${upper}`;
1510
+ }
1511
+ }
1512
+ return key;
1513
+ }
1514
+ function getKeyText(key) {
1515
+ if (KEY_TEXT_MAP[key] !== void 0) {
1516
+ return KEY_TEXT_MAP[key] || void 0;
1517
+ }
1518
+ if (key.length === 1) {
1519
+ return key;
1520
+ }
1521
+ return void 0;
1522
+ }
1523
+ async function browserPressKey(cdp, params) {
1524
+ const parts = params.key.split("+");
1525
+ const mainKey = parts[parts.length - 1];
1526
+ const modifierKeys = parts.slice(0, -1);
1527
+ let modifiers = 0;
1528
+ for (const mod of modifierKeys) {
1529
+ if (MODIFIER_BITS2[mod] !== void 0) {
1530
+ modifiers |= MODIFIER_BITS2[mod];
1531
+ }
1532
+ }
1533
+ for (const mod of modifierKeys) {
1534
+ await cdp.send("Input.dispatchKeyEvent", {
1535
+ type: "keyDown",
1536
+ key: mod,
1537
+ code: getCode(mod),
1538
+ windowsVirtualKeyCode: getKeyCode(mod),
1539
+ modifiers
1540
+ });
1541
+ }
1542
+ const text = getKeyText(mainKey);
1543
+ const keyDownParams = {
1544
+ type: "keyDown",
1545
+ key: mainKey,
1546
+ code: getCode(mainKey),
1547
+ windowsVirtualKeyCode: getKeyCode(mainKey),
1548
+ modifiers
1549
+ };
1550
+ if (text !== void 0) {
1551
+ keyDownParams.text = text;
1552
+ }
1553
+ await cdp.send("Input.dispatchKeyEvent", keyDownParams);
1554
+ if (text && modifierKeys.length === 0) {
1555
+ await cdp.send("Input.dispatchKeyEvent", {
1556
+ type: "char",
1557
+ key: mainKey,
1558
+ code: getCode(mainKey),
1559
+ windowsVirtualKeyCode: getKeyCode(mainKey),
1560
+ modifiers,
1561
+ text
1562
+ });
1563
+ }
1564
+ await cdp.send("Input.dispatchKeyEvent", {
1565
+ type: "keyUp",
1566
+ key: mainKey,
1567
+ code: getCode(mainKey),
1568
+ windowsVirtualKeyCode: getKeyCode(mainKey),
1569
+ modifiers
1570
+ });
1571
+ for (let i = modifierKeys.length - 1; i >= 0; i--) {
1572
+ const mod = modifierKeys[i];
1573
+ await cdp.send("Input.dispatchKeyEvent", {
1574
+ type: "keyUp",
1575
+ key: mod,
1576
+ code: getCode(mod),
1577
+ windowsVirtualKeyCode: getKeyCode(mod),
1578
+ modifiers: 0
1579
+ });
1580
+ }
1581
+ return { success: true };
1582
+ }
1583
+ var KEY_CODE_MAP, KEY_TO_CODE, KEY_TEXT_MAP, MODIFIER_BITS2;
1584
+ var init_browser_press_key = __esm({
1585
+ "src/tools/browser-press-key.ts"() {
1586
+ "use strict";
1587
+ KEY_CODE_MAP = {
1588
+ Enter: 13,
1589
+ Tab: 9,
1590
+ Escape: 27,
1591
+ Backspace: 8,
1592
+ Delete: 46,
1593
+ ArrowLeft: 37,
1594
+ ArrowUp: 38,
1595
+ ArrowRight: 39,
1596
+ ArrowDown: 40,
1597
+ Home: 36,
1598
+ End: 35,
1599
+ PageUp: 33,
1600
+ PageDown: 34,
1601
+ Insert: 45,
1602
+ Space: 32,
1603
+ " ": 32,
1604
+ F1: 112,
1605
+ F2: 113,
1606
+ F3: 114,
1607
+ F4: 115,
1608
+ F5: 116,
1609
+ F6: 117,
1610
+ F7: 118,
1611
+ F8: 119,
1612
+ F9: 120,
1613
+ F10: 121,
1614
+ F11: 122,
1615
+ F12: 123,
1616
+ Control: 17,
1617
+ Shift: 16,
1618
+ Alt: 18,
1619
+ Meta: 91
1620
+ };
1621
+ KEY_TO_CODE = {
1622
+ Enter: "Enter",
1623
+ Tab: "Tab",
1624
+ Escape: "Escape",
1625
+ Backspace: "Backspace",
1626
+ Delete: "Delete",
1627
+ ArrowLeft: "ArrowLeft",
1628
+ ArrowUp: "ArrowUp",
1629
+ ArrowRight: "ArrowRight",
1630
+ ArrowDown: "ArrowDown",
1631
+ Home: "Home",
1632
+ End: "End",
1633
+ PageUp: "PageUp",
1634
+ PageDown: "PageDown",
1635
+ Insert: "Insert",
1636
+ Space: "Space",
1637
+ " ": "Space",
1638
+ Control: "ControlLeft",
1639
+ Shift: "ShiftLeft",
1640
+ Alt: "AltLeft",
1641
+ Meta: "MetaLeft"
1642
+ };
1643
+ KEY_TEXT_MAP = {
1644
+ Enter: "\r",
1645
+ Tab: " ",
1646
+ Escape: "",
1647
+ Space: " ",
1648
+ Backspace: "\b"
1649
+ };
1650
+ MODIFIER_BITS2 = {
1651
+ Alt: 1,
1652
+ Control: 2,
1653
+ Meta: 4,
1654
+ Shift: 8
1655
+ };
1656
+ }
1657
+ });
1658
+
1659
+ // src/tools/browser-hover.ts
1660
+ function calculateCenter2(content) {
1661
+ const x = (content[0] + content[2] + content[4] + content[6]) / 4;
1662
+ const y = (content[1] + content[3] + content[5] + content[7]) / 4;
1663
+ return { x: Math.round(x), y: Math.round(y) };
1664
+ }
1665
+ async function resolveElementCoordinates2(cdp, params) {
1666
+ let backendNodeId;
1667
+ let nodeId;
1668
+ if (params.ref) {
1669
+ const match = REF_PATTERN2.exec(params.ref);
1670
+ if (!match) {
1671
+ throw new Error(`Invalid ref format: ${params.ref}`);
1672
+ }
1673
+ backendNodeId = parseInt(match[1], 10);
1674
+ await cdp.send("DOM.resolveNode", {
1675
+ backendNodeId
1676
+ });
1677
+ } else if (params.selector) {
1678
+ const docResponse = await cdp.send("DOM.getDocument");
1679
+ const queryResponse = await cdp.send("DOM.querySelector", {
1680
+ nodeId: docResponse.root.nodeId,
1681
+ selector: params.selector
1682
+ });
1683
+ if (!queryResponse.nodeId || queryResponse.nodeId === 0) {
1684
+ throw new Error(
1685
+ `Element not found: no element matches selector "${params.selector}"`
1686
+ );
1687
+ }
1688
+ nodeId = queryResponse.nodeId;
1689
+ } else {
1690
+ throw new Error("Either ref or selector must be provided");
1691
+ }
1692
+ const scrollParams = {};
1693
+ if (backendNodeId !== void 0) {
1694
+ scrollParams.backendNodeId = backendNodeId;
1695
+ } else if (nodeId !== void 0) {
1696
+ scrollParams.nodeId = nodeId;
1697
+ }
1698
+ await cdp.send("DOM.scrollIntoViewIfNeeded", scrollParams);
1699
+ const boxParams = {};
1700
+ if (backendNodeId !== void 0) {
1701
+ boxParams.backendNodeId = backendNodeId;
1702
+ } else if (nodeId !== void 0) {
1703
+ boxParams.nodeId = nodeId;
1704
+ }
1705
+ const boxResponse = await cdp.send("DOM.getBoxModel", boxParams);
1706
+ return calculateCenter2(boxResponse.model.content);
1707
+ }
1708
+ async function browserHover(cdp, params) {
1709
+ const { x, y } = await resolveElementCoordinates2(cdp, params);
1710
+ await cdp.send("Input.dispatchMouseEvent", {
1711
+ type: "mouseMoved",
1712
+ x,
1713
+ y
1714
+ });
1715
+ return { success: true };
1716
+ }
1717
+ var REF_PATTERN2;
1718
+ var init_browser_hover = __esm({
1719
+ "src/tools/browser-hover.ts"() {
1720
+ "use strict";
1721
+ REF_PATTERN2 = /^@?e(\d+)$/;
1722
+ }
1723
+ });
1724
+
1725
+ // src/tools/browser-resize.ts
1726
+ async function browserResize(cdp, params) {
1727
+ let width;
1728
+ let height;
1729
+ if (params.preset?.toLowerCase() === "reset") {
1730
+ await cdp.send("Emulation.clearDeviceMetricsOverride");
1731
+ return { success: true, width: 0, height: 0 };
1732
+ }
1733
+ if (params.preset) {
1734
+ const preset = PRESETS[params.preset.toLowerCase()];
1735
+ if (!preset) {
1736
+ throw new Error(
1737
+ `Unknown preset "${params.preset}". Available: ${Object.keys(PRESETS).join(", ")}, reset`
1738
+ );
1739
+ }
1740
+ width = preset.width;
1741
+ height = preset.height;
1742
+ } else {
1743
+ width = params.width ?? 1280;
1744
+ height = params.height ?? 720;
1745
+ }
1746
+ if (params.width !== void 0) width = params.width;
1747
+ if (params.height !== void 0) height = params.height;
1748
+ const deviceScaleFactor = params.deviceScaleFactor ?? 0;
1749
+ const mobile = width < 768;
1750
+ await cdp.send("Emulation.setDeviceMetricsOverride", {
1751
+ width,
1752
+ height,
1753
+ deviceScaleFactor,
1754
+ mobile
1755
+ });
1756
+ return {
1757
+ success: true,
1758
+ width,
1759
+ height
1760
+ };
1761
+ }
1762
+ var PRESETS;
1763
+ var init_browser_resize = __esm({
1764
+ "src/tools/browser-resize.ts"() {
1765
+ "use strict";
1766
+ PRESETS = {
1767
+ mobile: { width: 375, height: 667 },
1768
+ tablet: { width: 768, height: 1024 },
1769
+ desktop: { width: 1280, height: 720 },
1770
+ fullhd: { width: 1920, height: 1080 }
1771
+ };
1772
+ }
1773
+ });
1774
+
1775
+ // src/tools/browser-close.ts
1776
+ async function browserClose(cdp, params) {
1777
+ let closedCount = 0;
1778
+ if (params.closeAll) {
1779
+ const targetsResponse = await cdp.send("Target.getTargets");
1780
+ const pageTargets = targetsResponse.targetInfos.filter(
1781
+ (t) => t.type === "page"
1782
+ );
1783
+ for (const target of pageTargets) {
1784
+ try {
1785
+ await cdp.send("Target.closeTarget", {
1786
+ targetId: target.targetId
1787
+ });
1788
+ closedCount++;
1789
+ } catch {
1790
+ }
1791
+ }
1792
+ } else if (params.targetId) {
1793
+ await cdp.send("Target.closeTarget", {
1794
+ targetId: params.targetId
1795
+ });
1796
+ closedCount = 1;
1797
+ } else {
1798
+ const targetsResponse = await cdp.send("Target.getTargets");
1799
+ const pageTargets = targetsResponse.targetInfos.filter(
1800
+ (t) => t.type === "page"
1801
+ );
1802
+ if (pageTargets.length === 0) {
1803
+ throw new Error("No page targets found to close");
1804
+ }
1805
+ const activeTarget = pageTargets.find((t) => t.attached) ?? pageTargets[0];
1806
+ await cdp.send("Target.closeTarget", {
1807
+ targetId: activeTarget.targetId
1808
+ });
1809
+ closedCount = 1;
1810
+ }
1811
+ return {
1812
+ success: true,
1813
+ closedTargets: closedCount
1814
+ };
1815
+ }
1816
+ var init_browser_close = __esm({
1817
+ "src/tools/browser-close.ts"() {
1818
+ "use strict";
1819
+ }
1820
+ });
1821
+
1822
+ // src/tools/browser-fill-form.ts
1823
+ function parseRef(ref) {
1824
+ const match = ref.match(/@?e(\d+)/);
1825
+ if (!match) {
1826
+ throw new Error(`Invalid ref format: ${ref}`);
1827
+ }
1828
+ return parseInt(match[1], 10);
1829
+ }
1830
+ async function resolveObjectId(cdp, field) {
1831
+ if (field.ref) {
1832
+ const backendNodeId = parseRef(field.ref);
1833
+ const result = await cdp.send("DOM.resolveNode", {
1834
+ backendNodeId
1835
+ });
1836
+ return result.object.objectId;
1837
+ }
1838
+ if (field.selector) {
1839
+ const evalResult = await cdp.send("Runtime.evaluate", {
1840
+ expression: `document.querySelector(${JSON.stringify(field.selector)})`,
1841
+ returnByValue: false
1842
+ });
1843
+ if (evalResult.result.objectId) {
1844
+ return evalResult.result.objectId;
1845
+ }
1846
+ const docResult = await cdp.send(
1847
+ "DOM.getDocument",
1848
+ {}
1849
+ );
1850
+ const queryResult = await cdp.send("DOM.querySelector", {
1851
+ nodeId: docResult.root.nodeId,
1852
+ selector: field.selector
1853
+ });
1854
+ const resolveResult = await cdp.send("DOM.resolveNode", {
1855
+ nodeId: queryResult.nodeId
1856
+ });
1857
+ return resolveResult.object.objectId;
1858
+ }
1859
+ throw new Error(`Field "${field.name}" has neither ref nor selector`);
1860
+ }
1861
+ async function isReadonlyOrDisabled(cdp, objectId) {
1862
+ const result = await cdp.send("Runtime.callFunctionOn", {
1863
+ objectId,
1864
+ functionDeclaration: `function() { return this.readOnly || this.disabled; }`,
1865
+ returnByValue: true
1866
+ });
1867
+ return result.result.value === true;
1868
+ }
1869
+ async function clickElement(cdp, field) {
1870
+ let backendNodeId;
1871
+ if (field.ref) {
1872
+ backendNodeId = parseRef(field.ref);
1873
+ }
1874
+ if (backendNodeId !== void 0) {
1875
+ await cdp.send("DOM.scrollIntoViewIfNeeded", {
1876
+ backendNodeId
1877
+ });
1878
+ }
1879
+ const boxResult = await cdp.send("DOM.getBoxModel", {
1880
+ backendNodeId
1881
+ });
1882
+ const quad = boxResult.model.content;
1883
+ const centerX = (quad[0] + quad[2] + quad[4] + quad[6]) / 4;
1884
+ const centerY = (quad[1] + quad[3] + quad[5] + quad[7]) / 4;
1885
+ await cdp.send("Input.dispatchMouseEvent", {
1886
+ type: "mousePressed",
1887
+ x: centerX,
1888
+ y: centerY,
1889
+ button: "left",
1890
+ clickCount: 1
1891
+ });
1892
+ await cdp.send("Input.dispatchMouseEvent", {
1893
+ type: "mouseReleased",
1894
+ x: centerX,
1895
+ y: centerY,
1896
+ button: "left",
1897
+ clickCount: 1
1898
+ });
1899
+ }
1900
+ async function fillTextbox(cdp, field, objectId) {
1901
+ if (field.ref) {
1902
+ const backendNodeId = parseRef(field.ref);
1903
+ await cdp.send("DOM.focus", {
1904
+ backendNodeId
1905
+ });
1906
+ } else {
1907
+ await cdp.send("Runtime.callFunctionOn", {
1908
+ objectId,
1909
+ functionDeclaration: `function() { this.focus(); }`,
1910
+ returnByValue: true
1911
+ });
1912
+ }
1913
+ await cdp.send("Runtime.callFunctionOn", {
1914
+ objectId,
1915
+ functionDeclaration: `function() { this.value = ''; this.dispatchEvent(new Event('input', { bubbles: true })); }`,
1916
+ returnByValue: true
1917
+ });
1918
+ await cdp.send("Input.insertText", {
1919
+ text: field.value
1920
+ });
1921
+ await cdp.send("Runtime.callFunctionOn", {
1922
+ objectId,
1923
+ functionDeclaration: `function() {
1924
+ this.dispatchEvent(new Event('input', { bubbles: true }));
1925
+ this.dispatchEvent(new Event('change', { bubbles: true }));
1926
+ }`,
1927
+ returnByValue: true
1928
+ });
1929
+ }
1930
+ async function fillCombobox(cdp, _field, objectId, value) {
1931
+ await cdp.send("Runtime.callFunctionOn", {
1932
+ objectId,
1933
+ functionDeclaration: `function(val) {
1934
+ this.value = val;
1935
+ this.dispatchEvent(new Event('input', { bubbles: true }));
1936
+ this.dispatchEvent(new Event('change', { bubbles: true }));
1937
+ return [val];
1938
+ }`,
1939
+ arguments: [{ value }],
1940
+ returnByValue: true
1941
+ });
1942
+ }
1943
+ async function fillSlider(cdp, _field, objectId, value) {
1944
+ await cdp.send("Runtime.callFunctionOn", {
1945
+ objectId,
1946
+ functionDeclaration: `function(val) {
1947
+ this.value = val;
1948
+ this.dispatchEvent(new Event('input', { bubbles: true }));
1949
+ this.dispatchEvent(new Event('change', { bubbles: true }));
1950
+ }`,
1951
+ arguments: [{ value }],
1952
+ returnByValue: true
1953
+ });
1954
+ }
1955
+ async function browserFillForm(cdp, params) {
1956
+ let filledCount = 0;
1957
+ const errors = [];
1958
+ for (const field of params.fields) {
1959
+ try {
1960
+ const objectId = await resolveObjectId(cdp, field);
1961
+ switch (field.type) {
1962
+ case "textbox": {
1963
+ const isBlocked = await isReadonlyOrDisabled(cdp, objectId);
1964
+ if (isBlocked) {
1965
+ errors.push({
1966
+ field: field.name,
1967
+ error: `Cannot fill readonly or disabled field "${field.name}"`
1968
+ });
1969
+ continue;
1970
+ }
1971
+ await fillTextbox(cdp, field, objectId);
1972
+ filledCount++;
1973
+ break;
1974
+ }
1975
+ case "checkbox": {
1976
+ await clickElement(cdp, field);
1977
+ filledCount++;
1978
+ break;
1979
+ }
1980
+ case "radio": {
1981
+ await clickElement(cdp, field);
1982
+ filledCount++;
1983
+ break;
1984
+ }
1985
+ case "combobox": {
1986
+ await fillCombobox(cdp, field, objectId, field.value);
1987
+ filledCount++;
1988
+ break;
1989
+ }
1990
+ case "slider": {
1991
+ await fillSlider(cdp, field, objectId, field.value);
1992
+ filledCount++;
1993
+ break;
1994
+ }
1995
+ default: {
1996
+ await fillTextbox(cdp, field, objectId);
1997
+ filledCount++;
1998
+ break;
1999
+ }
2000
+ }
2001
+ } catch (err) {
2002
+ errors.push({
2003
+ field: field.name,
2004
+ error: err instanceof Error ? err.message : `Unknown error filling ${field.name}`
2005
+ });
2006
+ }
2007
+ }
2008
+ return {
2009
+ success: errors.length === 0,
2010
+ filledCount,
2011
+ errors: errors.length > 0 ? errors : void 0
2012
+ };
2013
+ }
2014
+ var init_browser_fill_form = __esm({
2015
+ "src/tools/browser-fill-form.ts"() {
2016
+ "use strict";
2017
+ }
2018
+ });
2019
+
2020
+ // src/tools/browser-type.ts
2021
+ function parseRef2(ref) {
2022
+ const match = ref.match(/@e(\d+)/);
2023
+ if (!match) {
2024
+ throw new Error(`Invalid ref format: ${ref}`);
2025
+ }
2026
+ return parseInt(match[1], 10);
2027
+ }
2028
+ function getKeyCode2(key) {
2029
+ if (key.length === 1) {
2030
+ return key.toUpperCase().charCodeAt(0);
2031
+ }
2032
+ const codes = {
2033
+ Enter: 13,
2034
+ Tab: 9,
2035
+ Backspace: 8
2036
+ };
2037
+ return codes[key] ?? 0;
2038
+ }
2039
+ function getCode2(key) {
2040
+ if (key.length === 1) {
2041
+ const upper = key.toUpperCase();
2042
+ if (upper >= "A" && upper <= "Z") return `Key${upper}`;
2043
+ if (upper >= "0" && upper <= "9") return `Digit${upper}`;
2044
+ }
2045
+ return key;
2046
+ }
2047
+ function delay3(ms) {
2048
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
2049
+ }
2050
+ async function dispatchCharEvents(cdp, char) {
2051
+ const keyCode = getKeyCode2(char);
2052
+ const code = getCode2(char);
2053
+ await cdp.send("Input.dispatchKeyEvent", {
2054
+ type: "keyDown",
2055
+ key: char,
2056
+ code,
2057
+ windowsVirtualKeyCode: keyCode,
2058
+ text: char
2059
+ });
2060
+ await cdp.send("Input.dispatchKeyEvent", {
2061
+ type: "char",
2062
+ key: char,
2063
+ code,
2064
+ windowsVirtualKeyCode: keyCode,
2065
+ text: char
2066
+ });
2067
+ await cdp.send("Input.dispatchKeyEvent", {
2068
+ type: "keyUp",
2069
+ key: char,
2070
+ code,
2071
+ windowsVirtualKeyCode: keyCode
2072
+ });
2073
+ }
2074
+ async function browserType(cdp, params) {
2075
+ if (params.ref) {
2076
+ const backendNodeId = parseRef2(params.ref);
2077
+ await cdp.send("DOM.focus", {
2078
+ backendNodeId
2079
+ });
2080
+ }
2081
+ if (!params.ref && params.selector) {
2082
+ const evalResult = await cdp.send("Runtime.evaluate", {
2083
+ expression: `document.querySelector(${JSON.stringify(params.selector)})`,
2084
+ returnByValue: false
2085
+ });
2086
+ if (evalResult.result.objectId) {
2087
+ await cdp.send("Runtime.callFunctionOn", {
2088
+ objectId: evalResult.result.objectId,
2089
+ functionDeclaration: `function() { this.focus(); }`,
2090
+ returnByValue: true
2091
+ });
2092
+ }
2093
+ }
2094
+ if (params.slowly) {
2095
+ for (let i = 0; i < params.text.length; i++) {
2096
+ await dispatchCharEvents(cdp, params.text[i]);
2097
+ if (i < params.text.length - 1) {
2098
+ await delay3(50);
2099
+ }
2100
+ }
2101
+ } else {
2102
+ await cdp.send("Input.insertText", {
2103
+ text: params.text
2104
+ });
2105
+ }
2106
+ if (params.submit) {
2107
+ await cdp.send("Input.dispatchKeyEvent", {
2108
+ type: "keyDown",
2109
+ key: "Enter",
2110
+ code: "Enter",
2111
+ windowsVirtualKeyCode: 13
2112
+ });
2113
+ await cdp.send("Input.dispatchKeyEvent", {
2114
+ type: "keyUp",
2115
+ key: "Enter",
2116
+ code: "Enter",
2117
+ windowsVirtualKeyCode: 13
2118
+ });
2119
+ }
2120
+ return { success: true };
2121
+ }
2122
+ var init_browser_type = __esm({
2123
+ "src/tools/browser-type.ts"() {
2124
+ "use strict";
2125
+ }
2126
+ });
2127
+
2128
+ // src/tools/browser-select-option.ts
2129
+ function refToBackendNodeId(ref) {
2130
+ const match = /^@?e(\d+)$/.exec(ref);
2131
+ if (!match) {
2132
+ throw new Error(`Invalid ref format: ${ref}`);
2133
+ }
2134
+ return parseInt(match[1], 10);
2135
+ }
2136
+ async function browserSelectOption(cdp, params) {
2137
+ const backendNodeId = refToBackendNodeId(params.ref);
2138
+ const resolveResponse = await cdp.send("DOM.resolveNode", {
2139
+ backendNodeId
2140
+ });
2141
+ const objectId = resolveResponse.object.objectId;
2142
+ const valuesJson = JSON.stringify(params.values);
2143
+ const result = await cdp.send("Runtime.callFunctionOn", {
2144
+ objectId,
2145
+ functionDeclaration: `function() {
2146
+ var select = this;
2147
+ if (select.tagName !== 'SELECT') {
2148
+ throw new Error('Element is not a SELECT');
2149
+ }
2150
+ var values = ${valuesJson};
2151
+ var matched = [];
2152
+
2153
+ for (var i = 0; i < select.options.length; i++) {
2154
+ var option = select.options[i];
2155
+ var isMatch = values.indexOf(option.value) !== -1 ||
2156
+ values.indexOf(option.textContent.trim()) !== -1;
2157
+ option.selected = isMatch;
2158
+ if (isMatch) {
2159
+ matched.push(option.value);
2160
+ }
2161
+ }
2162
+
2163
+ select.dispatchEvent(new Event('input', { bubbles: true }));
2164
+ select.dispatchEvent(new Event('change', { bubbles: true }));
2165
+
2166
+ return matched;
2167
+ }`,
2168
+ returnByValue: true
2169
+ });
2170
+ const selected = result.result.value ?? [];
2171
+ return {
2172
+ success: true,
2173
+ selected
2174
+ };
2175
+ }
2176
+ var init_browser_select_option = __esm({
2177
+ "src/tools/browser-select-option.ts"() {
2178
+ "use strict";
2179
+ }
2180
+ });
2181
+
2182
+ // src/tools/browser-wait-for.ts
2183
+ function normalizeTimeoutMs(timeout) {
2184
+ if (timeout > 60) {
2185
+ return timeout;
2186
+ }
2187
+ return timeout * 1e3;
2188
+ }
2189
+ async function browserWaitFor(cdp, params) {
2190
+ const timeoutMs = normalizeTimeoutMs(params.timeout ?? DEFAULT_TIMEOUT_S);
2191
+ const start = Date.now();
2192
+ if (params.time !== void 0) {
2193
+ const delayMs = params.time * 1e3;
2194
+ await delay4(delayMs);
2195
+ return { success: true, elapsed: Date.now() - start };
2196
+ }
2197
+ const condition = buildCondition(params);
2198
+ while (true) {
2199
+ const elapsed = Date.now() - start;
2200
+ if (elapsed >= timeoutMs) {
2201
+ throw new Error(
2202
+ `Timeout after ${timeoutMs}ms waiting for condition: ${describeCondition(params)}`
2203
+ );
2204
+ }
2205
+ let met = false;
2206
+ try {
2207
+ met = await evaluateCondition(cdp, condition);
2208
+ } catch {
2209
+ }
2210
+ if (met) {
2211
+ return { success: true, elapsed: Date.now() - start };
2212
+ }
2213
+ await delay4(POLL_INTERVAL_MS2);
2214
+ }
2215
+ }
2216
+ function buildCondition(params) {
2217
+ if (params.url !== void 0) {
2218
+ return { kind: "url", expression: params.url };
2219
+ }
2220
+ if (params.fn !== void 0) {
2221
+ return {
2222
+ kind: "fn",
2223
+ expression: `Boolean(${params.fn})`
2224
+ };
2225
+ }
2226
+ if (params.selector !== void 0 && params.state === "hidden") {
2227
+ return {
2228
+ kind: "selectorHidden",
2229
+ expression: buildVisibilityCheck(params.selector)
2230
+ };
2231
+ }
2232
+ if (params.selector !== void 0 && params.visible) {
2233
+ return {
2234
+ kind: "selectorVisible",
2235
+ expression: buildVisibilityCheck(params.selector)
2236
+ };
2237
+ }
2238
+ if (params.selector !== void 0) {
2239
+ return {
2240
+ kind: "selector",
2241
+ expression: `document.querySelector(${JSON.stringify(params.selector)})`
2242
+ };
2243
+ }
2244
+ if (params.text !== void 0) {
2245
+ return {
2246
+ kind: "text",
2247
+ expression: `document.body && document.body.innerText.includes(${JSON.stringify(params.text)})`
2248
+ };
2249
+ }
2250
+ if (params.textGone !== void 0) {
2251
+ return {
2252
+ kind: "textGone",
2253
+ expression: `document.body && !document.body.innerText.includes(${JSON.stringify(params.textGone)})`
2254
+ };
2255
+ }
2256
+ if (params.networkIdle) {
2257
+ return {
2258
+ kind: "networkIdle",
2259
+ expression: "true"
2260
+ // Simplified: check via Runtime.evaluate
2261
+ };
2262
+ }
2263
+ if (params.loadState !== void 0) {
2264
+ return {
2265
+ kind: "loadState",
2266
+ expression: params.loadState
2267
+ };
2268
+ }
2269
+ if (params.load) {
2270
+ return {
2271
+ kind: "load",
2272
+ expression: "document.readyState"
2273
+ };
2274
+ }
2275
+ throw new Error("browserWaitFor: no wait condition specified");
2276
+ }
2277
+ function buildVisibilityCheck(selector) {
2278
+ const sel = JSON.stringify(selector);
2279
+ return `(function() {
2280
+ var el = document.querySelector(${sel});
2281
+ if (!el) return false;
2282
+ var style = window.getComputedStyle(el);
2283
+ return style.display !== 'none' && style.visibility !== 'hidden' && el.offsetParent !== null;
2284
+ })()`;
2285
+ }
2286
+ async function evaluateCondition(cdp, condition) {
2287
+ if (condition.kind === "url") {
2288
+ return evaluateUrlCondition(cdp, condition.expression);
2289
+ }
2290
+ if (condition.kind === "load") {
2291
+ return evaluateLoadCondition(cdp, condition.expression);
2292
+ }
2293
+ if (condition.kind === "loadState") {
2294
+ return evaluateLoadStateCondition(cdp, condition.expression);
2295
+ }
2296
+ if (condition.kind === "selectorHidden") {
2297
+ const response2 = await cdp.send("Runtime.evaluate", {
2298
+ expression: condition.expression,
2299
+ returnByValue: true
2300
+ });
2301
+ return response2.result.value === false;
2302
+ }
2303
+ if (condition.kind === "selector") {
2304
+ const response2 = await cdp.send("Runtime.evaluate", {
2305
+ expression: condition.expression,
2306
+ returnByValue: true
2307
+ });
2308
+ return response2.result.value !== null && response2.result.subtype !== "null";
2309
+ }
2310
+ const response = await cdp.send("Runtime.evaluate", {
2311
+ expression: condition.expression,
2312
+ returnByValue: true
2313
+ });
2314
+ return response.result.value === true;
2315
+ }
2316
+ async function evaluateLoadCondition(cdp, expression) {
2317
+ const response = await cdp.send("Runtime.evaluate", {
2318
+ expression,
2319
+ returnByValue: true
2320
+ });
2321
+ if (response.result.type === "string") {
2322
+ return response.result.value === "complete";
2323
+ }
2324
+ return response.result.value === true;
2325
+ }
2326
+ async function evaluateLoadStateCondition(cdp, targetState) {
2327
+ const response = await cdp.send("Runtime.evaluate", {
2328
+ expression: "document.readyState",
2329
+ returnByValue: true
2330
+ });
2331
+ const current = response.result.value;
2332
+ if (targetState === "complete") {
2333
+ return current === "complete";
2334
+ }
2335
+ if (targetState === "interactive") {
2336
+ return current === "interactive" || current === "complete";
2337
+ }
2338
+ return current === targetState;
2339
+ }
2340
+ async function evaluateUrlCondition(cdp, pattern) {
2341
+ const response = await cdp.send("Runtime.evaluate", {
2342
+ expression: "location.href",
2343
+ returnByValue: true
2344
+ });
2345
+ const currentUrl = response.result.value;
2346
+ return globMatch(pattern, currentUrl);
2347
+ }
2348
+ function globMatch(pattern, value) {
2349
+ const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\u0000/g, ".*").replace(/\?/g, ".");
2350
+ const regex = new RegExp(regexStr);
2351
+ return regex.test(value);
2352
+ }
2353
+ function describeCondition(params) {
2354
+ if (params.text) return `text "${params.text}" to appear`;
2355
+ if (params.textGone) return `text "${params.textGone}" to disappear`;
2356
+ if (params.selector && params.state === "hidden")
2357
+ return `selector "${params.selector}" to become hidden`;
2358
+ if (params.selector && params.visible)
2359
+ return `selector "${params.selector}" to become visible`;
2360
+ if (params.selector) return `selector "${params.selector}" to appear`;
2361
+ if (params.url) return `URL matching "${params.url}"`;
2362
+ if (params.fn) return `JS condition: ${params.fn}`;
2363
+ if (params.loadState) return `document.readyState === "${params.loadState}"`;
2364
+ if (params.networkIdle) return "network idle";
2365
+ if (params.load) return "page load complete";
2366
+ return "unknown condition";
2367
+ }
2368
+ function delay4(ms) {
2369
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
2370
+ }
2371
+ var DEFAULT_TIMEOUT_S, POLL_INTERVAL_MS2;
2372
+ var init_browser_wait_for = __esm({
2373
+ "src/tools/browser-wait-for.ts"() {
2374
+ "use strict";
2375
+ DEFAULT_TIMEOUT_S = 30;
2376
+ POLL_INTERVAL_MS2 = 100;
2377
+ }
2378
+ });
2379
+
2380
+ // src/tools/browser-drag.ts
2381
+ function calculateCenter3(content) {
2382
+ const x = (content[0] + content[2] + content[4] + content[6]) / 4;
2383
+ const y = (content[1] + content[3] + content[5] + content[7]) / 4;
2384
+ return { x: Math.round(x), y: Math.round(y) };
2385
+ }
2386
+ async function resolveRefCoordinates(cdp, ref) {
2387
+ const match = REF_PATTERN3.exec(ref);
2388
+ if (!match) {
2389
+ throw new Error(`Invalid ref format: ${ref}`);
2390
+ }
2391
+ const backendNodeId = parseInt(match[1], 10);
2392
+ const opts = { timeout: 5e3 };
2393
+ await cdp.send("DOM.resolveNode", {
2394
+ backendNodeId
2395
+ }, opts);
2396
+ try {
2397
+ await cdp.send("DOM.scrollIntoViewIfNeeded", {
2398
+ backendNodeId
2399
+ }, opts);
2400
+ } catch {
2401
+ }
2402
+ const boxResponse = await cdp.send("DOM.getBoxModel", {
2403
+ backendNodeId
2404
+ }, opts);
2405
+ return calculateCenter3(boxResponse.model.content);
2406
+ }
2407
+ function delay5(ms) {
2408
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
2409
+ }
2410
+ function interpolatePoints(startX, startY, endX, endY, steps = 8) {
2411
+ const points = [];
2412
+ for (let i = 1; i < steps; i++) {
2413
+ const t = i / steps;
2414
+ points.push({
2415
+ x: Math.round(startX + (endX - startX) * t),
2416
+ y: Math.round(startY + (endY - startY) * t)
2417
+ });
2418
+ }
2419
+ return points;
2420
+ }
2421
+ async function browserDrag(cdp, params) {
2422
+ let startX;
2423
+ let startY;
2424
+ if (params.startX !== void 0 && params.startY !== void 0) {
2425
+ startX = params.startX;
2426
+ startY = params.startY;
2427
+ } else if (params.startRef) {
2428
+ const coords = await resolveRefCoordinates(cdp, params.startRef);
2429
+ startX = coords.x;
2430
+ startY = coords.y;
2431
+ } else {
2432
+ throw new Error("Either startRef or startX/startY must be provided");
2433
+ }
2434
+ let endX;
2435
+ let endY;
2436
+ if (params.endX !== void 0 && params.endY !== void 0) {
2437
+ endX = params.endX;
2438
+ endY = params.endY;
2439
+ } else if (params.endRef) {
2440
+ const coords = await resolveRefCoordinates(cdp, params.endRef);
2441
+ endX = coords.x;
2442
+ endY = coords.y;
2443
+ } else {
2444
+ throw new Error("Either endRef or endX/endY must be provided");
2445
+ }
2446
+ const mouseOpts = { timeout: 3e3 };
2447
+ await cdp.send("Input.dispatchMouseEvent", {
2448
+ type: "mouseMoved",
2449
+ x: startX,
2450
+ y: startY
2451
+ }, mouseOpts);
2452
+ await cdp.send("Input.dispatchMouseEvent", {
2453
+ type: "mousePressed",
2454
+ x: startX,
2455
+ y: startY,
2456
+ button: "left",
2457
+ clickCount: 1
2458
+ }, mouseOpts);
2459
+ const intermediatePoints = interpolatePoints(startX, startY, endX, endY, 4);
2460
+ for (const point of intermediatePoints) {
2461
+ await cdp.send("Input.dispatchMouseEvent", {
2462
+ type: "mouseMoved",
2463
+ x: point.x,
2464
+ y: point.y
2465
+ }, mouseOpts);
2466
+ await delay5(10);
2467
+ }
2468
+ await cdp.send("Input.dispatchMouseEvent", {
2469
+ type: "mouseMoved",
2470
+ x: endX,
2471
+ y: endY
2472
+ }, mouseOpts);
2473
+ await cdp.send("Input.dispatchMouseEvent", {
2474
+ type: "mouseReleased",
2475
+ x: endX,
2476
+ y: endY,
2477
+ button: "left",
2478
+ clickCount: 1
2479
+ }, mouseOpts);
2480
+ return { success: true };
2481
+ }
2482
+ var REF_PATTERN3;
2483
+ var init_browser_drag = __esm({
2484
+ "src/tools/browser-drag.ts"() {
2485
+ "use strict";
2486
+ REF_PATTERN3 = /^@?e(\d+)$/;
2487
+ }
2488
+ });
2489
+
2490
+ // src/tools/browser-handle-dialog.ts
2491
+ async function browserHandleDialog(cdp, params) {
2492
+ const dialogParams = {
2493
+ accept: params.accept
2494
+ };
2495
+ if (params.promptText !== void 0) {
2496
+ dialogParams.promptText = params.promptText;
2497
+ }
2498
+ try {
2499
+ await cdp.send("Page.handleJavaScriptDialog", dialogParams);
2500
+ return { success: true };
2501
+ } catch (err) {
2502
+ const message = err instanceof Error ? err.message : String(err);
2503
+ if (message.includes("No dialog is showing") || message.includes("no dialog")) {
2504
+ return {
2505
+ success: false,
2506
+ error: "No JavaScript dialog is currently pending. A dialog must be open before it can be handled."
2507
+ };
2508
+ }
2509
+ return {
2510
+ success: false,
2511
+ error: `Failed to handle dialog: ${message}`
2512
+ };
2513
+ }
2514
+ }
2515
+ var init_browser_handle_dialog = __esm({
2516
+ "src/tools/browser-handle-dialog.ts"() {
2517
+ "use strict";
2518
+ }
2519
+ });
2520
+
2521
+ // src/tools/browser-file-upload.ts
2522
+ async function browserFileUpload(cdp, params) {
2523
+ const match = REF_PATTERN4.exec(params.ref);
2524
+ if (!match) {
2525
+ return {
2526
+ success: false,
2527
+ filesCount: 0,
2528
+ error: `Invalid ref format: ${params.ref}. Expected @eN pattern (e.g. @e5).`
2529
+ };
2530
+ }
2531
+ const backendNodeId = parseInt(match[1], 10);
2532
+ const resolved = await cdp.send("DOM.resolveNode", {
2533
+ backendNodeId
2534
+ });
2535
+ const objectId = resolved.object.objectId;
2536
+ await cdp.send("DOM.setFileInputFiles", {
2537
+ files: params.paths,
2538
+ objectId
2539
+ });
2540
+ return {
2541
+ success: true,
2542
+ filesCount: params.paths.length
2543
+ };
2544
+ }
2545
+ var REF_PATTERN4;
2546
+ var init_browser_file_upload = __esm({
2547
+ "src/tools/browser-file-upload.ts"() {
2548
+ "use strict";
2549
+ REF_PATTERN4 = /^@e(\d+)$/;
2550
+ }
2551
+ });
2552
+
2553
+ // src/event-buffer.ts
2554
+ var EventBuffer;
2555
+ var init_event_buffer = __esm({
2556
+ "src/event-buffer.ts"() {
2557
+ "use strict";
2558
+ EventBuffer = class {
2559
+ _buffer;
2560
+ _capacity;
2561
+ _head;
2562
+ // next write index
2563
+ _size;
2564
+ _totalPushed;
2565
+ constructor(capacity = 500) {
2566
+ this._capacity = capacity;
2567
+ this._buffer = new Array(capacity);
2568
+ this._head = 0;
2569
+ this._size = 0;
2570
+ this._totalPushed = 0;
2571
+ }
2572
+ /** Append an event, evicting the oldest if at capacity. */
2573
+ push(event) {
2574
+ this._buffer[this._head] = event;
2575
+ this._head = (this._head + 1) % this._capacity;
2576
+ if (this._size < this._capacity) {
2577
+ this._size++;
2578
+ }
2579
+ this._totalPushed++;
2580
+ }
2581
+ /**
2582
+ * Return the last `n` events in chronological (oldest-first) order.
2583
+ * Defaults to all events when `n` is omitted.
2584
+ */
2585
+ last(n) {
2586
+ const count = n === void 0 ? this._size : Math.min(n, this._size);
2587
+ if (count === 0) return [];
2588
+ const result = new Array(count);
2589
+ let start = (this._head - count + this._capacity) % this._capacity;
2590
+ for (let i = 0; i < count; i++) {
2591
+ result[i] = this._buffer[(start + i) % this._capacity];
2592
+ }
2593
+ return result;
2594
+ }
2595
+ /** Remove all events from the buffer. */
2596
+ clear() {
2597
+ this._buffer = new Array(this._capacity);
2598
+ this._head = 0;
2599
+ this._size = 0;
2600
+ }
2601
+ /**
2602
+ * Return the last `n` events AND remove them from the buffer.
2603
+ * Defaults to all events when `n` is omitted.
2604
+ */
2605
+ drain(n) {
2606
+ const events = this.last(n);
2607
+ this.clear();
2608
+ return events;
2609
+ }
2610
+ /** Current number of events stored. */
2611
+ get size() {
2612
+ return this._size;
2613
+ }
2614
+ /** Maximum number of events this buffer can hold. */
2615
+ get capacity() {
2616
+ return this._capacity;
2617
+ }
2618
+ /** Total number of events ever pushed (including evicted ones). */
2619
+ get totalPushed() {
2620
+ return this._totalPushed;
2621
+ }
2622
+ /** Return events matching `predicate` without modifying the buffer. */
2623
+ filter(predicate) {
2624
+ return this.last().filter(predicate);
2625
+ }
2626
+ /** Snapshot of buffer statistics. */
2627
+ get stats() {
2628
+ return {
2629
+ size: this._size,
2630
+ capacity: this._capacity,
2631
+ totalPushed: this._totalPushed,
2632
+ evicted: this._totalPushed - this._size
2633
+ };
2634
+ }
2635
+ };
2636
+ }
2637
+ });
2638
+
2639
+ // src/redactor.ts
2640
+ function redactInlineSecrets(text, opts) {
2641
+ if (opts?.enabled === false) {
2642
+ return text;
2643
+ }
2644
+ let result = text.replace(JWT_PATTERN, REDACTED_JWT);
2645
+ result = result.replace(BEARER_PATTERN, (match) => {
2646
+ const bearerWord = match.split(/\s+/)[0];
2647
+ return `${bearerWord} ${REDACTED}`;
2648
+ });
2649
+ return result;
2650
+ }
2651
+ var JWT_PATTERN, BEARER_PATTERN, REDACTED, REDACTED_JWT;
2652
+ var init_redactor = __esm({
2653
+ "src/redactor.ts"() {
2654
+ "use strict";
2655
+ JWT_PATTERN = /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+/g;
2656
+ BEARER_PATTERN = /Bearer\s+\S+/gi;
2657
+ REDACTED = "[REDACTED]";
2658
+ REDACTED_JWT = "[REDACTED_JWT]";
2659
+ }
2660
+ });
2661
+
2662
+ // src/tools/browser-network-requests.ts
2663
+ function setupNetworkCapture(cdp) {
2664
+ cdp.on("Network.requestWillBeSent", (params) => {
2665
+ const p = params;
2666
+ const entry = {
2667
+ requestId: p.requestId,
2668
+ url: p.request.url,
2669
+ method: p.request.method,
2670
+ type: p.type ?? "Other",
2671
+ timestamp: p.timestamp ? Math.floor(p.timestamp * 1e3) : Date.now()
2672
+ };
2673
+ pendingRequests.set(p.requestId, entry);
2674
+ networkBuffer.push(entry);
2675
+ });
2676
+ cdp.on("Network.responseReceived", (params) => {
2677
+ const p = params;
2678
+ const entry = pendingRequests.get(p.requestId);
2679
+ if (entry) {
2680
+ entry.status = p.response.status;
2681
+ pendingRequests.delete(p.requestId);
2682
+ }
2683
+ });
2684
+ }
2685
+ function resetNetworkBuffer() {
2686
+ networkBuffer.clear();
2687
+ pendingRequests.clear();
2688
+ }
2689
+ async function browserNetworkRequests(_cdp, params) {
2690
+ let entries = networkBuffer.last();
2691
+ if (!params.includeStatic) {
2692
+ entries = entries.filter((e) => !STATIC_TYPES.has(e.type));
2693
+ }
2694
+ if (params.filter) {
2695
+ const filterLower = params.filter.toLowerCase();
2696
+ entries = entries.filter((e) => e.url.toLowerCase().includes(filterLower));
2697
+ }
2698
+ const limit = params.limit ?? 100;
2699
+ entries = entries.slice(0, limit);
2700
+ const requests = entries.map((e) => ({
2701
+ url: redactInlineSecrets(e.url),
2702
+ method: e.method,
2703
+ status: e.status,
2704
+ type: e.type
2705
+ }));
2706
+ return { requests };
2707
+ }
2708
+ var STATIC_TYPES, networkBuffer, pendingRequests;
2709
+ var init_browser_network_requests = __esm({
2710
+ "src/tools/browser-network-requests.ts"() {
2711
+ "use strict";
2712
+ init_event_buffer();
2713
+ init_redactor();
2714
+ STATIC_TYPES = /* @__PURE__ */ new Set([
2715
+ "Image",
2716
+ "Stylesheet",
2717
+ "Font",
2718
+ "Script",
2719
+ "Media"
2720
+ ]);
2721
+ networkBuffer = new EventBuffer(500);
2722
+ pendingRequests = /* @__PURE__ */ new Map();
2723
+ }
2724
+ });
2725
+
2726
+ // src/tools/browser-console-messages.ts
2727
+ function setupConsoleCapture(cdp) {
2728
+ cdp.on("Runtime.consoleAPICalled", (params) => {
2729
+ const p = params;
2730
+ if (!SUPPORTED_LEVELS.has(p.type)) return;
2731
+ const text = (p.args ?? []).map((arg) => {
2732
+ if (arg.type === "string") return String(arg.value);
2733
+ if (arg.type === "undefined") return "undefined";
2734
+ if (arg.value !== void 0) return String(arg.value);
2735
+ if (arg.description) return arg.description;
2736
+ return "";
2737
+ }).join(" ");
2738
+ consoleBuffer.push({
2739
+ level: p.type === "warning" ? "warn" : p.type,
2740
+ text,
2741
+ timestamp: p.timestamp ? Math.floor(p.timestamp) : Date.now()
2742
+ });
2743
+ });
2744
+ }
2745
+ function resetConsoleBuffer() {
2746
+ consoleBuffer.clear();
2747
+ }
2748
+ async function browserConsoleMessages(_cdp, params) {
2749
+ let messages = consoleBuffer.last();
2750
+ messages = messages.map((m) => ({ ...m, text: redactInlineSecrets(m.text) }));
2751
+ if (params.level) {
2752
+ messages = messages.filter((m) => m.level === params.level);
2753
+ }
2754
+ const limit = params.limit ?? 100;
2755
+ if (messages.length > limit) {
2756
+ messages = messages.slice(-limit);
2757
+ }
2758
+ return { messages };
2759
+ }
2760
+ var SUPPORTED_LEVELS, consoleBuffer;
2761
+ var init_browser_console_messages = __esm({
2762
+ "src/tools/browser-console-messages.ts"() {
2763
+ "use strict";
2764
+ init_event_buffer();
2765
+ init_redactor();
2766
+ SUPPORTED_LEVELS = /* @__PURE__ */ new Set(["log", "warn", "warning", "error", "info"]);
2767
+ consoleBuffer = new EventBuffer(500);
2768
+ }
2769
+ });
2770
+
2771
+ // src/tools/browser-annotated-screenshot.ts
2772
+ async function browserAnnotatedScreenshot(cdp, params) {
2773
+ const result = await browserScreenshot(cdp, {
2774
+ annotate: true,
2775
+ selector: params.selector
2776
+ });
2777
+ return {
2778
+ base64: result.base64,
2779
+ annotations: result.annotations ?? []
2780
+ };
2781
+ }
2782
+ var init_browser_annotated_screenshot = __esm({
2783
+ "src/tools/browser-annotated-screenshot.ts"() {
2784
+ "use strict";
2785
+ init_browser_screenshot();
2786
+ }
2787
+ });
2788
+
2789
+ // src/tools/browser-inspect-source.ts
2790
+ async function browserInspectSource(cdp, params) {
2791
+ let objectId;
2792
+ if (params.ref) {
2793
+ const match = REF_PATTERN5.exec(params.ref);
2794
+ if (!match) throw new Error(`Invalid ref format: ${params.ref}`);
2795
+ const backendNodeId = parseInt(match[1], 10);
2796
+ const resolved = await cdp.send("DOM.resolveNode", { backendNodeId });
2797
+ objectId = resolved.object.objectId;
2798
+ } else if (params.selector) {
2799
+ const evalResult = await cdp.send("Runtime.evaluate", {
2800
+ expression: `document.querySelector(${JSON.stringify(params.selector)})`,
2801
+ returnByValue: false
2802
+ });
2803
+ if (!evalResult.result.objectId || evalResult.result.subtype === "null") {
2804
+ throw new Error(`Element not found: ${params.selector}`);
2805
+ }
2806
+ objectId = evalResult.result.objectId;
2807
+ } else {
2808
+ throw new Error("Either ref or selector must be provided");
2809
+ }
2810
+ const cdpResult = await cdp.send("Runtime.callFunctionOn", {
2811
+ objectId,
2812
+ functionDeclaration: `function() {
2813
+ var el = this;
2814
+ var tagName = (el.tagName || '').toLowerCase();
2815
+
2816
+ // Find React Fiber
2817
+ var fiberKey = Object.keys(el).find(function(k) { return k.startsWith('__reactFiber'); });
2818
+
2819
+ // Also check Vue, Svelte
2820
+ var vueComp = el.__vueParentComponent;
2821
+ var svelteMeta = el.__svelte_meta;
2822
+
2823
+ if (!fiberKey && !vueComp && !svelteMeta) {
2824
+ return JSON.stringify({ tagName: tagName, componentName: null, source: null, stack: [], framework: null });
2825
+ }
2826
+
2827
+ // --- React path ---
2828
+ if (fiberKey) {
2829
+ var fiber = el[fiberKey];
2830
+ var stack = [];
2831
+ var current = fiber;
2832
+ var firstSource = null;
2833
+ var firstName = null;
2834
+
2835
+ while (current && stack.length < 15) {
2836
+ if (typeof current.type === 'function' && current.type.name) {
2837
+ var fn = current.type;
2838
+ var fnStr = fn.toString();
2839
+ var fileName = null;
2840
+ var lineNumber = null;
2841
+ var columnNumber = null;
2842
+
2843
+ // Parse jsxDEV calls for embedded fileName/lineNumber
2844
+ var fileMatch = fnStr.match(/fileName:\\s*"([^"]+)"/);
2845
+ if (fileMatch) {
2846
+ fileName = fileMatch[1];
2847
+ var lineMatch = fnStr.match(/lineNumber:\\s*(\\d+)/);
2848
+ if (lineMatch) lineNumber = parseInt(lineMatch[1]);
2849
+ var colMatch = fnStr.match(/columnNumber:\\s*(\\d+)/);
2850
+ if (colMatch) columnNumber = parseInt(colMatch[1]);
2851
+ }
2852
+
2853
+ var entry = {
2854
+ filePath: fileName,
2855
+ lineNumber: lineNumber,
2856
+ columnNumber: columnNumber,
2857
+ componentName: fn.name
2858
+ };
2859
+
2860
+ stack.push(entry);
2861
+
2862
+ if (fileName && !firstSource) {
2863
+ firstSource = entry;
2864
+ }
2865
+ if (!firstName && fn.name.length > 1) {
2866
+ firstName = fn.name;
2867
+ }
2868
+ }
2869
+ current = current.return;
2870
+ }
2871
+
2872
+ return JSON.stringify({
2873
+ tagName: tagName,
2874
+ componentName: firstName || null,
2875
+ source: firstSource || (stack.length > 0 ? { filePath: null, lineNumber: null, columnNumber: null, componentName: stack[0].componentName } : null),
2876
+ stack: stack.filter(function(s) { return s.filePath; }),
2877
+ framework: 'react'
2878
+ });
2879
+ }
2880
+
2881
+ // --- Svelte path ---
2882
+ if (svelteMeta) {
2883
+ var loc = svelteMeta.loc || {};
2884
+ return JSON.stringify({
2885
+ tagName: tagName,
2886
+ componentName: loc.char ? null : (svelteMeta.component || null),
2887
+ source: loc.file ? { filePath: loc.file, lineNumber: loc.line || null, columnNumber: (loc.column || 0) + 1, componentName: null } : null,
2888
+ stack: [],
2889
+ framework: 'svelte'
2890
+ });
2891
+ }
2892
+
2893
+ // --- Vue path ---
2894
+ if (vueComp) {
2895
+ var comp = vueComp;
2896
+ var vueName = comp.type?.__name || comp.type?.name || null;
2897
+ var vueFile = comp.type?.__file || null;
2898
+ return JSON.stringify({
2899
+ tagName: tagName,
2900
+ componentName: vueName,
2901
+ source: vueFile ? { filePath: vueFile, lineNumber: null, columnNumber: null, componentName: vueName } : null,
2902
+ stack: [],
2903
+ framework: 'vue'
2904
+ });
2905
+ }
2906
+
2907
+ return JSON.stringify({ tagName: tagName, componentName: null, source: null, stack: [], framework: null });
2908
+ }`,
2909
+ returnByValue: true
2910
+ });
2911
+ return JSON.parse(cdpResult.result.value);
2912
+ }
2913
+ var REF_PATTERN5;
2914
+ var init_browser_inspect_source = __esm({
2915
+ "src/tools/browser-inspect-source.ts"() {
2916
+ "use strict";
2917
+ REF_PATTERN5 = /^@?e(\d+)$/;
2918
+ }
2919
+ });
2920
+
2921
+ // src/tools/browser-intercept.ts
2922
+ function matchGlob(pattern, url) {
2923
+ const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "___DOUBLESTAR___").replace(/\*/g, "[^/]*").replace(/___DOUBLESTAR___/g, ".*");
2924
+ return new RegExp(`^${regex}$`).test(url);
2925
+ }
2926
+ async function syncFetchPatterns(cdp) {
2927
+ const patterns = [
2928
+ ...Array.from(activeRoutes.keys()),
2929
+ ...Array.from(activeAborts.keys())
2930
+ ].map((p) => ({ urlPattern: p }));
2931
+ if (patterns.length === 0) {
2932
+ if (fetchEnabled) {
2933
+ await cdp.send("Fetch.disable");
2934
+ fetchEnabled = false;
2935
+ }
2936
+ return;
2937
+ }
2938
+ await cdp.send("Fetch.enable", { patterns });
2939
+ fetchEnabled = true;
2940
+ if (!handlerAttached) {
2941
+ cdp.on("Fetch.requestPaused", async (params) => {
2942
+ const url = params.request.url;
2943
+ const requestId = params.requestId;
2944
+ try {
2945
+ for (const [pattern] of activeAborts) {
2946
+ if (matchGlob(pattern, url)) {
2947
+ await cdp.send("Fetch.failRequest", {
2948
+ requestId,
2949
+ reason: "BlockedByClient"
2950
+ });
2951
+ return;
2952
+ }
2953
+ }
2954
+ for (const [pattern, rule] of activeRoutes) {
2955
+ if (matchGlob(pattern, url)) {
2956
+ const responseHeaders = Object.entries(rule.headers).map(
2957
+ ([name, value]) => ({ name, value })
2958
+ );
2959
+ await cdp.send("Fetch.fulfillRequest", {
2960
+ requestId,
2961
+ responseCode: rule.status,
2962
+ body: btoa(rule.body),
2963
+ responseHeaders
2964
+ });
2965
+ return;
2966
+ }
2967
+ }
2968
+ await cdp.send("Fetch.continueRequest", { requestId });
2969
+ } catch {
2970
+ }
2971
+ });
2972
+ handlerAttached = true;
2973
+ }
2974
+ }
2975
+ async function browserRoute(cdp, params) {
2976
+ const body = typeof params.body === "object" && params.body !== null ? JSON.stringify(params.body) : String(params.body);
2977
+ const status = params.status ?? 200;
2978
+ const headers = params.headers ?? { "Content-Type": "application/json" };
2979
+ const rule = {
2980
+ urlPattern: params.url,
2981
+ body,
2982
+ status,
2983
+ headers
2984
+ };
2985
+ activeRoutes.set(params.url, rule);
2986
+ await syncFetchPatterns(cdp);
2987
+ return {
2988
+ url: params.url,
2989
+ status,
2990
+ activeRoutes: activeRoutes.size
2991
+ };
2992
+ }
2993
+ async function browserAbort(cdp, params) {
2994
+ const rule = {
2995
+ urlPattern: params.url
2996
+ };
2997
+ activeAborts.set(params.url, rule);
2998
+ await syncFetchPatterns(cdp);
2999
+ return {
3000
+ url: params.url,
3001
+ activeAborts: activeAborts.size
3002
+ };
3003
+ }
3004
+ async function browserUnroute(cdp, params) {
3005
+ let removed = 0;
3006
+ if (params.all) {
3007
+ removed = activeRoutes.size + activeAborts.size;
3008
+ activeRoutes.clear();
3009
+ activeAborts.clear();
3010
+ } else if (params.url) {
3011
+ if (activeRoutes.delete(params.url)) removed++;
3012
+ if (activeAborts.delete(params.url)) removed++;
3013
+ }
3014
+ await syncFetchPatterns(cdp);
3015
+ return {
3016
+ removed,
3017
+ remaining: activeRoutes.size + activeAborts.size
3018
+ };
3019
+ }
3020
+ var activeRoutes, activeAborts, fetchEnabled, handlerAttached;
3021
+ var init_browser_intercept = __esm({
3022
+ "src/tools/browser-intercept.ts"() {
3023
+ "use strict";
3024
+ activeRoutes = /* @__PURE__ */ new Map();
3025
+ activeAborts = /* @__PURE__ */ new Map();
3026
+ fetchEnabled = false;
3027
+ handlerAttached = false;
3028
+ }
3029
+ });
3030
+
3031
+ // src/tools/browser-find.ts
3032
+ async function browserFind(cdp, params) {
3033
+ const nth = params.nth ?? 0;
3034
+ const response = await cdp.send("Accessibility.getFullAXTree", void 0, {
3035
+ timeout: 1e4
3036
+ });
3037
+ const matches = response.nodes.filter((node) => {
3038
+ if (params.role) {
3039
+ const nodeRole = node.role?.value ?? "";
3040
+ if (nodeRole.toLowerCase() !== params.role.toLowerCase()) {
3041
+ return false;
3042
+ }
3043
+ }
3044
+ if (params.name) {
3045
+ const nodeName = node.name?.value ?? "";
3046
+ if (!nodeName.includes(params.name)) {
3047
+ return false;
3048
+ }
3049
+ }
3050
+ if (params.text) {
3051
+ const nodeName = node.name?.value ?? "";
3052
+ if (!nodeName.includes(params.text)) {
3053
+ return false;
3054
+ }
3055
+ }
3056
+ return true;
3057
+ });
3058
+ const count = matches.length;
3059
+ if (nth >= count || count === 0) {
3060
+ return {
3061
+ found: false,
3062
+ ref: null,
3063
+ role: null,
3064
+ name: null,
3065
+ count
3066
+ };
3067
+ }
3068
+ const match = matches[nth];
3069
+ const ref = match.backendDOMNodeId ? `@e${match.backendDOMNodeId}` : null;
3070
+ return {
3071
+ found: true,
3072
+ ref,
3073
+ role: match.role?.value ?? null,
3074
+ name: match.name?.value ?? null,
3075
+ count
3076
+ };
3077
+ }
3078
+ var init_browser_find = __esm({
3079
+ "src/tools/browser-find.ts"() {
3080
+ "use strict";
3081
+ }
3082
+ });
3083
+
3084
+ // src/tools/browser-diff.ts
3085
+ async function captureScreenshot(cdp, selector) {
3086
+ const captureParams = { format: "png" };
3087
+ if (selector) {
3088
+ const doc = await cdp.send("DOM.getDocument", {});
3089
+ const queryResult = await cdp.send("DOM.querySelector", {
3090
+ nodeId: doc.root.nodeId,
3091
+ selector
3092
+ });
3093
+ if (!queryResult.nodeId) {
3094
+ throw new Error(`Element not found: ${selector}`);
3095
+ }
3096
+ const boxModel = await cdp.send("DOM.getBoxModel", {
3097
+ nodeId: queryResult.nodeId
3098
+ });
3099
+ const content = boxModel.model.content;
3100
+ captureParams.clip = {
3101
+ x: content[0],
3102
+ y: content[1],
3103
+ width: content[2] - content[0],
3104
+ height: content[5] - content[1],
3105
+ scale: 1
3106
+ };
3107
+ }
3108
+ const screenshot = await cdp.send(
3109
+ "Page.captureScreenshot",
3110
+ captureParams
3111
+ );
3112
+ return screenshot.data;
3113
+ }
3114
+ function buildComparisonExpression(threshold) {
3115
+ return [
3116
+ "(async () => {",
3117
+ " const beforeSrc = window._diffBefore;",
3118
+ " const afterSrc = window._diffAfter;",
3119
+ "",
3120
+ " const loadImg = (src) => new Promise((res, rej) => {",
3121
+ " const img = new Image();",
3122
+ " img.onload = () => res(img);",
3123
+ " img.onerror = (e) => rej(new Error('Failed to load image'));",
3124
+ " img.src = src;",
3125
+ " });",
3126
+ "",
3127
+ " const img1 = await loadImg('data:image/png;base64,' + beforeSrc);",
3128
+ " const img2 = await loadImg('data:image/png;base64,' + afterSrc);",
3129
+ "",
3130
+ " const w = Math.max(img1.width, img2.width);",
3131
+ " const h = Math.max(img1.height, img2.height);",
3132
+ "",
3133
+ " const c1 = document.createElement('canvas');",
3134
+ " c1.width = w; c1.height = h;",
3135
+ " const ctx1 = c1.getContext('2d');",
3136
+ " ctx1.drawImage(img1, 0, 0);",
3137
+ "",
3138
+ " const c2 = document.createElement('canvas');",
3139
+ " c2.width = w; c2.height = h;",
3140
+ " const ctx2 = c2.getContext('2d');",
3141
+ " ctx2.drawImage(img2, 0, 0);",
3142
+ "",
3143
+ " const d1 = ctx1.getImageData(0, 0, w, h).data;",
3144
+ " const d2 = ctx2.getImageData(0, 0, w, h).data;",
3145
+ "",
3146
+ " const diff = document.createElement('canvas');",
3147
+ " diff.width = w; diff.height = h;",
3148
+ " const dCtx = diff.getContext('2d');",
3149
+ " dCtx.drawImage(img2, 0, 0);",
3150
+ " const dData = dCtx.getImageData(0, 0, w, h);",
3151
+ "",
3152
+ " let diffCount = 0;",
3153
+ " const threshold = " + threshold + ";",
3154
+ " for (let i = 0; i < d1.length; i += 4) {",
3155
+ " const dr = Math.abs(d1[i] - d2[i]);",
3156
+ " const dg = Math.abs(d1[i+1] - d2[i+1]);",
3157
+ " const db = Math.abs(d1[i+2] - d2[i+2]);",
3158
+ " if (dr > threshold || dg > threshold || db > threshold) {",
3159
+ " diffCount++;",
3160
+ " dData.data[i] = 255;",
3161
+ " dData.data[i+1] = 0;",
3162
+ " dData.data[i+2] = 0;",
3163
+ " dData.data[i+3] = 200;",
3164
+ " }",
3165
+ " }",
3166
+ "",
3167
+ " dCtx.putImageData(dData, 0, 0);",
3168
+ " const diffBase64 = diff.toDataURL('image/png').split(',')[1];",
3169
+ "",
3170
+ " const total = w * h;",
3171
+ " return JSON.stringify({",
3172
+ " diffPercentage: parseFloat((diffCount / total * 100).toFixed(4)),",
3173
+ " totalPixels: total,",
3174
+ " diffPixels: diffCount,",
3175
+ " identical: (diffCount / total) < 0.001,",
3176
+ " diffImage: diffBase64,",
3177
+ " width: w,",
3178
+ " height: h",
3179
+ " });",
3180
+ "})()"
3181
+ ].join("\n");
3182
+ }
3183
+ async function browserDiff(cdp, params) {
3184
+ const threshold = params.threshold ?? 30;
3185
+ let beforeBase64;
3186
+ if (params.before === "current") {
3187
+ beforeBase64 = await captureScreenshot(cdp, params.selector);
3188
+ } else {
3189
+ beforeBase64 = params.before;
3190
+ }
3191
+ let afterBase64;
3192
+ if (!params.after || params.after === "current") {
3193
+ afterBase64 = await captureScreenshot(cdp, params.selector);
3194
+ } else {
3195
+ afterBase64 = params.after;
3196
+ }
3197
+ await cdp.send("Runtime.evaluate", {
3198
+ expression: "window._diffBefore = " + JSON.stringify(beforeBase64) + ";",
3199
+ returnByValue: true
3200
+ });
3201
+ await cdp.send("Runtime.evaluate", {
3202
+ expression: "window._diffAfter = " + JSON.stringify(afterBase64) + ";",
3203
+ returnByValue: true
3204
+ });
3205
+ const result = await cdp.send("Runtime.evaluate", {
3206
+ expression: buildComparisonExpression(threshold),
3207
+ awaitPromise: true,
3208
+ returnByValue: true
3209
+ });
3210
+ if (result.exceptionDetails) {
3211
+ throw new Error(
3212
+ "Diff comparison failed: " + result.exceptionDetails.text
3213
+ );
3214
+ }
3215
+ await cdp.send("Runtime.evaluate", {
3216
+ expression: "delete window._diffBefore; delete window._diffAfter;",
3217
+ returnByValue: true
3218
+ });
3219
+ return JSON.parse(result.result.value);
3220
+ }
3221
+ var init_browser_diff = __esm({
3222
+ "src/tools/browser-diff.ts"() {
3223
+ "use strict";
3224
+ }
3225
+ });
3226
+
3227
+ // src/tools/browser-session-state.ts
3228
+ import { writeFileSync, readFileSync as readFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
3229
+ import { join as join2 } from "path";
3230
+ import { homedir as homedir2 } from "os";
3231
+ function getStatesDir() {
3232
+ return join2(homedir2(), ".browsirai", "states");
3233
+ }
3234
+ function ensureStatesDir() {
3235
+ const dir = getStatesDir();
3236
+ if (!existsSync2(dir)) {
3237
+ mkdirSync2(dir, { recursive: true });
3238
+ }
3239
+ return dir;
3240
+ }
3241
+ function getStatePath(name) {
3242
+ return join2(getStatesDir(), `${name}.json`);
3243
+ }
3244
+ async function browserSaveState(cdp, params) {
3245
+ const { name } = params;
3246
+ const cookieResponse = await cdp.send("Network.getAllCookies");
3247
+ const cookies = cookieResponse.cookies ?? [];
3248
+ const urlResponse = await cdp.send("Runtime.evaluate", {
3249
+ expression: "window.location.href",
3250
+ returnByValue: true
3251
+ });
3252
+ const url = urlResponse.result.value ?? "";
3253
+ const localStorageResponse = await cdp.send("Runtime.evaluate", {
3254
+ expression: "JSON.stringify(Object.entries(localStorage))",
3255
+ returnByValue: true
3256
+ });
3257
+ const localStorageEntries = JSON.parse(
3258
+ localStorageResponse.result.value ?? "[]"
3259
+ );
3260
+ const localStorage = Object.fromEntries(localStorageEntries);
3261
+ const sessionStorageResponse = await cdp.send("Runtime.evaluate", {
3262
+ expression: "JSON.stringify(Object.entries(sessionStorage))",
3263
+ returnByValue: true
3264
+ });
3265
+ const sessionStorageEntries = JSON.parse(
3266
+ sessionStorageResponse.result.value ?? "[]"
3267
+ );
3268
+ const sessionStorage = Object.fromEntries(sessionStorageEntries);
3269
+ const dir = ensureStatesDir();
3270
+ const filePath = join2(dir, `${name}.json`);
3271
+ const stateFile = {
3272
+ version: 1,
3273
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
3274
+ url,
3275
+ cookies,
3276
+ localStorage,
3277
+ sessionStorage
3278
+ };
3279
+ writeFileSync(filePath, JSON.stringify(stateFile, null, 2), "utf-8");
3280
+ return {
3281
+ name,
3282
+ path: filePath,
3283
+ cookies: cookies.length,
3284
+ localStorage: localStorageEntries.length,
3285
+ sessionStorage: sessionStorageEntries.length
3286
+ };
3287
+ }
3288
+ async function browserLoadState(cdp, params) {
3289
+ const { name, url: customUrl } = params;
3290
+ const filePath = getStatePath(name);
3291
+ if (!existsSync2(filePath)) {
3292
+ throw new Error(`State file not found: ${filePath}`);
3293
+ }
3294
+ const stateFile = JSON.parse(readFileSync2(filePath, "utf-8"));
3295
+ const targetUrl = customUrl ?? stateFile.url;
3296
+ if (targetUrl) {
3297
+ await cdp.send("Page.enable");
3298
+ await cdp.send("Page.navigate", { url: targetUrl });
3299
+ }
3300
+ if (stateFile.cookies.length > 0) {
3301
+ await cdp.send("Network.setCookies", { cookies: stateFile.cookies });
3302
+ }
3303
+ const localEntries = Object.entries(stateFile.localStorage);
3304
+ if (localEntries.length > 0) {
3305
+ const localScript = localEntries.map(([k, v]) => `localStorage.setItem(${JSON.stringify(k)}, ${JSON.stringify(v)})`).join(";");
3306
+ await cdp.send("Runtime.evaluate", {
3307
+ expression: localScript,
3308
+ returnByValue: true
3309
+ });
3310
+ }
3311
+ const sessionEntries = Object.entries(stateFile.sessionStorage);
3312
+ if (sessionEntries.length > 0) {
3313
+ const sessionScript = sessionEntries.map(([k, v]) => `sessionStorage.setItem(${JSON.stringify(k)}, ${JSON.stringify(v)})`).join(";");
3314
+ await cdp.send("Runtime.evaluate", {
3315
+ expression: sessionScript,
3316
+ returnByValue: true
3317
+ });
3318
+ }
3319
+ await cdp.send("Page.reload");
3320
+ return {
3321
+ name,
3322
+ cookies: stateFile.cookies.length,
3323
+ localStorage: localEntries.length,
3324
+ sessionStorage: sessionEntries.length
3325
+ };
3326
+ }
3327
+ var init_browser_session_state = __esm({
3328
+ "src/tools/browser-session-state.ts"() {
3329
+ "use strict";
3330
+ }
3331
+ });
3332
+
3333
+ // src/upgrade.ts
3334
+ var upgrade_exports = {};
3335
+ __export(upgrade_exports, {
3336
+ checkForUpgrade: () => checkForUpgrade,
3337
+ getInstallMethod: () => getInstallMethod,
3338
+ getInstallPath: () => getInstallPath,
3339
+ getUpgradeStatus: () => getUpgradeStatus
3340
+ });
3341
+ import { spawn as spawn2, execSync as execSync2 } from "child_process";
3342
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
3343
+ import { homedir as homedir3 } from "os";
3344
+ import { join as join3, dirname } from "path";
3345
+ import { fileURLToPath } from "url";
3346
+ function getInstallMethod() {
3347
+ try {
3348
+ const scriptPath = fileURLToPath(import.meta.url);
3349
+ const parentDir = dirname(dirname(scriptPath));
3350
+ if (existsSync3(join3(parentDir, ".git")) || existsSync3(join3(parentDir, "tsconfig.json"))) {
3351
+ return "dev";
3352
+ }
3353
+ if (scriptPath.includes("_npx") || scriptPath.includes(".npm")) {
3354
+ return "npx";
3355
+ }
3356
+ try {
3357
+ const globalPrefix = execSync2("npm prefix -g", { stdio: "pipe" }).toString().trim();
3358
+ if (scriptPath.startsWith(globalPrefix)) {
3359
+ return "global";
3360
+ }
3361
+ } catch {
3362
+ }
3363
+ return "npx";
3364
+ } catch {
3365
+ return "npx";
3366
+ }
3367
+ }
3368
+ function getInstallPath() {
3369
+ try {
3370
+ return dirname(dirname(fileURLToPath(import.meta.url)));
3371
+ } catch {
3372
+ return "unknown";
3373
+ }
3374
+ }
3375
+ function isNewer(current, latest) {
3376
+ const a = current.split(".").map(Number);
3377
+ const b = latest.split(".").map(Number);
3378
+ for (let i = 0; i < 3; i++) {
3379
+ if ((b[i] ?? 0) > (a[i] ?? 0)) return true;
3380
+ if ((b[i] ?? 0) < (a[i] ?? 0)) return false;
3381
+ }
3382
+ return false;
3383
+ }
3384
+ function getUpgradeStatus() {
3385
+ try {
3386
+ if (!existsSync3(UPGRADE_FILE)) return null;
3387
+ return JSON.parse(readFileSync3(UPGRADE_FILE, "utf-8"));
3388
+ } catch {
3389
+ return null;
3390
+ }
3391
+ }
3392
+ function writeUpgradeStatus(status) {
3393
+ try {
3394
+ mkdirSync3(BROWSIR_DIR, { recursive: true });
3395
+ writeFileSync2(UPGRADE_FILE, JSON.stringify(status, null, 2));
3396
+ } catch {
3397
+ }
3398
+ }
3399
+ async function checkForUpgrade() {
3400
+ try {
3401
+ const method = getInstallMethod();
3402
+ if (method === "dev") return null;
3403
+ const cached = getUpgradeStatus();
3404
+ if (cached) {
3405
+ const elapsed = Date.now() - new Date(cached.checkedAt).getTime();
3406
+ if (elapsed < CHECK_INTERVAL_MS) return cached;
3407
+ }
3408
+ const controller = new AbortController();
3409
+ const timeout = setTimeout(() => controller.abort(), 5e3);
3410
+ const res = await fetch("https://registry.npmjs.org/browsirai/latest", {
3411
+ signal: controller.signal,
3412
+ headers: { Accept: "application/json" }
3413
+ });
3414
+ clearTimeout(timeout);
3415
+ if (!res.ok) return null;
3416
+ const data = await res.json();
3417
+ const latest = data.version;
3418
+ if (!latest) return null;
3419
+ const status = {
3420
+ current: VERSION,
3421
+ latest,
3422
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
3423
+ installMethod: method
3424
+ };
3425
+ writeUpgradeStatus(status);
3426
+ if (isNewer(VERSION, latest)) {
3427
+ process.stderr.write(
3428
+ `browsirai: v${latest} available (current: v${VERSION}). Upgrading in background...
3429
+ `
3430
+ );
3431
+ if (method === "npx") {
3432
+ spawn2("npm", ["cache", "clean", "--force"], {
3433
+ stdio: "ignore",
3434
+ detached: true
3435
+ }).unref();
3436
+ } else if (method === "global") {
3437
+ spawn2("npm", ["install", "-g", `browsirai@${latest}`], {
3438
+ stdio: "ignore",
3439
+ detached: true
3440
+ }).unref();
3441
+ }
3442
+ }
3443
+ return status;
3444
+ } catch {
3445
+ return null;
3446
+ }
3447
+ }
3448
+ var BROWSIR_DIR, UPGRADE_FILE, CHECK_INTERVAL_MS;
3449
+ var init_upgrade = __esm({
3450
+ "src/upgrade.ts"() {
3451
+ "use strict";
3452
+ init_version();
3453
+ BROWSIR_DIR = join3(homedir3(), ".browsirai");
3454
+ UPGRADE_FILE = join3(BROWSIR_DIR, "upgrade.json");
3455
+ CHECK_INTERVAL_MS = 60 * 60 * 1e3;
3456
+ }
3457
+ });
3458
+
3459
+ // src/tools/index.ts
3460
+ import { z } from "zod";
3461
+ function attachLifecycleListeners(conn) {
3462
+ const resetState = () => {
3463
+ browserCdp = null;
3464
+ activeSessionId = null;
3465
+ resetConsoleBuffer();
3466
+ resetNetworkBuffer();
3467
+ };
3468
+ conn.on("reconnectionFailed", resetState);
3469
+ conn.on("browserCrashed", resetState);
3470
+ }
3471
+ async function getCDP() {
3472
+ if (browserCdp?.isConnected && activeSessionId) {
3473
+ return new SessionCDP(browserCdp, activeSessionId);
3474
+ }
3475
+ if (headlessMode) {
3476
+ const launch = await launchHeadlessChrome();
3477
+ if (!launch.success) {
3478
+ throw new Error(launch.error ?? "Failed to launch headless Chrome.");
3479
+ }
3480
+ const wsUrl2 = launch.wsEndpoint ?? `ws://127.0.0.1:${launch.port}/devtools/browser`;
3481
+ browserCdp = new CDPConnection(wsUrl2);
3482
+ await browserCdp.connect();
3483
+ attachLifecycleListeners(browserCdp);
3484
+ const targets2 = await browserCdp.send("Target.getTargets");
3485
+ let page2 = targets2.targetInfos.find((t) => t.type === "page");
3486
+ if (!page2) {
3487
+ const created = await browserCdp.send("Target.createTarget", { url: "about:blank" });
3488
+ page2 = { targetId: created.targetId, type: "page", url: "about:blank", title: "" };
3489
+ }
3490
+ const attached2 = await browserCdp.send("Target.attachToTarget", {
3491
+ targetId: page2.targetId,
3492
+ flatten: true
3493
+ });
3494
+ activeSessionId = attached2.sessionId;
3495
+ const session2 = new SessionCDP(browserCdp, activeSessionId);
3496
+ await Promise.all([
3497
+ session2.send("Network.enable"),
3498
+ session2.send("Runtime.enable"),
3499
+ session2.send("Page.enable")
3500
+ ]).catch(() => {
3501
+ });
3502
+ setupConsoleCapture(session2);
3503
+ setupNetworkCapture(session2);
3504
+ return session2;
3505
+ }
3506
+ const connection = await connectChrome({ autoLaunch: true });
3507
+ if (!connection.success) {
3508
+ throw new Error(
3509
+ connection.error ?? "Cannot connect to Chrome. Run `browsirai doctor` to set up."
3510
+ );
3511
+ }
3512
+ const wsUrl = connection.wsEndpoint ?? `ws://127.0.0.1:${connection.port}/devtools/browser`;
3513
+ browserCdp = new CDPConnection(wsUrl);
3514
+ await browserCdp.connect();
3515
+ attachLifecycleListeners(browserCdp);
3516
+ const targets = await browserCdp.send("Target.getTargets");
3517
+ let page = targets.targetInfos.find(
3518
+ (t) => t.type === "page" && !t.url.startsWith("chrome://")
3519
+ ) ?? targets.targetInfos.find(
3520
+ (t) => t.type === "page"
3521
+ );
3522
+ if (!page) {
3523
+ const created = await browserCdp.send("Target.createTarget", { url: "about:blank" });
3524
+ page = { targetId: created.targetId, type: "page", url: "about:blank", title: "" };
3525
+ }
3526
+ const attached = await browserCdp.send("Target.attachToTarget", {
3527
+ targetId: page.targetId,
3528
+ flatten: true
3529
+ });
3530
+ activeSessionId = attached.sessionId;
3531
+ const session = new SessionCDP(browserCdp, activeSessionId);
3532
+ await Promise.all([
3533
+ session.send("Network.enable"),
3534
+ session.send("Runtime.enable"),
3535
+ session.send("Page.enable")
3536
+ ]).catch(() => {
3537
+ });
3538
+ setupConsoleCapture(session);
3539
+ setupNetworkCapture(session);
3540
+ return session;
3541
+ }
3542
+ function textResult(text) {
3543
+ return { content: [{ type: "text", text }] };
3544
+ }
3545
+ function errorResult(msg) {
3546
+ return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
3547
+ }
3548
+ function appendHint(result, toolName) {
3549
+ const hint = toolHints[toolName];
3550
+ if (!hint) return result;
3551
+ const lastTextIdx = result.content.findLastIndex((c) => c.type === "text");
3552
+ if (lastTextIdx >= 0) {
3553
+ result.content[lastTextIdx] = {
3554
+ ...result.content[lastTextIdx],
3555
+ text: (result.content[lastTextIdx].text ?? "") + hint
3556
+ };
3557
+ } else {
3558
+ result.content.push({ type: "text", text: hint.trimStart() });
3559
+ }
3560
+ return result;
3561
+ }
3562
+ function coerceArgs(args) {
3563
+ const out = {};
3564
+ for (const [k, v] of Object.entries(args)) {
3565
+ if (typeof v === "string") {
3566
+ if (v === "true") {
3567
+ out[k] = true;
3568
+ continue;
3569
+ }
3570
+ if (v === "false") {
3571
+ out[k] = false;
3572
+ continue;
3573
+ }
3574
+ const n = Number(v);
3575
+ if (v !== "" && !isNaN(n)) {
3576
+ out[k] = n;
3577
+ continue;
3578
+ }
3579
+ }
3580
+ out[k] = v;
3581
+ }
3582
+ return out;
3583
+ }
3584
+ function createHandlers() {
3585
+ return {
3586
+ // --- Core tools with real implementations ---
3587
+ browser_navigate: async (args) => {
3588
+ try {
3589
+ if (needsCookieResync() && browserCdp?.isConnected) {
3590
+ browserCdp.close();
3591
+ browserCdp = void 0;
3592
+ activeSessionId = void 0;
3593
+ }
3594
+ const conn = await getCDP();
3595
+ const result = await browserNavigate(conn, args);
3596
+ return textResult(`Navigated to ${result.url}
3597
+ Title: ${result.title}`);
3598
+ } catch (e) {
3599
+ return errorResult(e.message);
3600
+ }
3601
+ },
3602
+ browser_screenshot: async (args) => {
3603
+ try {
3604
+ const conn = await getCDP();
3605
+ const needsImage = args.visual === true || args.fullPage === true || args.selector || args.annotate === true;
3606
+ if (!needsImage) {
3607
+ const snap = await browserSnapshot(conn, {});
3608
+ const text = typeof snap === "string" ? snap : snap.snapshot ?? JSON.stringify(snap, null, 2);
3609
+ return textResult(`[auto-optimized: snapshot returned \u2014 pass visual: true for image]
3610
+
3611
+ ${text}`);
3612
+ }
3613
+ const result = await browserScreenshot(conn, args);
3614
+ const mimeType = args.format === "jpeg" ? "image/jpeg" : "image/png";
3615
+ const content = [{
3616
+ type: "image",
3617
+ data: result.base64,
3618
+ mimeType
3619
+ }];
3620
+ if (result.annotations?.length) {
3621
+ content.push({
3622
+ type: "text",
3623
+ text: result.annotations.map((a) => `${a.label} ${a.role}: ${a.name} (${a.ref})`).join("\n")
3624
+ });
3625
+ }
3626
+ return { content };
3627
+ } catch (e) {
3628
+ return errorResult(e.message);
3629
+ }
3630
+ },
3631
+ browser_tabs: async (args) => {
3632
+ try {
3633
+ const conn = await getCDP();
3634
+ const result = await browserTabs(conn, args);
3635
+ const lines = result.tabs.map((t) => `[${t.id}] ${t.title}
3636
+ ${t.url}`);
3637
+ return textResult(lines.length ? lines.join("\n\n") : "No tabs found");
3638
+ } catch (e) {
3639
+ return errorResult(e.message);
3640
+ }
3641
+ },
3642
+ browser_evaluate: async (args) => {
3643
+ try {
3644
+ const conn = await getCDP();
3645
+ const result = await browserEval(conn, args);
3646
+ if (result.error) return errorResult(result.error);
3647
+ return textResult(JSON.stringify(result.result, null, 2));
3648
+ } catch (e) {
3649
+ return errorResult(e.message);
3650
+ }
3651
+ },
3652
+ browser_snapshot: async (args) => {
3653
+ try {
3654
+ const conn = await getCDP();
3655
+ const result = await browserSnapshot(conn, args);
3656
+ return textResult(typeof result === "string" ? result : JSON.stringify(result, null, 2));
3657
+ } catch (e) {
3658
+ return errorResult(e.message);
3659
+ }
3660
+ },
3661
+ browser_click: async (args) => {
3662
+ try {
3663
+ const conn = await getCDP();
3664
+ const result = await browserClick(conn, args);
3665
+ return textResult(typeof result === "string" ? result : JSON.stringify(result));
3666
+ } catch (e) {
3667
+ return errorResult(e.message);
3668
+ }
3669
+ },
3670
+ browser_scroll: async (args) => {
3671
+ try {
3672
+ const conn = await getCDP();
3673
+ const result = await browserScroll(conn, args);
3674
+ return textResult(typeof result === "string" ? result : JSON.stringify(result));
3675
+ } catch (e) {
3676
+ return errorResult(e.message);
3677
+ }
3678
+ },
3679
+ browser_html: async (args) => {
3680
+ try {
3681
+ const conn = await getCDP();
3682
+ const result = await browserHtml(conn, args);
3683
+ return textResult(typeof result === "string" ? result : JSON.stringify(result));
3684
+ } catch (e) {
3685
+ return errorResult(e.message);
3686
+ }
3687
+ },
3688
+ browser_connect: async (args) => {
3689
+ try {
3690
+ const typed = args;
3691
+ const wasHeadless = headlessMode;
3692
+ headlessMode = typed.headless === true;
3693
+ if ((wasHeadless !== headlessMode || typed.resync === true) && browserCdp?.isConnected) {
3694
+ browserCdp.close();
3695
+ browserCdp = void 0;
3696
+ activeSessionId = void 0;
3697
+ }
3698
+ const conn = await getCDP();
3699
+ const mode = headlessMode ? " (headless)" : "";
3700
+ let summary = SKILL_SUMMARY.replace("Connected to Chrome via CDP.", `Connected to Chrome via CDP${mode}.`);
3701
+ try {
3702
+ const { getUpgradeStatus: getUpgradeStatus2 } = await Promise.resolve().then(() => (init_upgrade(), upgrade_exports));
3703
+ const status = getUpgradeStatus2();
3704
+ if (status && status.latest !== status.current) {
3705
+ summary += `
3706
+
3707
+ \u26A0\uFE0F browsirai v${status.latest} available (current: v${status.current}). Restart to apply.`;
3708
+ }
3709
+ } catch {
3710
+ }
3711
+ return textResult(summary);
3712
+ } catch (e) {
3713
+ return errorResult(e.message);
3714
+ }
3715
+ },
3716
+ browser_list: async () => {
3717
+ try {
3718
+ if (!browserCdp?.isConnected) {
3719
+ await getCDP();
3720
+ }
3721
+ const targets = await browserCdp.send("Target.getTargets");
3722
+ const pages = targets.targetInfos.filter((t) => t.type === "page");
3723
+ const lines = pages.map((t) => `[${t.targetId}] ${t.title}
3724
+ ${t.url}`);
3725
+ return textResult(lines.length ? lines.join("\n\n") : "No pages found");
3726
+ } catch (e) {
3727
+ return errorResult(e.message);
3728
+ }
3729
+ },
3730
+ // --- Newly wired tools ---
3731
+ browser_navigate_back: async (args) => {
3732
+ try {
3733
+ const conn = await getCDP();
3734
+ const result = await browserNavigateBack(conn, args);
3735
+ return textResult(result.url ? `Navigated ${args.direction || "back"} to ${result.url}` : `Navigated ${args.direction || "back"}`);
3736
+ } catch (e) {
3737
+ return errorResult(e.message);
3738
+ }
3739
+ },
3740
+ browser_press_key: async (args) => {
3741
+ try {
3742
+ const conn = await getCDP();
3743
+ await browserPressKey(conn, args);
3744
+ return textResult(`Pressed key: ${args.key}`);
3745
+ } catch (e) {
3746
+ return errorResult(e.message);
3747
+ }
3748
+ },
3749
+ browser_type: async (args) => {
3750
+ try {
3751
+ const conn = await getCDP();
3752
+ await browserType(conn, args);
3753
+ return textResult(`Typed ${args.text.length} characters`);
3754
+ } catch (e) {
3755
+ return errorResult(e.message);
3756
+ }
3757
+ },
3758
+ browser_fill_form: async (args) => {
3759
+ try {
3760
+ const conn = await getCDP();
3761
+ const fields = [{
3762
+ name: args.selector ?? args.ref ?? "field",
3763
+ type: "textbox",
3764
+ ref: args.ref,
3765
+ selector: args.selector,
3766
+ value: args.value
3767
+ }];
3768
+ await browserFillForm(conn, { fields });
3769
+ return textResult(`Filled form field with value`);
3770
+ } catch (e) {
3771
+ return errorResult(e.message);
3772
+ }
3773
+ },
3774
+ browser_hover: async (args) => {
3775
+ try {
3776
+ const conn = await getCDP();
3777
+ await browserHover(conn, args);
3778
+ return textResult(`Hovered over element`);
3779
+ } catch (e) {
3780
+ return errorResult(e.message);
3781
+ }
3782
+ },
3783
+ browser_drag: async (args) => {
3784
+ try {
3785
+ const conn = await getCDP();
3786
+ await browserDrag(conn, args);
3787
+ return textResult(`Dragged from ${args.startRef} to ${args.endRef}`);
3788
+ } catch (e) {
3789
+ return errorResult(e.message);
3790
+ }
3791
+ },
3792
+ browser_select_option: async (args) => {
3793
+ try {
3794
+ const conn = await getCDP();
3795
+ const result = await browserSelectOption(conn, args);
3796
+ return textResult(`Selected: ${result.selected.join(", ")}`);
3797
+ } catch (e) {
3798
+ return errorResult(e.message);
3799
+ }
3800
+ },
3801
+ browser_handle_dialog: async (args) => {
3802
+ try {
3803
+ const conn = await getCDP();
3804
+ await browserHandleDialog(conn, args);
3805
+ return textResult(`Dialog ${args.accept ? "accepted" : "dismissed"}`);
3806
+ } catch (e) {
3807
+ return errorResult(e.message);
3808
+ }
3809
+ },
3810
+ browser_file_upload: async (args) => {
3811
+ try {
3812
+ const conn = await getCDP();
3813
+ const result = await browserFileUpload(conn, args);
3814
+ return textResult(`Uploaded ${result.filesCount} file(s)`);
3815
+ } catch (e) {
3816
+ return errorResult(e.message);
3817
+ }
3818
+ },
3819
+ browser_wait_for: async (args) => {
3820
+ try {
3821
+ const conn = await getCDP();
3822
+ const result = await browserWaitFor(conn, args);
3823
+ return textResult(`Wait completed in ${result.elapsed}ms`);
3824
+ } catch (e) {
3825
+ return errorResult(e.message);
3826
+ }
3827
+ },
3828
+ browser_resize: async (args) => {
3829
+ try {
3830
+ const conn = await getCDP();
3831
+ const result = await browserResize(conn, args);
3832
+ return textResult(`Resized to ${result.width}x${result.height}`);
3833
+ } catch (e) {
3834
+ return errorResult(e.message);
3835
+ }
3836
+ },
3837
+ browser_close: async (args) => {
3838
+ try {
3839
+ const conn = await getCDP();
3840
+ const result = await browserClose(conn, args);
3841
+ return textResult(`Closed ${result.closedTargets} tab(s)`);
3842
+ } catch (e) {
3843
+ return errorResult(e.message);
3844
+ }
3845
+ },
3846
+ browser_network_requests: async (args) => {
3847
+ try {
3848
+ const conn = await getCDP();
3849
+ const result = await browserNetworkRequests(conn, args);
3850
+ const lines = result.requests.map((r) => `${r.method} ${r.status ?? "?"} ${r.url}`);
3851
+ return textResult(lines.length ? lines.join("\n") : "No network requests captured");
3852
+ } catch (e) {
3853
+ return errorResult(e.message);
3854
+ }
3855
+ },
3856
+ browser_console_messages: async (args) => {
3857
+ try {
3858
+ const conn = await getCDP();
3859
+ const result = await browserConsoleMessages(conn, args);
3860
+ const lines = result.messages.map((m) => `[${m.level}] ${m.text}`);
3861
+ return textResult(lines.length ? lines.join("\n") : "No console messages");
3862
+ } catch (e) {
3863
+ return errorResult(e.message);
3864
+ }
3865
+ },
3866
+ browser_inspect_source: async (args) => {
3867
+ try {
3868
+ const conn = await getCDP();
3869
+ const result = await browserInspectSource(conn, args);
3870
+ const lines = [];
3871
+ lines.push(`Element: <${result.tagName}>`);
3872
+ if (result.componentName) {
3873
+ lines.push(`Component: ${result.componentName}`);
3874
+ }
3875
+ if (result.source) {
3876
+ lines.push(`Source: ${result.source.filePath}:${result.source.lineNumber ?? "?"}:${result.source.columnNumber ?? "?"}`);
3877
+ } else {
3878
+ lines.push("Source: not found (no framework metadata detected)");
3879
+ }
3880
+ if (result.stack.length > 0) {
3881
+ lines.push("\nComponent Stack:");
3882
+ for (const frame of result.stack) {
3883
+ const loc = `${frame.filePath}:${frame.lineNumber ?? "?"}`;
3884
+ lines.push(` ${frame.componentName ?? "anonymous"} (${loc})`);
3885
+ }
3886
+ }
3887
+ return textResult(lines.join("\n"));
3888
+ } catch (e) {
3889
+ return errorResult(e.message);
3890
+ }
3891
+ },
3892
+ browser_route: async (args) => {
3893
+ try {
3894
+ const conn = await getCDP();
3895
+ const result = await browserRoute(conn, args);
3896
+ return textResult(`Route registered: ${result.url} \u2192 ${result.status}
3897
+ Active routes: ${result.activeRoutes}`);
3898
+ } catch (e) {
3899
+ return errorResult(e.message);
3900
+ }
3901
+ },
3902
+ browser_abort: async (args) => {
3903
+ try {
3904
+ const conn = await getCDP();
3905
+ const result = await browserAbort(conn, args);
3906
+ return textResult(`Abort registered: ${result.url}
3907
+ Active aborts: ${result.activeAborts}`);
3908
+ } catch (e) {
3909
+ return errorResult(e.message);
3910
+ }
3911
+ },
3912
+ browser_unroute: async (args) => {
3913
+ try {
3914
+ const conn = await getCDP();
3915
+ const result = await browserUnroute(conn, args);
3916
+ return textResult(`Removed ${result.removed} rule(s)
3917
+ Remaining: ${result.remaining}`);
3918
+ } catch (e) {
3919
+ return errorResult(e.message);
3920
+ }
3921
+ },
3922
+ browser_annotated_screenshot: async (args) => {
3923
+ try {
3924
+ const conn = await getCDP();
3925
+ const result = await browserAnnotatedScreenshot(conn, args);
3926
+ const content = [{
3927
+ type: "image",
3928
+ data: result.base64,
3929
+ mimeType: "image/png"
3930
+ }];
3931
+ if (result.annotations?.length) {
3932
+ content.push({
3933
+ type: "text",
3934
+ text: result.annotations.map((a) => `${a.label} ${a.role}: ${a.name} (${a.ref})`).join("\n")
3935
+ });
3936
+ }
3937
+ return { content };
3938
+ } catch (e) {
3939
+ return errorResult(e.message);
3940
+ }
3941
+ },
3942
+ browser_diff: async (args) => {
3943
+ try {
3944
+ const conn = await getCDP();
3945
+ const result = await browserDiff(conn, args);
3946
+ const content = [{
3947
+ type: "image",
3948
+ data: result.diffImage,
3949
+ mimeType: "image/png"
3950
+ }, {
3951
+ type: "text",
3952
+ text: [
3953
+ `Diff: ${result.diffPercentage}% changed`,
3954
+ `Pixels: ${result.diffPixels} / ${result.totalPixels} differ`,
3955
+ `Dimensions: ${result.width}x${result.height}`,
3956
+ `Identical: ${result.identical}`
3957
+ ].join("\n")
3958
+ }];
3959
+ return { content };
3960
+ } catch (e) {
3961
+ return errorResult(e.message);
3962
+ }
3963
+ },
3964
+ browser_find: async (args) => {
3965
+ try {
3966
+ const conn = await getCDP();
3967
+ const result = await browserFind(conn, args);
3968
+ if (!result.found) {
3969
+ return textResult(`No match found (${result.count} candidates)`);
3970
+ }
3971
+ const lines = [];
3972
+ lines.push(`Found: ${result.ref ?? "no ref"}`);
3973
+ lines.push(`Role: ${result.role ?? "unknown"}`);
3974
+ lines.push(`Name: ${result.name ?? ""}`);
3975
+ lines.push(`Matches: ${result.count}`);
3976
+ return textResult(lines.join("\n"));
3977
+ } catch (e) {
3978
+ return errorResult(e.message);
3979
+ }
3980
+ },
3981
+ browser_save_state: async (args) => {
3982
+ try {
3983
+ const conn = await getCDP();
3984
+ const result = await browserSaveState(conn, args);
3985
+ return textResult(
3986
+ `State "${result.name}" saved to ${result.path}
3987
+ Cookies: ${result.cookies}, localStorage: ${result.localStorage}, sessionStorage: ${result.sessionStorage}`
3988
+ );
3989
+ } catch (e) {
3990
+ return errorResult(e.message);
3991
+ }
3992
+ },
3993
+ browser_load_state: async (args) => {
3994
+ try {
3995
+ const conn = await getCDP();
3996
+ const result = await browserLoadState(conn, args);
3997
+ return textResult(
3998
+ `State "${result.name}" restored
3999
+ Cookies: ${result.cookies}, localStorage: ${result.localStorage}, sessionStorage: ${result.sessionStorage}`
4000
+ );
4001
+ } catch (e) {
4002
+ return errorResult(e.message);
4003
+ }
4004
+ }
4005
+ };
4006
+ }
4007
+ function registerTools(server, _manager) {
4008
+ const handlers = createHandlers();
4009
+ const toolNames = Object.keys(schemas);
4010
+ for (const name of toolNames) {
4011
+ const description = descriptions[name] ?? name;
4012
+ const shape = toolShapes[name] ?? {};
4013
+ const rawHandler = handlers[name] ?? (async () => textResult(`${name}: not yet implemented`));
4014
+ const handler = async (args) => {
4015
+ const coerced = coerceArgs(args);
4016
+ const result = await rawHandler(coerced);
4017
+ return appendHint(result, name);
4018
+ };
4019
+ server.tool(name, description, shape, handler);
4020
+ }
4021
+ }
4022
+ var browserCdp, activeSessionId, SessionCDP, headlessMode, SKILL_SUMMARY, toolHints, browser_connect, browser_tabs, browser_snapshot, browser_screenshot, browser_click, browser_fill_form, browser_type, browser_press_key, browser_navigate, browser_navigate_back, browser_evaluate, browser_scroll, browser_network_requests, browser_console_messages, browser_html, browser_close, browser_wait_for, browser_hover, browser_drag, browser_select_option, browser_handle_dialog, browser_file_upload, browser_resize, browser_annotated_screenshot, browser_inspect_source, browser_route, browser_abort, browser_unroute, browser_find, browser_diff, browser_save_state, browser_load_state, browser_list, schemas, descriptions, cNum, cBool, toolShapes;
4023
+ var init_tools = __esm({
4024
+ "src/tools/index.ts"() {
4025
+ "use strict";
4026
+ init_connection();
4027
+ init_chrome_launcher();
4028
+ init_browser_navigate();
4029
+ init_browser_screenshot();
4030
+ init_browser_tabs();
4031
+ init_browser_eval();
4032
+ init_browser_snapshot();
4033
+ init_browser_click();
4034
+ init_browser_scroll();
4035
+ init_browser_html();
4036
+ init_browser_navigate_back();
4037
+ init_browser_press_key();
4038
+ init_browser_hover();
4039
+ init_browser_resize();
4040
+ init_browser_close();
4041
+ init_browser_fill_form();
4042
+ init_browser_type();
4043
+ init_browser_select_option();
4044
+ init_browser_wait_for();
4045
+ init_browser_drag();
4046
+ init_browser_handle_dialog();
4047
+ init_browser_file_upload();
4048
+ init_browser_network_requests();
4049
+ init_browser_console_messages();
4050
+ init_browser_annotated_screenshot();
4051
+ init_browser_inspect_source();
4052
+ init_browser_intercept();
4053
+ init_browser_find();
4054
+ init_browser_diff();
4055
+ init_browser_session_state();
4056
+ browserCdp = null;
4057
+ activeSessionId = null;
4058
+ SessionCDP = class {
4059
+ constructor(conn, sessionId) {
4060
+ this.conn = conn;
4061
+ this.sessionId = sessionId;
4062
+ }
4063
+ get isConnected() {
4064
+ return this.conn.isConnected;
4065
+ }
4066
+ send(method, params, options) {
4067
+ return this.conn.send(method, params, {
4068
+ ...options,
4069
+ sessionId: options?.sessionId ?? this.sessionId
4070
+ });
4071
+ }
4072
+ on(event, handler) {
4073
+ this.conn.on(event, handler);
4074
+ }
4075
+ off(event, handler) {
4076
+ this.conn.off(event, handler);
4077
+ }
4078
+ close() {
4079
+ }
4080
+ };
4081
+ headlessMode = process.env.BROWSIR_HEADLESS === "1" || process.env.BROWSIR_HEADLESS === "true";
4082
+ SKILL_SUMMARY = `Connected to Chrome via CDP.
4083
+
4084
+ ## browsirai \u2014 Quick Reference
4085
+
4086
+ **Cost hierarchy (cheapest first):**
4087
+ 1. \`browser_evaluate\` \u2014 JS expression for single values (~10 tokens). Use when you need one data point (count, text, attribute).
4088
+ 2. \`browser_snapshot\` \u2014 accessibility tree with @eN refs (~500 tokens). Use for page understanding and interaction.
4089
+ 3. \`browser_screenshot { visual: true }\` \u2014 full image (~10K tokens). ONLY for layout/colors/visual bugs.
4090
+
4091
+ **Core workflow: snapshot \u2192 ref \u2192 interact \u2192 snapshot**
4092
+
4093
+ 1. \`browser_snapshot\` \u2014 get accessibility tree with @eN refs
4094
+ 2. Use @eN refs with: \`browser_click\`, \`browser_fill_form\`, \`browser_hover\`, \`browser_type\`, \`browser_select_option\`, \`browser_drag\`, \`browser_inspect_source\`
4095
+ 3. \`browser_snapshot\` \u2014 verify result (ALWAYS use this)
4096
+
4097
+ **Cost optimization (enforced server-side):**
4098
+ - \`browser_screenshot\` without \`visual: true\` auto-returns snapshot text
4099
+ - For single data extraction: \`browser_evaluate\` > \`browser_snapshot\` (50x cheaper)
4100
+ - Reserve \`browser_screenshot { visual: true }\` for CSS/layout debugging only
4101
+
4102
+ **Identity resolution (cookie-based, always-first):**
4103
+ - NEVER guess usernames. The browser has an active session \u2014 use it.
4104
+ - To find the logged-in user: \`browser_evaluate\` on the site (e.g. GitHub avatar menu, X profile link).
4105
+ - When asked "go to my profile/repo/account" \u2192 navigate to the site root first, extract identity from session, then proceed.
4106
+ - Cookie sync means the browser IS the user. Trust the session, not assumptions.
4107
+
4108
+ **Key patterns:**
4109
+ - \`browser_fill_form\` clears existing value. Use \`browser_type\` to append.
4110
+ - \`browser_type\` with \`submit: true\` presses Enter after typing.
4111
+ - \`browser_wait_for\` \u2014 wait for text, selector, URL, or JS condition before proceeding.
4112
+ - \`browser_inspect_source\` \u2014 find source file, line, component name (React/Vue/Svelte, dev mode only).
4113
+ - \`browser_resize\` with \`preset: "reset"\` \u2014 restore native viewport.
4114
+ - Refs become stale after navigation or major DOM changes \u2014 take a new snapshot.
4115
+ - \`browser_evaluate\` cannot access cross-origin iframes \u2014 use \`browser_type\` instead.
4116
+ - \`browser_console_messages\` / \`browser_network_requests\` \u2014 check for errors after interactions.`;
4117
+ toolHints = {
4118
+ browser_navigate: "\n\n\u2192 Next: browser_evaluate for quick data extraction (~10 tokens), or browser_snapshot for page structure and @eN refs (~500 tokens).",
4119
+ browser_navigate_back: "\n\n\u2192 Next: browser_snapshot to see updated page.",
4120
+ browser_snapshot: "\n\n\u2192 Next: Use @eN refs with browser_click, browser_fill_form, browser_hover, browser_type, browser_select_option, browser_drag, or browser_inspect_source.",
4121
+ browser_screenshot: "\n\n\u2192 Cost: This call was auto-optimized to snapshot text. For full image, pass { visual: true }. Prefer browser_snapshot (~500 tokens) over browser_screenshot (~10K tokens).",
4122
+ browser_annotated_screenshot: "\n\n\u2192 Cost: ~12K tokens. Consider browser_snapshot { interactive: true } (~500 tokens) for same info as text.",
4123
+ browser_click: "\n\n\u2192 Next: browser_snapshot to verify result. Refs may be stale after DOM changes.",
4124
+ browser_fill_form: "\n\n\u2192 Next: browser_snapshot to verify. Note: fill_form clears existing value first.",
4125
+ browser_type: "\n\n\u2192 Tip: Use submit: true to press Enter. Does NOT clear existing value (unlike fill_form).",
4126
+ browser_press_key: "\n\n\u2192 Next: browser_snapshot to verify effect.",
4127
+ browser_hover: "\n\n\u2192 Next: browser_snapshot to verify hover state. Use browser_screenshot { visual: true } only for visual hover effects.",
4128
+ browser_drag: "\n\n\u2192 Next: browser_snapshot to verify drag result.",
4129
+ browser_scroll: "\n\n\u2192 Next: browser_snapshot to see new viewport content.",
4130
+ browser_select_option: "\n\n\u2192 Next: browser_snapshot to verify selection.",
4131
+ browser_handle_dialog: "\n\n\u2192 Next: browser_snapshot to see page state after dialog.",
4132
+ browser_file_upload: "\n\n\u2192 Next: browser_snapshot to verify upload.",
4133
+ browser_wait_for: "\n\n\u2192 Next: browser_snapshot to see current page state.",
4134
+ browser_resize: '\n\n\u2192 Next: browser_snapshot to verify viewport. Use browser_screenshot { visual: true } only to check visual layout. Use preset: "reset" to restore.',
4135
+ browser_close: "\n\n\u2192 Next: browser_tabs to see remaining tabs.",
4136
+ browser_evaluate: "\n\n\u2192 Tip: Cheapest tool (~10 tokens). Use for single data extraction (counts, text, attributes). Prefer browser_snapshot + @eN refs for DOM interaction. Cannot access cross-origin iframes.",
4137
+ browser_html: "\n\n\u2192 Tip: Prefer browser_snapshot for structured page understanding with @eN refs.",
4138
+ browser_tabs: "\n\n\u2192 Tip: Use browser_navigate to open a URL in current tab.",
4139
+ browser_console_messages: '\n\n\u2192 Tip: Use level: "error" to filter for errors only.',
4140
+ browser_network_requests: '\n\n\u2192 Tip: Use filter: "*api*" and includeStatic: false for API calls only.',
4141
+ browser_inspect_source: "\n\n\u2192 Tip: Dev mode only. Use browser_snapshot first to get @eN refs for specific elements.",
4142
+ browser_route: "\n\n\u2192 Tip: Use browser_network_requests to verify intercepted responses. Use browser_unroute to remove.",
4143
+ browser_abort: "\n\n\u2192 Tip: Use browser_network_requests to verify blocked requests. Use browser_unroute to remove.",
4144
+ browser_unroute: "\n\n\u2192 Tip: Use {all: true} to clear all intercepts at once.",
4145
+ browser_find: "\n\n\u2192 Next: Use the @eN ref with browser_click, browser_fill_form, browser_hover, or browser_inspect_source.",
4146
+ browser_diff: "\n\n\u2192 Tip: Use 'current' as before value to capture the page now. Make changes, then call again with the first result as before.",
4147
+ browser_save_state: "\n\n\u2192 Tip: State saved to ~/.browsirai/states/. Use browser_load_state to restore later.",
4148
+ browser_load_state: "\n\n\u2192 Next: browser_snapshot to see the restored page state.",
4149
+ browser_connect: "",
4150
+ // SKILL_SUMMARY is returned directly
4151
+ browser_list: "\n\n\u2192 Tip: Use browser_connect to connect to a specific instance."
4152
+ };
4153
+ browser_connect = z.object({
4154
+ port: z.number().optional(),
4155
+ host: z.string().optional(),
4156
+ headless: z.boolean().optional()
4157
+ });
4158
+ browser_tabs = z.object({
4159
+ filter: z.string().optional()
4160
+ });
4161
+ browser_snapshot = z.object({
4162
+ selector: z.string().optional(),
4163
+ compact: z.boolean().optional(),
4164
+ interactive: z.boolean().optional(),
4165
+ cursor: z.boolean().optional(),
4166
+ depth: z.number().optional()
4167
+ });
4168
+ browser_screenshot = z.object({
4169
+ selector: z.string().optional(),
4170
+ fullPage: z.boolean().optional(),
4171
+ format: z.enum(["png", "jpeg"]).optional(),
4172
+ quality: z.number().optional(),
4173
+ annotate: z.boolean().optional(),
4174
+ visual: z.boolean().optional()
4175
+ });
4176
+ browser_click = z.object({
4177
+ selector: z.string().optional(),
4178
+ ref: z.string().optional(),
4179
+ x: z.number().optional(),
4180
+ y: z.number().optional(),
4181
+ newTab: z.boolean().optional()
4182
+ }).refine(
4183
+ (data) => {
4184
+ if (data.selector) return true;
4185
+ if (data.ref) return true;
4186
+ if (data.x !== void 0 && data.y !== void 0) return true;
4187
+ return false;
4188
+ },
4189
+ { message: "Must provide selector, ref, or both x and y coordinates" }
4190
+ );
4191
+ browser_fill_form = z.object({
4192
+ ref: z.string().optional(),
4193
+ selector: z.string().optional(),
4194
+ value: z.string()
4195
+ }).refine(
4196
+ (data) => data.ref !== void 0 || data.selector !== void 0,
4197
+ { message: "Must provide ref or selector" }
4198
+ );
4199
+ browser_type = z.object({
4200
+ text: z.string(),
4201
+ ref: z.string().optional(),
4202
+ slowly: z.boolean().optional(),
4203
+ submit: z.boolean().optional()
4204
+ });
4205
+ browser_press_key = z.object({
4206
+ key: z.string()
4207
+ });
4208
+ browser_navigate = z.object({
4209
+ url: z.string(),
4210
+ waitUntil: z.enum(["load", "domcontentloaded", "networkidle"]).optional()
4211
+ });
4212
+ browser_navigate_back = z.object({
4213
+ direction: z.enum(["back", "forward"]).optional()
4214
+ });
4215
+ browser_evaluate = z.object({
4216
+ expression: z.string(),
4217
+ frameId: z.string().optional()
4218
+ });
4219
+ browser_scroll = z.object({
4220
+ direction: z.enum(["up", "down", "left", "right"]).optional(),
4221
+ pixels: z.number().optional(),
4222
+ selector: z.string().optional()
4223
+ });
4224
+ browser_network_requests = z.object({
4225
+ filter: z.string().optional(),
4226
+ limit: z.number().optional(),
4227
+ includeHeaders: z.boolean().optional(),
4228
+ includeStatic: z.boolean().optional()
4229
+ });
4230
+ browser_console_messages = z.object({
4231
+ limit: z.number().optional(),
4232
+ level: z.enum(["log", "warn", "error", "info"]).optional()
4233
+ });
4234
+ browser_html = z.object({
4235
+ selector: z.string().optional()
4236
+ });
4237
+ browser_close = z.object({
4238
+ targetId: z.string().optional(),
4239
+ force: z.boolean().optional(),
4240
+ closeAll: z.boolean().optional()
4241
+ });
4242
+ browser_wait_for = z.object({
4243
+ text: z.string().optional(),
4244
+ textGone: z.string().optional(),
4245
+ time: z.number().optional(),
4246
+ timeout: z.number().optional(),
4247
+ url: z.string().optional(),
4248
+ fn: z.string().optional(),
4249
+ selector: z.string().optional(),
4250
+ state: z.string().optional(),
4251
+ loadState: z.string().optional()
4252
+ });
4253
+ browser_hover = z.object({
4254
+ ref: z.string()
4255
+ });
4256
+ browser_drag = z.object({
4257
+ startRef: z.string(),
4258
+ endRef: z.string()
4259
+ });
4260
+ browser_select_option = z.object({
4261
+ ref: z.string(),
4262
+ values: z.array(z.string())
4263
+ });
4264
+ browser_handle_dialog = z.object({
4265
+ accept: z.boolean(),
4266
+ promptText: z.string().optional()
4267
+ });
4268
+ browser_file_upload = z.object({
4269
+ ref: z.string(),
4270
+ paths: z.array(z.string())
4271
+ });
4272
+ browser_resize = z.object({
4273
+ width: z.number().optional(),
4274
+ height: z.number().optional(),
4275
+ deviceScaleFactor: z.number().optional(),
4276
+ preset: z.string().optional()
4277
+ }).refine(
4278
+ (data) => {
4279
+ if (data.preset) return true;
4280
+ if (data.width !== void 0 && data.height !== void 0) return true;
4281
+ return false;
4282
+ },
4283
+ { message: "Must provide both width and height together, or a preset" }
4284
+ );
4285
+ browser_annotated_screenshot = z.object({
4286
+ selector: z.string().optional()
4287
+ });
4288
+ browser_inspect_source = z.object({
4289
+ ref: z.string().optional(),
4290
+ selector: z.string().optional()
4291
+ }).refine(
4292
+ (data) => data.ref !== void 0 || data.selector !== void 0,
4293
+ { message: "Must provide ref or selector" }
4294
+ );
4295
+ browser_route = z.object({
4296
+ url: z.string(),
4297
+ body: z.string(),
4298
+ status: z.number().optional(),
4299
+ headers: z.record(z.string()).optional()
4300
+ });
4301
+ browser_abort = z.object({
4302
+ url: z.string()
4303
+ });
4304
+ browser_unroute = z.object({
4305
+ url: z.string().optional(),
4306
+ all: z.boolean().optional()
4307
+ }).refine(
4308
+ (data) => data.url || data.all,
4309
+ { message: "Must provide url or all" }
4310
+ );
4311
+ browser_find = z.object({
4312
+ role: z.string().optional(),
4313
+ name: z.string().optional(),
4314
+ text: z.string().optional(),
4315
+ nth: z.number().optional()
4316
+ }).refine(
4317
+ (data) => data.role !== void 0 || data.name !== void 0 || data.text !== void 0,
4318
+ { message: "Must provide at least one of role, name, or text" }
4319
+ );
4320
+ browser_diff = z.object({
4321
+ before: z.string(),
4322
+ after: z.string().optional(),
4323
+ selector: z.string().optional(),
4324
+ threshold: z.number().optional()
4325
+ });
4326
+ browser_save_state = z.object({
4327
+ name: z.string()
4328
+ });
4329
+ browser_load_state = z.object({
4330
+ name: z.string(),
4331
+ url: z.string().optional()
4332
+ });
4333
+ browser_list = z.object({}).passthrough().optional().transform((val) => val ?? {});
4334
+ schemas = {
4335
+ browser_connect,
4336
+ browser_tabs,
4337
+ browser_snapshot,
4338
+ browser_screenshot,
4339
+ browser_click,
4340
+ browser_fill_form,
4341
+ browser_type,
4342
+ browser_press_key,
4343
+ browser_navigate,
4344
+ browser_navigate_back,
4345
+ browser_evaluate,
4346
+ browser_scroll,
4347
+ browser_network_requests,
4348
+ browser_console_messages,
4349
+ browser_html,
4350
+ browser_close,
4351
+ browser_wait_for,
4352
+ browser_hover,
4353
+ browser_drag,
4354
+ browser_select_option,
4355
+ browser_handle_dialog,
4356
+ browser_file_upload,
4357
+ browser_resize,
4358
+ browser_annotated_screenshot,
4359
+ browser_inspect_source,
4360
+ browser_route,
4361
+ browser_abort,
4362
+ browser_unroute,
4363
+ browser_find,
4364
+ browser_diff,
4365
+ browser_save_state,
4366
+ browser_load_state,
4367
+ browser_list
4368
+ };
4369
+ descriptions = {
4370
+ browser_connect: "Connect to a running browser instance via Chrome DevTools Protocol. Use headless: true to run in background without visible window. Use resync: true to force cookie re-sync (e.g. after switching Chrome profile).",
4371
+ browser_tabs: "List open browser tabs, optionally filtered by title or URL",
4372
+ browser_snapshot: "Capture an accessibility snapshot of the current page or a specific element. [~500 tokens, PREFERRED for page understanding]",
4373
+ browser_screenshot: "Take a screenshot. Auto-returns snapshot text unless visual: true, fullPage: true, or selector is specified. [~10K tokens when image returned]",
4374
+ browser_click: "Click an element identified by selector, ref, or coordinates",
4375
+ browser_fill_form: "Fill a form field identified by ref or selector with a value",
4376
+ browser_type: "Type text into the focused element or a specific ref",
4377
+ browser_press_key: "Press a keyboard key or key combination",
4378
+ browser_navigate: "Navigate to a URL",
4379
+ browser_navigate_back: "Navigate back or forward in browser history",
4380
+ browser_evaluate: "Evaluate a JavaScript expression in the page context",
4381
+ browser_scroll: "Scroll the page or a specific element in a given direction",
4382
+ browser_network_requests: "List captured network requests, optionally filtered",
4383
+ browser_console_messages: "Retrieve console messages from the page, optionally filtered by level",
4384
+ browser_html: "Get the HTML content of the page or a specific element",
4385
+ browser_close: "Close a browser tab or the entire browser",
4386
+ browser_wait_for: "Wait for a condition: text appearance/disappearance, time, URL, selector, or JS function",
4387
+ browser_hover: "Hover over an element identified by ref",
4388
+ browser_drag: "Drag from one element to another by ref",
4389
+ browser_select_option: "Select option(s) in a select element identified by ref",
4390
+ browser_handle_dialog: "Accept or dismiss a browser dialog (alert, confirm, prompt)",
4391
+ browser_file_upload: "Upload file(s) to a file input element identified by ref",
4392
+ browser_resize: "Resize the browser viewport to specific dimensions or a preset",
4393
+ browser_annotated_screenshot: "Take a screenshot with element annotations overlaid. [~12K tokens, prefer browser_snapshot { interactive: true } for text alternative]",
4394
+ browser_inspect_source: "Inspect a DOM element and return its source code location (file, line, component name). Works with React, Vue, Svelte, Solid.",
4395
+ browser_route: "Intercept requests matching a URL pattern and respond with a custom body, status, and headers",
4396
+ browser_abort: "Block requests matching a URL pattern",
4397
+ browser_unroute: "Remove request intercept rules \u2014 a specific pattern or all at once",
4398
+ browser_find: "Find elements by ARIA role, accessible name, or text content. Returns @eN ref for use with other tools.",
4399
+ browser_diff: "Compare two screenshots pixel-by-pixel. Returns diff percentage and visual diff image highlighting changes. [~11K tokens]",
4400
+ browser_save_state: "Save browser state (cookies, localStorage, sessionStorage) to a named file for later restoration",
4401
+ browser_load_state: "Load a previously saved browser state (cookies, storage) and optionally navigate to a URL",
4402
+ browser_list: "List available browser instances"
4403
+ };
4404
+ cNum = z.coerce.number();
4405
+ cBool = z.preprocess(
4406
+ (v) => v === "true" ? true : v === "false" ? false : v,
4407
+ z.boolean()
4408
+ );
4409
+ toolShapes = {
4410
+ browser_connect: { port: cNum.optional(), host: z.string().optional(), headless: cBool.optional(), resync: cBool.optional() },
4411
+ browser_tabs: { filter: z.string().optional() },
4412
+ browser_snapshot: {
4413
+ selector: z.string().optional(),
4414
+ compact: cBool.optional(),
4415
+ interactive: cBool.optional(),
4416
+ cursor: cBool.optional(),
4417
+ depth: cNum.optional()
4418
+ },
4419
+ browser_screenshot: {
4420
+ selector: z.string().optional(),
4421
+ fullPage: cBool.optional(),
4422
+ format: z.enum(["png", "jpeg"]).optional(),
4423
+ quality: cNum.optional(),
4424
+ annotate: cBool.optional(),
4425
+ visual: cBool.optional()
4426
+ },
4427
+ browser_click: {
4428
+ selector: z.string().optional(),
4429
+ ref: z.string().optional(),
4430
+ x: cNum.optional(),
4431
+ y: cNum.optional(),
4432
+ newTab: cBool.optional()
4433
+ },
4434
+ browser_fill_form: { ref: z.string().optional(), selector: z.string().optional(), value: z.string() },
4435
+ browser_type: { text: z.string(), ref: z.string().optional(), slowly: cBool.optional(), submit: cBool.optional() },
4436
+ browser_press_key: { key: z.string() },
4437
+ browser_navigate: { url: z.string(), waitUntil: z.enum(["load", "domcontentloaded", "networkidle"]).optional() },
4438
+ browser_navigate_back: { direction: z.enum(["back", "forward"]).optional() },
4439
+ browser_evaluate: { expression: z.string(), frameId: z.string().optional() },
4440
+ browser_scroll: { direction: z.enum(["up", "down", "left", "right"]).optional(), pixels: cNum.optional(), selector: z.string().optional() },
4441
+ browser_network_requests: { filter: z.string().optional(), limit: cNum.optional(), includeHeaders: cBool.optional(), includeStatic: cBool.optional() },
4442
+ browser_console_messages: { limit: cNum.optional(), level: z.enum(["log", "warn", "error", "info"]).optional() },
4443
+ browser_html: { selector: z.string().optional() },
4444
+ browser_close: { targetId: z.string().optional(), force: cBool.optional(), closeAll: cBool.optional() },
4445
+ browser_wait_for: { text: z.string().optional(), textGone: z.string().optional(), time: cNum.optional(), timeout: cNum.optional(), url: z.string().optional(), fn: z.string().optional(), selector: z.string().optional(), state: z.string().optional(), loadState: z.string().optional() },
4446
+ browser_hover: { ref: z.string() },
4447
+ browser_drag: { startRef: z.string(), endRef: z.string() },
4448
+ browser_select_option: { ref: z.string(), values: z.array(z.string()) },
4449
+ browser_handle_dialog: { accept: cBool, promptText: z.string().optional() },
4450
+ browser_file_upload: { ref: z.string(), paths: z.array(z.string()) },
4451
+ browser_resize: { width: cNum.optional(), height: cNum.optional(), deviceScaleFactor: cNum.optional(), preset: z.string().optional() },
4452
+ browser_annotated_screenshot: { selector: z.string().optional() },
4453
+ browser_inspect_source: { ref: z.string().optional(), selector: z.string().optional() },
4454
+ browser_route: { url: z.string(), body: z.string(), status: cNum.optional(), headers: z.record(z.string()).optional() },
4455
+ browser_abort: { url: z.string() },
4456
+ browser_unroute: { url: z.string().optional(), all: cBool.optional() },
4457
+ browser_find: { role: z.string().optional(), name: z.string().optional(), text: z.string().optional(), nth: cNum.optional() },
4458
+ browser_diff: { before: z.string(), after: z.string().optional(), selector: z.string().optional(), threshold: cNum.optional() },
4459
+ browser_save_state: { name: z.string() },
4460
+ browser_load_state: { name: z.string(), url: z.string().optional() },
4461
+ browser_list: {}
4462
+ };
4463
+ }
4464
+ });
4465
+
4466
+ // src/server.ts
4467
+ var server_exports = {};
4468
+ __export(server_exports, {
4469
+ createServer: () => createServer
4470
+ });
4471
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4472
+ async function createServer() {
4473
+ const server = new McpServer({
4474
+ name: "browsirai",
4475
+ version: VERSION
4476
+ });
4477
+ registerTools(server);
4478
+ return server;
4479
+ }
4480
+ var init_server = __esm({
4481
+ "src/server.ts"() {
4482
+ "use strict";
4483
+ init_version();
4484
+ init_tools();
4485
+ }
4486
+ });
4487
+
4488
+ // src/adapters/detect.ts
4489
+ function detectPlatform() {
4490
+ if (process.env.CLAUDE_PROJECT_DIR) {
4491
+ return {
4492
+ platform: "claude-code",
4493
+ confidence: "high",
4494
+ reason: `CLAUDE_PROJECT_DIR is set (${process.env.CLAUDE_PROJECT_DIR})`
4495
+ };
4496
+ }
4497
+ if (process.env.CURSOR_TRACE_ID) {
4498
+ return {
4499
+ platform: "cursor",
4500
+ confidence: "high",
4501
+ reason: `CURSOR_TRACE_ID is set (${process.env.CURSOR_TRACE_ID})`
4502
+ };
4503
+ }
4504
+ if (process.env.GEMINI_CLI) {
4505
+ return {
4506
+ platform: "gemini-cli",
4507
+ confidence: "high",
4508
+ reason: `GEMINI_CLI is set (${process.env.GEMINI_CLI})`
4509
+ };
4510
+ }
4511
+ if (process.env.VSCODE_PID) {
4512
+ return {
4513
+ platform: "vscode-copilot",
4514
+ confidence: "medium",
4515
+ reason: `VSCODE_PID is set (${process.env.VSCODE_PID})`
4516
+ };
4517
+ }
4518
+ if (process.env.TERM_PROGRAM === "vscode") {
4519
+ return {
4520
+ platform: "vscode-copilot",
4521
+ confidence: "medium",
4522
+ reason: `TERM_PROGRAM is set to vscode`
4523
+ };
4524
+ }
4525
+ if (process.env.OPENCODE_CONFIG) {
4526
+ return {
4527
+ platform: "opencode",
4528
+ confidence: "high",
4529
+ reason: `OPENCODE_CONFIG is set (${process.env.OPENCODE_CONFIG})`
4530
+ };
4531
+ }
4532
+ return {
4533
+ platform: "generic",
4534
+ confidence: "low",
4535
+ reason: "No platform-specific environment variables detected"
4536
+ };
4537
+ }
4538
+ function getInstallConfig(platformId) {
4539
+ const config = installConfigs[platformId];
4540
+ return {
4541
+ configPath: config.configPath,
4542
+ configKey: config.configKey,
4543
+ serverEntry: {
4544
+ command: "npx",
4545
+ args: ["-y", "browsirai"]
4546
+ }
4547
+ };
4548
+ }
4549
+ var installConfigs;
4550
+ var init_detect = __esm({
4551
+ "src/adapters/detect.ts"() {
4552
+ "use strict";
4553
+ installConfigs = {
4554
+ "claude-code": {
4555
+ configPath: ".mcp.json",
4556
+ configKey: "mcpServers"
4557
+ },
4558
+ cursor: {
4559
+ configPath: ".cursor/mcp.json",
4560
+ configKey: "mcpServers"
4561
+ },
4562
+ "gemini-cli": {
4563
+ configPath: "~/.gemini/settings.json",
4564
+ configKey: "mcpServers"
4565
+ },
4566
+ windsurf: {
4567
+ configPath: "~/.codeium/windsurf/mcp_config.json",
4568
+ configKey: "mcpServers"
4569
+ },
4570
+ cline: {
4571
+ configPath: "~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",
4572
+ configKey: "mcpServers"
4573
+ },
4574
+ "vscode-copilot": {
4575
+ configPath: ".vscode/mcp.json",
4576
+ configKey: "servers"
4577
+ },
4578
+ opencode: {
4579
+ configPath: "opencode.json",
4580
+ configKey: "mcpServers"
4581
+ },
4582
+ zed: {
4583
+ configPath: "~/.config/zed/settings.json",
4584
+ configKey: "context_servers"
4585
+ },
4586
+ continue: {
4587
+ configPath: "~/.continue/config.yaml",
4588
+ configKey: "mcpServers"
4589
+ },
4590
+ generic: {
4591
+ configPath: "mcp.json",
4592
+ configKey: "mcpServers"
4593
+ }
4594
+ };
4595
+ }
4596
+ });
4597
+
4598
+ // src/doctor.ts
4599
+ var doctor_exports = {};
4600
+ __export(doctor_exports, {
4601
+ runDoctor: () => runDoctor
4602
+ });
4603
+ import { execSync as execSync3 } from "child_process";
4604
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
4605
+ function findChromePath() {
4606
+ const whichCommands = process.platform === "win32" ? ["where chrome", "where chromium", "where msedge"] : ["which google-chrome", "which chromium", "which chromium-browser", "which chrome"];
4607
+ for (const cmd of whichCommands) {
4608
+ try {
4609
+ const result = execSync3(cmd, { stdio: "pipe" });
4610
+ const path = result.toString().trim();
4611
+ if (path) return path;
4612
+ } catch {
4613
+ }
4614
+ }
4615
+ if (process.platform === "darwin") {
4616
+ const macPaths = [
4617
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
4618
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
4619
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
4620
+ ];
4621
+ for (const p of macPaths) {
4622
+ try {
4623
+ execSync3(`test -x '${p}'`, { stdio: "pipe" });
4624
+ return p;
4625
+ } catch {
4626
+ }
4627
+ }
4628
+ }
4629
+ return null;
4630
+ }
4631
+ function checkNodeVersion() {
4632
+ const versionStr = process.version.replace(/^v/, "");
4633
+ const major = parseInt(versionStr.split(".")[0], 10);
4634
+ const ok = major >= 18;
4635
+ return {
4636
+ ok,
4637
+ label: "Node.js version",
4638
+ message: ok ? `v${versionStr} (>= 18 required)` : `v${versionStr} \u2014 Node.js >= 18 is required`
4639
+ };
4640
+ }
4641
+ function checkPlatformConfig() {
4642
+ const detection = detectPlatform();
4643
+ const config = getInstallConfig(detection.platform);
4644
+ const configPath = config.configPath;
4645
+ if (!existsSync4(configPath)) {
4646
+ return {
4647
+ ok: false,
4648
+ label: "Platform config",
4649
+ message: `Config file not found: ${configPath} (platform: ${detection.platform})`
4650
+ };
4651
+ }
4652
+ try {
4653
+ const content = readFileSync4(configPath, "utf-8");
4654
+ const parsed = JSON.parse(content);
4655
+ const servers = parsed[config.configKey];
4656
+ if (servers && typeof servers === "object" && "browsirai" in servers) {
4657
+ return {
4658
+ ok: true,
4659
+ label: "Platform config",
4660
+ message: `browsirai found in ${configPath} (platform: ${detection.platform})`
4661
+ };
4662
+ }
4663
+ return {
4664
+ ok: false,
4665
+ label: "Platform config",
4666
+ message: `browsirai not found in ${configPath} under "${config.configKey}"`
4667
+ };
4668
+ } catch {
4669
+ return {
4670
+ ok: false,
4671
+ label: "Platform config",
4672
+ message: `Failed to parse config file: ${configPath}`
4673
+ };
4674
+ }
4675
+ }
4676
+ async function runDoctor() {
4677
+ const checks = [];
4678
+ const method = getInstallMethod();
4679
+ checks.push({
4680
+ ok: true,
4681
+ label: "browsirai version",
4682
+ message: `v${VERSION} (${method})`
4683
+ });
4684
+ checks.push({
4685
+ ok: true,
4686
+ label: "Install path",
4687
+ message: getInstallPath()
4688
+ });
4689
+ let status = getUpgradeStatus();
4690
+ if (!status) {
4691
+ status = await checkForUpgrade();
4692
+ }
4693
+ if (status) {
4694
+ const upToDate = status.current === status.latest;
4695
+ checks.push({
4696
+ ok: upToDate,
4697
+ label: "Latest version",
4698
+ message: upToDate ? `v${status.latest} (up to date)` : `v${status.latest} available (current: v${status.current}, restart to apply)`
4699
+ });
4700
+ }
4701
+ const chromePath = findChromePath();
4702
+ checks.push({
4703
+ ok: chromePath !== null,
4704
+ label: "Chrome/Chromium installed",
4705
+ message: chromePath ? `Found at ${chromePath}` : "Chrome or Chromium not found in PATH"
4706
+ });
4707
+ checks.push(checkNodeVersion());
4708
+ const { connectChrome: connectChrome2 } = await Promise.resolve().then(() => (init_chrome_launcher(), chrome_launcher_exports));
4709
+ const connection = await connectChrome2({ autoLaunch: !!chromePath });
4710
+ if (connection.success) {
4711
+ checks.push({
4712
+ ok: true,
4713
+ label: "CDP connection",
4714
+ message: connection.wsEndpoint ? `Connected (port ${connection.port}, --remote-debugging-port)` : `CDP reachable on port ${connection.port}`
4715
+ });
4716
+ } else {
4717
+ checks.push({
4718
+ ok: false,
4719
+ label: "CDP connection",
4720
+ message: connection.error ?? "CDP not available"
4721
+ });
4722
+ }
4723
+ checks.push(checkPlatformConfig());
4724
+ console.log("browsirai doctor: running diagnostics...\n");
4725
+ for (const check of checks) {
4726
+ const icon = check.ok ? "PASS" : "FAIL";
4727
+ console.log(` [${icon}] ${check.label}`);
4728
+ if (check.message) {
4729
+ console.log(` ${check.message}`);
4730
+ }
4731
+ }
4732
+ const allPassed = checks.every((c) => c.ok);
4733
+ console.log(allPassed ? "\nAll checks passed!" : "\nSome checks failed.");
4734
+ return checks;
4735
+ }
4736
+ var init_doctor = __esm({
4737
+ "src/doctor.ts"() {
4738
+ "use strict";
4739
+ init_detect();
4740
+ init_version();
4741
+ init_upgrade();
4742
+ }
4743
+ });
4744
+
4745
+ // src/install.ts
4746
+ var install_exports = {};
4747
+ __export(install_exports, {
4748
+ runInstall: () => runInstall
4749
+ });
4750
+ import { intro, select, confirm, spinner, outro, isCancel, cancel, log } from "@clack/prompts";
4751
+ import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
4752
+ import { resolve, dirname as dirname2 } from "path";
4753
+ import { homedir as homedir4 } from "os";
4754
+ function resolveConfigPath(configPath, scope) {
4755
+ if (configPath.startsWith("~")) {
4756
+ return resolve(homedir4(), configPath.slice(2));
4757
+ }
4758
+ if (scope === "global") {
4759
+ return resolve(homedir4(), configPath);
4760
+ }
4761
+ return resolve(process.cwd(), configPath);
4762
+ }
4763
+ async function runInstall() {
4764
+ intro("browsirai installer");
4765
+ const detected = detectPlatform();
4766
+ const installedPlatforms = /* @__PURE__ */ new Set();
4767
+ for (const opt of platformOptions) {
4768
+ const config2 = getInstallConfig(opt.value);
4769
+ const paths = [
4770
+ resolve(process.cwd(), config2.configPath),
4771
+ config2.configPath.startsWith("~") ? resolve(homedir4(), config2.configPath.slice(2)) : resolve(homedir4(), config2.configPath)
4772
+ ];
4773
+ for (const filePath2 of paths) {
4774
+ if (existsSync5(filePath2)) {
4775
+ try {
4776
+ const existing = JSON.parse(readFileSync5(filePath2, "utf-8"));
4777
+ const section = existing[config2.configKey];
4778
+ if (section?.browsirai) installedPlatforms.add(opt.value);
4779
+ } catch {
4780
+ }
4781
+ }
4782
+ }
4783
+ }
4784
+ const platform = await select({
4785
+ message: "Select your AI coding platform:",
4786
+ options: platformOptions.map((opt) => ({
4787
+ value: opt.value,
4788
+ label: installedPlatforms.has(opt.value) ? `${opt.label} (installed)` : opt.label
4789
+ })),
4790
+ initialValue: detected.platform
4791
+ });
4792
+ if (isCancel(platform)) {
4793
+ cancel("Installation cancelled.");
4794
+ return;
4795
+ }
4796
+ const scope = await select({
4797
+ message: "Install scope:",
4798
+ options: [
4799
+ { value: "project", label: "Project (current directory)" },
4800
+ { value: "global", label: "Global (user home)" }
4801
+ ]
4802
+ });
4803
+ if (isCancel(scope)) {
4804
+ cancel("Installation cancelled.");
4805
+ return;
4806
+ }
4807
+ const selectedPlatform = platform;
4808
+ const selectedScope = scope;
4809
+ const config = getInstallConfig(selectedPlatform);
4810
+ const serverConfig = {
4811
+ [config.configKey]: {
4812
+ browsirai: config.serverEntry
4813
+ }
4814
+ };
4815
+ const filePath = resolveConfigPath(config.configPath, selectedScope);
4816
+ if (existsSync5(filePath)) {
4817
+ const existingRaw = readFileSync5(filePath, "utf-8");
4818
+ const existingConfig = JSON.parse(existingRaw);
4819
+ const shouldMerge = await confirm({
4820
+ message: `Config file already exists at ${filePath}. Merge browsirai into it?`
4821
+ });
4822
+ if (isCancel(shouldMerge) || !shouldMerge) {
4823
+ cancel("Installation cancelled.");
4824
+ return;
4825
+ }
4826
+ const existingSection = existingConfig[config.configKey] ?? {};
4827
+ existingConfig[config.configKey] = {
4828
+ ...existingSection,
4829
+ browsirai: config.serverEntry
4830
+ };
4831
+ mkdirSync4(dirname2(filePath), { recursive: true });
4832
+ writeFileSync3(filePath, JSON.stringify(existingConfig, null, 2));
4833
+ } else {
4834
+ mkdirSync4(dirname2(filePath), { recursive: true });
4835
+ writeFileSync3(filePath, JSON.stringify(serverConfig, null, 2));
4836
+ }
4837
+ log.success(`Config written to ${filePath}`);
4838
+ const s = spinner();
4839
+ s.start("Connecting to Chrome DevTools Protocol...");
4840
+ const connection = await connectChrome({ autoLaunch: true });
4841
+ if (connection.success) {
4842
+ s.stop(connection.wsEndpoint ? `Connected to Chrome (port ${connection.port})` : `CDP reachable on port ${connection.port}`);
4843
+ } else {
4844
+ s.stop(connection.error ?? "Could not connect to Chrome");
4845
+ log.warn("Run `browsirai doctor` to diagnose.");
4846
+ }
4847
+ outro("browsirai is ready! Your AI agent can now control your browser.");
4848
+ }
4849
+ var platformOptions;
4850
+ var init_install = __esm({
4851
+ "src/install.ts"() {
4852
+ "use strict";
4853
+ init_detect();
4854
+ init_chrome_launcher();
4855
+ platformOptions = [
4856
+ { value: "claude-code", label: "Claude Code" },
4857
+ { value: "cursor", label: "Cursor" },
4858
+ { value: "gemini-cli", label: "Gemini CLI" },
4859
+ { value: "vscode-copilot", label: "VS Code Copilot" },
4860
+ { value: "opencode", label: "OpenCode" },
4861
+ { value: "zed", label: "Zed" },
4862
+ { value: "windsurf", label: "Windsurf" },
4863
+ { value: "cline", label: "Cline" },
4864
+ { value: "continue", label: "Continue" }
4865
+ ];
4866
+ }
4867
+ });
4868
+
4869
+ // src/cli.ts
4870
+ init_version();
4871
+ process.on("unhandledRejection", () => {
4872
+ });
4873
+ process.on("uncaughtException", () => {
4874
+ });
4875
+ async function runCli(args) {
4876
+ const command = args[0];
4877
+ switch (command) {
4878
+ case void 0: {
4879
+ const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
4880
+ const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
4881
+ const server = await createServer2();
4882
+ const transport = new StdioServerTransport();
4883
+ await server.connect(transport);
4884
+ Promise.resolve().then(() => (init_upgrade(), upgrade_exports)).then((m) => m.checkForUpgrade()).catch(() => {
4885
+ });
4886
+ break;
4887
+ }
4888
+ case "doctor": {
4889
+ const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
4890
+ await runDoctor2();
4891
+ break;
4892
+ }
4893
+ case "install": {
4894
+ const { runInstall: runInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
4895
+ await runInstall2();
4896
+ break;
4897
+ }
4898
+ case "--version":
4899
+ case "-v": {
4900
+ console.log(VERSION);
4901
+ break;
4902
+ }
4903
+ default: {
4904
+ console.error(`Unknown command: ${command}`);
4905
+ console.log(
4906
+ [
4907
+ `browsirai v${VERSION}`,
4908
+ "",
4909
+ "Usage: browsirai [command]",
4910
+ "",
4911
+ "Commands:",
4912
+ " (none) Start the MCP server (default)",
4913
+ " doctor Run diagnostics",
4914
+ " install Install browser integration",
4915
+ " --version Print version",
4916
+ ""
4917
+ ].join("\n")
4918
+ );
4919
+ break;
4920
+ }
4921
+ }
4922
+ }
4923
+
4924
+ // src/bin.ts
4925
+ runCli(process.argv.slice(2)).catch((err) => {
4926
+ console.error(err);
4927
+ process.exit(1);
4928
+ });
4929
+ //# sourceMappingURL=bin.js.map