@hasna/browser 0.0.4 → 0.0.5

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/mcp/index.js CHANGED
@@ -270,6 +270,7 @@ var init_console_log = __esm(() => {
270
270
  var exports_snapshot = {};
271
271
  __export(exports_snapshot, {
272
272
  takeSnapshot: () => takeSnapshot,
273
+ takeBunSnapshot: () => takeBunSnapshot,
273
274
  setLastSnapshot: () => setLastSnapshot,
274
275
  hasRefs: () => hasRefs,
275
276
  getSessionRefs: () => getSessionRefs,
@@ -290,6 +291,10 @@ function clearLastSnapshot(sessionId) {
290
291
  lastSnapshots.delete(sessionId);
291
292
  }
292
293
  async function takeSnapshot(page, sessionId) {
294
+ const isBunView = typeof page.getNativeView === "function" || typeof page.bunView !== "undefined";
295
+ if (isBunView) {
296
+ return takeBunSnapshot(page, sessionId);
297
+ }
293
298
  let ariaTree;
294
299
  try {
295
300
  ariaTree = await page.locator("body").ariaSnapshot();
@@ -444,6 +449,71 @@ function diffSnapshots(before, after) {
444
449
  const title_changed = before.tree !== after.tree && (added.length > 0 || removed.length > 0 || modified.length > 0);
445
450
  return { added, removed, modified, url_changed, title_changed };
446
451
  }
452
+ async function takeBunSnapshot(page, sessionId) {
453
+ const refs = {};
454
+ const refMap = new Map;
455
+ let refCounter = 0;
456
+ const lines = [];
457
+ try {
458
+ const elements = await page.evaluate(`
459
+ (() => {
460
+ const SELECTOR = 'a[href], button, input:not([type=hidden]), select, textarea, [role=button], [role=link], [role=checkbox], [role=combobox], [role=menuitem], [role=tab], [role=option]';
461
+ const els = Array.from(document.querySelectorAll(SELECTOR));
462
+ return els.slice(0, 100).map(el => {
463
+ const tag = el.tagName.toLowerCase();
464
+ const inputType = el.getAttribute('type') ?? '';
465
+ let role = el.getAttribute('role') || (['a'].includes(tag) ? 'link' : ['button'].includes(tag) ? 'button' : ['input'].includes(tag) ? (inputType === 'checkbox' ? 'checkbox' : inputType === 'radio' ? 'radio' : 'textbox') : ['select'].includes(tag) ? 'combobox' : ['textarea'].includes(tag) ? 'textbox' : tag);
466
+ const name = (el.getAttribute('aria-label') || el.textContent?.trim() || el.getAttribute('placeholder') || el.getAttribute('title') || el.getAttribute('value') || el.id || '').slice(0, 80);
467
+ const enabled = !el.disabled && !el.getAttribute('disabled');
468
+ const style = window.getComputedStyle(el);
469
+ const visible = style.display !== 'none' && style.visibility !== 'hidden' && el.offsetWidth > 0;
470
+ const checked = el.type === 'checkbox' || el.type === 'radio' ? el.checked : undefined;
471
+ const value = ['input', 'select', 'textarea'].includes(tag) && el.type !== 'checkbox' && el.type !== 'radio' ? el.value : undefined;
472
+ const selector = el.id ? '#' + el.id : (el.getAttribute('aria-label') ? '[aria-label="' + el.getAttribute('aria-label') + '"]' : tag);
473
+ return { role, name, enabled, visible, checked, value, selector };
474
+ }).filter(e => e.visible && e.name);
475
+ })()
476
+ `);
477
+ const pageTitle = await page.evaluate("document.title");
478
+ const pageUrl = typeof page.url === "function" ? page.url() : "";
479
+ lines.push(`# ${pageTitle || "Page"} (${pageUrl})`);
480
+ for (const el of elements) {
481
+ if (!el.name)
482
+ continue;
483
+ const ref = `@e${refCounter}`;
484
+ refCounter++;
485
+ refs[ref] = {
486
+ role: el.role,
487
+ name: el.name,
488
+ visible: el.visible,
489
+ enabled: el.enabled,
490
+ value: el.value,
491
+ checked: el.checked
492
+ };
493
+ refMap.set(ref, { role: el.role, name: el.name, locatorSelector: el.selector });
494
+ const extras = [];
495
+ if (el.checked !== undefined)
496
+ extras.push(`checked=${el.checked}`);
497
+ if (!el.enabled)
498
+ extras.push("disabled");
499
+ if (el.value && el.value !== el.name)
500
+ extras.push(`value="${el.value.slice(0, 30)}"`);
501
+ const extrasStr = extras.length ? ` (${extras.join(", ")})` : "";
502
+ lines.push(`${el.role} "${el.name}" [${ref}]${extrasStr}`);
503
+ }
504
+ } catch (err) {
505
+ lines.push(`# (snapshot error: ${err instanceof Error ? err.message : String(err)})`);
506
+ }
507
+ if (sessionId) {
508
+ sessionRefMaps.set(sessionId, refMap);
509
+ }
510
+ return {
511
+ tree: lines.join(`
512
+ `),
513
+ refs,
514
+ interactive_count: refCounter
515
+ };
516
+ }
447
517
  var lastSnapshots, sessionRefMaps, INTERACTIVE_ROLES;
448
518
  var init_snapshot = __esm(() => {
449
519
  lastSnapshots = new Map;
@@ -10904,7 +10974,7 @@ var coerce = {
10904
10974
  var NEVER = INVALID;
10905
10975
  // src/mcp/index.ts
10906
10976
  import { readFileSync as readFileSync3 } from "fs";
10907
- import { join as join7 } from "path";
10977
+ import { join as join8 } from "path";
10908
10978
 
10909
10979
  // src/types/index.ts
10910
10980
  class BrowserError extends Error {
@@ -11150,14 +11220,413 @@ async function connectLightpanda(port) {
11150
11220
  }
11151
11221
  }
11152
11222
 
11223
+ // src/engines/bun-webview.ts
11224
+ import { join as join2 } from "path";
11225
+ import { mkdirSync as mkdirSync2 } from "fs";
11226
+ import { homedir as homedir2 } from "os";
11227
+ function isBunWebViewAvailable() {
11228
+ return typeof globalThis.Bun !== "undefined" && typeof globalThis.Bun.WebView !== "undefined";
11229
+ }
11230
+ function getProfileDir(profileName) {
11231
+ const base = process.env["BROWSER_DATA_DIR"] ?? join2(homedir2(), ".browser");
11232
+ const dir = join2(base, "profiles", profileName);
11233
+ mkdirSync2(dir, { recursive: true });
11234
+ return dir;
11235
+ }
11236
+
11237
+ class BunWebViewSession {
11238
+ view;
11239
+ _sessionId;
11240
+ _eventListeners = new Map;
11241
+ constructor(opts = {}) {
11242
+ if (!isBunWebViewAvailable()) {
11243
+ throw new Error("Bun.WebView is not available. Install Bun canary: bun upgrade --canary");
11244
+ }
11245
+ const BunWebView = globalThis.Bun.WebView;
11246
+ const constructorOpts = {
11247
+ width: opts.width ?? 1280,
11248
+ height: opts.height ?? 720
11249
+ };
11250
+ if (opts.profile) {
11251
+ constructorOpts.dataStore = { directory: getProfileDir(opts.profile) };
11252
+ } else {
11253
+ constructorOpts.dataStore = "ephemeral";
11254
+ }
11255
+ if (opts.onConsole) {
11256
+ constructorOpts.console = opts.onConsole;
11257
+ }
11258
+ this.view = new BunWebView(constructorOpts);
11259
+ this.view.onNavigated = (url) => {
11260
+ this._emit("navigated", url);
11261
+ };
11262
+ this.view.onNavigationFailed = (error) => {
11263
+ this._emit("navigationfailed", error);
11264
+ };
11265
+ }
11266
+ async goto(url, opts) {
11267
+ await this.view.navigate(url);
11268
+ await new Promise((r) => setTimeout(r, 200));
11269
+ }
11270
+ async goBack() {
11271
+ await this.view.goBack();
11272
+ }
11273
+ async goForward() {
11274
+ await this.view.goForward();
11275
+ }
11276
+ async reload() {
11277
+ await this.view.reload();
11278
+ }
11279
+ async evaluate(fnOrExpr, ...args) {
11280
+ let expr;
11281
+ if (typeof fnOrExpr === "function") {
11282
+ const serializedArgs = args.map((a) => JSON.stringify(a)).join(", ");
11283
+ expr = `(${fnOrExpr.toString()})(${serializedArgs})`;
11284
+ } else {
11285
+ expr = fnOrExpr;
11286
+ }
11287
+ return this.view.evaluate(expr);
11288
+ }
11289
+ async screenshot(opts) {
11290
+ const uint8 = await this.view.screenshot();
11291
+ return Buffer.from(uint8);
11292
+ }
11293
+ async click(selector, opts) {
11294
+ await this.view.click(selector, opts ? { button: opts.button } : undefined);
11295
+ }
11296
+ async type(selector, text, opts) {
11297
+ try {
11298
+ await this.view.click(selector);
11299
+ } catch {}
11300
+ await this.view.type(text);
11301
+ }
11302
+ async fill(selector, value) {
11303
+ await this.view.evaluate(`
11304
+ (() => {
11305
+ const el = document.querySelector(${JSON.stringify(selector)});
11306
+ if (el) { el.value = ''; el.dispatchEvent(new Event('input')); }
11307
+ })()
11308
+ `);
11309
+ await this.type(selector, value);
11310
+ }
11311
+ async press(key, opts) {
11312
+ await this.view.press(key, opts);
11313
+ }
11314
+ async scroll(direction, amount) {
11315
+ const dx = direction === "left" ? -amount : direction === "right" ? amount : 0;
11316
+ const dy = direction === "up" ? -amount : direction === "down" ? amount : 0;
11317
+ await this.view.scroll(dx, dy);
11318
+ }
11319
+ async scrollIntoView(selector) {
11320
+ await this.view.scrollTo(selector);
11321
+ }
11322
+ async hover(selector) {
11323
+ try {
11324
+ await this.view.scrollTo(selector);
11325
+ } catch {}
11326
+ }
11327
+ async resize(width, height) {
11328
+ await this.view.resize(width, height);
11329
+ }
11330
+ async $(selector) {
11331
+ const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
11332
+ if (!exists)
11333
+ return null;
11334
+ return {
11335
+ textContent: async () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`)
11336
+ };
11337
+ }
11338
+ async $$(selector) {
11339
+ const count = await this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)}).length`);
11340
+ return Array.from({ length: count }, (_, i) => ({
11341
+ textContent: async () => this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)})[${i}]?.textContent ?? null`)
11342
+ }));
11343
+ }
11344
+ async inputValue(selector) {
11345
+ return this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.value ?? ''`);
11346
+ }
11347
+ async isChecked(selector) {
11348
+ return this.view.evaluate(`!!(document.querySelector(${JSON.stringify(selector)})?.checked)`);
11349
+ }
11350
+ async isVisible(selector) {
11351
+ return this.view.evaluate(`
11352
+ (() => {
11353
+ const el = document.querySelector(${JSON.stringify(selector)});
11354
+ if (!el) return false;
11355
+ const style = window.getComputedStyle(el);
11356
+ return style.display !== 'none' && style.visibility !== 'hidden' && el.offsetWidth > 0;
11357
+ })()
11358
+ `);
11359
+ }
11360
+ async isEnabled(selector) {
11361
+ return this.view.evaluate(`!(document.querySelector(${JSON.stringify(selector)})?.disabled)`);
11362
+ }
11363
+ async selectOption(selector, value) {
11364
+ await this.view.evaluate(`
11365
+ (() => {
11366
+ const el = document.querySelector(${JSON.stringify(selector)});
11367
+ if (el) {
11368
+ el.value = ${JSON.stringify(value)};
11369
+ el.dispatchEvent(new Event('change'));
11370
+ }
11371
+ })()
11372
+ `);
11373
+ return [value];
11374
+ }
11375
+ async check(selector) {
11376
+ await this.view.evaluate(`
11377
+ (() => {
11378
+ const el = document.querySelector(${JSON.stringify(selector)});
11379
+ if (el && !el.checked) { el.checked = true; el.dispatchEvent(new Event('change')); }
11380
+ })()
11381
+ `);
11382
+ }
11383
+ async uncheck(selector) {
11384
+ await this.view.evaluate(`
11385
+ (() => {
11386
+ const el = document.querySelector(${JSON.stringify(selector)});
11387
+ if (el && el.checked) { el.checked = false; el.dispatchEvent(new Event('change')); }
11388
+ })()
11389
+ `);
11390
+ }
11391
+ async setInputFiles(selector, files) {
11392
+ throw new Error("File upload not supported in Bun.WebView engine. Use engine: 'playwright' instead.");
11393
+ }
11394
+ getByRole(role, opts) {
11395
+ const name = opts?.name?.toString() ?? "";
11396
+ const selector = name ? `[role="${role}"][aria-label*="${name}"], ${role}[aria-label*="${name}"]` : `[role="${role}"], ${role}`;
11397
+ return {
11398
+ click: (clickOpts) => this.click(selector, clickOpts),
11399
+ fill: (value) => this.fill(selector, value),
11400
+ check: () => this.check(selector),
11401
+ uncheck: () => this.uncheck(selector),
11402
+ isVisible: () => this.isVisible(selector),
11403
+ textContent: () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`),
11404
+ inputValue: () => this.inputValue(selector),
11405
+ first: () => ({
11406
+ click: (clickOpts) => this.click(selector, clickOpts),
11407
+ fill: (value) => this.fill(selector, value),
11408
+ textContent: () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`),
11409
+ isVisible: () => this.isVisible(selector),
11410
+ hover: () => this.hover(selector),
11411
+ boundingBox: async () => null,
11412
+ scrollIntoViewIfNeeded: () => this.scrollIntoView(selector),
11413
+ evaluate: (fn) => this.view.evaluate(`(${fn.toString()})(document.querySelector(${JSON.stringify(selector)}))`),
11414
+ waitFor: (opts2) => {
11415
+ return new Promise((resolve, reject) => {
11416
+ const timeout = opts2?.timeout ?? 1e4;
11417
+ const start = Date.now();
11418
+ const check = async () => {
11419
+ const visible = await this.isVisible(selector);
11420
+ if (visible)
11421
+ return resolve();
11422
+ if (Date.now() - start > timeout)
11423
+ return reject(new Error(`Timeout waiting for ${selector}`));
11424
+ setTimeout(check, 100);
11425
+ };
11426
+ check();
11427
+ });
11428
+ }
11429
+ }),
11430
+ count: async () => {
11431
+ const count = await this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)}).length`);
11432
+ return count;
11433
+ },
11434
+ nth: (n) => ({
11435
+ click: (clickOpts) => this.click(selector, clickOpts),
11436
+ textContent: () => this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)})[${n}]?.textContent ?? null`),
11437
+ isVisible: () => this.isVisible(selector)
11438
+ })
11439
+ };
11440
+ }
11441
+ getByText(text, opts) {
11442
+ const selector = opts?.exact ? `*:is(button, a, span, div, p, h1, h2, h3, h4, label)` : "*";
11443
+ return {
11444
+ first: () => ({
11445
+ click: async (clickOpts) => {
11446
+ await this.view.evaluate(`
11447
+ (() => {
11448
+ const text = ${JSON.stringify(text)};
11449
+ const all = document.querySelectorAll('*');
11450
+ for (const el of all) {
11451
+ if (el.children.length === 0 && el.textContent?.trim() === text) {
11452
+ el.click(); return;
11453
+ }
11454
+ }
11455
+ for (const el of all) {
11456
+ if (el.textContent?.includes(text)) { el.click(); return; }
11457
+ }
11458
+ })()
11459
+ `);
11460
+ },
11461
+ waitFor: (waitOpts) => {
11462
+ const timeout = waitOpts?.timeout ?? 1e4;
11463
+ return new Promise((resolve, reject) => {
11464
+ const start = Date.now();
11465
+ const check = async () => {
11466
+ const found = await this.view.evaluate(`document.body?.textContent?.includes(${JSON.stringify(text)})`);
11467
+ if (found)
11468
+ return resolve();
11469
+ if (Date.now() - start > timeout)
11470
+ return reject(new Error(`Timeout: text "${text}" not found`));
11471
+ setTimeout(check, 100);
11472
+ };
11473
+ check();
11474
+ });
11475
+ }
11476
+ })
11477
+ };
11478
+ }
11479
+ locator(selector) {
11480
+ return {
11481
+ click: (opts) => this.click(selector, opts),
11482
+ fill: (value) => this.fill(selector, value),
11483
+ scrollIntoViewIfNeeded: () => this.scrollIntoView(selector),
11484
+ first: () => this.getByRole("*").first(),
11485
+ evaluate: (fn) => this.view.evaluate(`(${fn.toString()})(document.querySelector(${JSON.stringify(selector)}))`),
11486
+ waitFor: (opts) => {
11487
+ const timeout = opts?.timeout ?? 1e4;
11488
+ return new Promise((resolve, reject) => {
11489
+ const start = Date.now();
11490
+ const check = async () => {
11491
+ const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
11492
+ if (exists)
11493
+ return resolve();
11494
+ if (Date.now() - start > timeout)
11495
+ return reject(new Error(`Timeout: ${selector}`));
11496
+ setTimeout(check, 100);
11497
+ };
11498
+ check();
11499
+ });
11500
+ }
11501
+ };
11502
+ }
11503
+ url() {
11504
+ return this.view.url;
11505
+ }
11506
+ async title() {
11507
+ return this.view.title || await this.evaluate("document.title");
11508
+ }
11509
+ viewportSize() {
11510
+ return { width: 1280, height: 720 };
11511
+ }
11512
+ async waitForLoadState(state, opts) {
11513
+ await new Promise((r) => setTimeout(r, 200));
11514
+ }
11515
+ async waitForURL(pattern, opts) {
11516
+ const timeout = opts?.timeout ?? 30000;
11517
+ const start = Date.now();
11518
+ while (Date.now() - start < timeout) {
11519
+ const url = this.view.url;
11520
+ const matches = pattern instanceof RegExp ? pattern.test(url) : url.includes(pattern);
11521
+ if (matches)
11522
+ return;
11523
+ await new Promise((r) => setTimeout(r, 100));
11524
+ }
11525
+ throw new Error(`Timeout waiting for URL to match ${pattern}`);
11526
+ }
11527
+ async waitForSelector(selector, opts) {
11528
+ const timeout = opts?.timeout ?? 1e4;
11529
+ const start = Date.now();
11530
+ while (Date.now() - start < timeout) {
11531
+ const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
11532
+ if (exists)
11533
+ return;
11534
+ await new Promise((r) => setTimeout(r, 100));
11535
+ }
11536
+ throw new Error(`Timeout waiting for ${selector}`);
11537
+ }
11538
+ async setContent(html) {
11539
+ await this.view.navigate(`data:text/html,${encodeURIComponent(html)}`);
11540
+ await new Promise((r) => setTimeout(r, 100));
11541
+ }
11542
+ async content() {
11543
+ return this.view.evaluate("document.documentElement.outerHTML");
11544
+ }
11545
+ async addInitScript(script) {
11546
+ const expr = typeof script === "function" ? `(${script.toString()})()` : script;
11547
+ await this.view.evaluate(expr);
11548
+ }
11549
+ keyboard = {
11550
+ press: (key) => this.view.press(key)
11551
+ };
11552
+ context() {
11553
+ return {
11554
+ close: async () => {
11555
+ await this.close();
11556
+ },
11557
+ newPage: async () => {
11558
+ throw new Error("Multi-tab not supported in Bun.WebView. Use engine: 'playwright'");
11559
+ },
11560
+ cookies: async () => [],
11561
+ addCookies: async (_) => {},
11562
+ clearCookies: async () => {},
11563
+ newCDPSession: async () => {
11564
+ throw new Error("CDP session via context not available in Bun.WebView. Use view.cdp() when shipped.");
11565
+ },
11566
+ route: async (_pattern, _handler) => {
11567
+ throw new Error("Network interception not supported in Bun.WebView. Use engine: 'cdp' or 'playwright'.");
11568
+ },
11569
+ unrouteAll: async () => {},
11570
+ pages: () => [],
11571
+ addInitScript: async (script) => {
11572
+ await this.addInitScript(script);
11573
+ }
11574
+ };
11575
+ }
11576
+ on(event, handler) {
11577
+ if (!this._eventListeners.has(event))
11578
+ this._eventListeners.set(event, []);
11579
+ this._eventListeners.get(event).push(handler);
11580
+ return this;
11581
+ }
11582
+ off(event, handler) {
11583
+ const listeners = this._eventListeners.get(event) ?? [];
11584
+ this._eventListeners.set(event, listeners.filter((l) => l !== handler));
11585
+ return this;
11586
+ }
11587
+ _emit(event, ...args) {
11588
+ for (const handler of this._eventListeners.get(event) ?? []) {
11589
+ try {
11590
+ handler(...args);
11591
+ } catch {}
11592
+ }
11593
+ }
11594
+ async pdf(_opts) {
11595
+ throw new Error("PDF generation not supported in Bun.WebView. Use engine: 'playwright'.");
11596
+ }
11597
+ coverage = {
11598
+ startJSCoverage: async () => {},
11599
+ stopJSCoverage: async () => [],
11600
+ startCSSCoverage: async () => {},
11601
+ stopCSSCoverage: async () => []
11602
+ };
11603
+ setSessionId(id) {
11604
+ this._sessionId = id;
11605
+ }
11606
+ getSessionId() {
11607
+ return this._sessionId;
11608
+ }
11609
+ getNativeView() {
11610
+ return this.view;
11611
+ }
11612
+ async close() {
11613
+ try {
11614
+ await this.view.close();
11615
+ } catch {}
11616
+ }
11617
+ [Symbol.asyncDispose]() {
11618
+ return this.close();
11619
+ }
11620
+ }
11621
+
11153
11622
  // src/engines/selector.ts
11154
11623
  var ENGINE_MAP = {
11155
- ["scrape" /* SCRAPE */]: "lightpanda",
11156
- ["extract_links" /* EXTRACT_LINKS */]: "lightpanda",
11157
- ["status_check" /* STATUS_CHECK */]: "lightpanda",
11624
+ ["scrape" /* SCRAPE */]: "bun",
11625
+ ["extract_links" /* EXTRACT_LINKS */]: "bun",
11626
+ ["status_check" /* STATUS_CHECK */]: "bun",
11627
+ ["screenshot" /* SCREENSHOT */]: "bun",
11628
+ ["spa_navigate" /* SPA_NAVIGATE */]: "bun",
11158
11629
  ["form_fill" /* FORM_FILL */]: "playwright",
11159
- ["spa_navigate" /* SPA_NAVIGATE */]: "playwright",
11160
- ["screenshot" /* SCREENSHOT */]: "playwright",
11161
11630
  ["auth_flow" /* AUTH_FLOW */]: "playwright",
11162
11631
  ["multi_tab" /* MULTI_TAB */]: "playwright",
11163
11632
  ["record_replay" /* RECORD_REPLAY */]: "playwright",
@@ -11171,6 +11640,14 @@ function selectEngine(useCase, explicit) {
11171
11640
  if (explicit && explicit !== "auto")
11172
11641
  return explicit;
11173
11642
  const preferred = ENGINE_MAP[useCase];
11643
+ if (preferred === "bun") {
11644
+ if (isBunWebViewAvailable())
11645
+ return "bun";
11646
+ if (useCase === "scrape" /* SCRAPE */ || useCase === "extract_links" /* EXTRACT_LINKS */ || useCase === "status_check" /* STATUS_CHECK */) {
11647
+ return isLightpandaAvailable() ? "lightpanda" : "playwright";
11648
+ }
11649
+ return "playwright";
11650
+ }
11174
11651
  if (preferred === "lightpanda" && !isLightpandaAvailable()) {
11175
11652
  return "playwright";
11176
11653
  }
@@ -11462,12 +11939,30 @@ async function handleDialog(sessionId, action, promptText) {
11462
11939
 
11463
11940
  // src/lib/session.ts
11464
11941
  var handles = new Map;
11942
+ function createBunProxy(view) {
11943
+ return view;
11944
+ }
11465
11945
  async function createSession2(opts = {}) {
11466
11946
  const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
11467
11947
  const resolvedEngine = engine === "auto" ? "playwright" : engine;
11468
- let browser;
11948
+ let browser = null;
11949
+ let bunView = null;
11469
11950
  let page;
11470
- if (resolvedEngine === "lightpanda") {
11951
+ if (resolvedEngine === "bun") {
11952
+ if (!isBunWebViewAvailable()) {
11953
+ console.warn("[browser] Bun.WebView requested but not available \u2014 falling back to playwright. Run: bun upgrade --canary");
11954
+ browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
11955
+ page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
11956
+ } else {
11957
+ bunView = new BunWebViewSession({
11958
+ width: opts.viewport?.width ?? 1280,
11959
+ height: opts.viewport?.height ?? 720,
11960
+ profile: opts.name ?? undefined
11961
+ });
11962
+ if (opts.stealth) {}
11963
+ page = createBunProxy(bunView);
11964
+ }
11965
+ } else if (resolvedEngine === "lightpanda") {
11471
11966
  browser = await connectLightpanda();
11472
11967
  const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
11473
11968
  page = await context.newPage();
@@ -11477,12 +11972,9 @@ async function createSession2(opts = {}) {
11477
11972
  viewport: opts.viewport,
11478
11973
  userAgent: opts.userAgent
11479
11974
  });
11480
- page = await getPage(browser, {
11481
- viewport: opts.viewport,
11482
- userAgent: opts.userAgent
11483
- });
11975
+ page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
11484
11976
  }
11485
- let sessionName = opts.name ?? (opts.startUrl ? (() => {
11977
+ const sessionName = opts.name ?? (opts.startUrl ? (() => {
11486
11978
  try {
11487
11979
  return new URL(opts.startUrl).hostname;
11488
11980
  } catch {
@@ -11490,35 +11982,57 @@ async function createSession2(opts = {}) {
11490
11982
  }
11491
11983
  })() : undefined);
11492
11984
  const session = createSession({
11493
- engine: resolvedEngine,
11985
+ engine: bunView ? "bun" : browser ? resolvedEngine : resolvedEngine,
11494
11986
  projectId: opts.projectId,
11495
11987
  agentId: opts.agentId,
11496
11988
  startUrl: opts.startUrl,
11497
11989
  name: sessionName
11498
11990
  });
11499
- if (opts.stealth) {
11991
+ if (opts.stealth && !bunView) {
11500
11992
  try {
11501
11993
  await applyStealthPatches(page);
11502
11994
  } catch {}
11503
11995
  }
11504
11996
  const cleanups = [];
11505
- if (opts.captureNetwork !== false) {
11506
- try {
11507
- cleanups.push(enableNetworkLogging(page, session.id));
11508
- } catch {}
11509
- }
11510
- if (opts.captureConsole !== false) {
11997
+ if (!bunView) {
11998
+ if (opts.captureNetwork !== false) {
11999
+ try {
12000
+ cleanups.push(enableNetworkLogging(page, session.id));
12001
+ } catch {}
12002
+ }
12003
+ if (opts.captureConsole !== false) {
12004
+ try {
12005
+ cleanups.push(enableConsoleCapture(page, session.id));
12006
+ } catch {}
12007
+ }
11511
12008
  try {
11512
- cleanups.push(enableConsoleCapture(page, session.id));
12009
+ cleanups.push(setupDialogHandler(page, session.id));
11513
12010
  } catch {}
12011
+ } else {
12012
+ if (opts.captureConsole !== false) {
12013
+ try {
12014
+ const { logConsoleMessage: logConsoleMessage2 } = await Promise.resolve().then(() => (init_console_log(), exports_console_log));
12015
+ await bunView.addInitScript(`
12016
+ (() => {
12017
+ const orig = { log: console.log, warn: console.warn, error: console.error, debug: console.debug, info: console.info };
12018
+ ['log','warn','error','debug','info'].forEach(level => {
12019
+ console[level] = (...args) => {
12020
+ orig[level](...args);
12021
+ };
12022
+ });
12023
+ })()
12024
+ `);
12025
+ } catch {}
12026
+ }
11514
12027
  }
11515
- try {
11516
- cleanups.push(setupDialogHandler(page, session.id));
11517
- } catch {}
11518
- handles.set(session.id, { browser, page, engine: resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
12028
+ handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
11519
12029
  if (opts.startUrl) {
11520
12030
  try {
11521
- await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
12031
+ if (bunView) {
12032
+ await bunView.goto(opts.startUrl);
12033
+ } else {
12034
+ await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
12035
+ }
11522
12036
  } catch {}
11523
12037
  }
11524
12038
  return { session, page };
@@ -11528,13 +12042,23 @@ function getSessionPage(sessionId) {
11528
12042
  if (!handle)
11529
12043
  throw new SessionNotFoundError(sessionId);
11530
12044
  try {
11531
- handle.page.url();
12045
+ if (handle.bunView) {
12046
+ handle.bunView.url();
12047
+ } else {
12048
+ handle.page.url();
12049
+ }
11532
12050
  } catch {
11533
12051
  handles.delete(sessionId);
11534
12052
  throw new SessionNotFoundError(sessionId);
11535
12053
  }
11536
12054
  return handle.page;
11537
12055
  }
12056
+ function getSessionBunView(sessionId) {
12057
+ return handles.get(sessionId)?.bunView ?? null;
12058
+ }
12059
+ function isBunSession(sessionId) {
12060
+ return handles.get(sessionId)?.engine === "bun";
12061
+ }
11538
12062
  function setSessionPage(sessionId, page) {
11539
12063
  const handle = handles.get(sessionId);
11540
12064
  if (!handle)
@@ -11549,12 +12073,19 @@ async function closeSession2(sessionId) {
11549
12073
  cleanup();
11550
12074
  } catch {}
11551
12075
  }
11552
- try {
11553
- await handle.page.context().close();
11554
- } catch {}
11555
- try {
11556
- await closeBrowser(handle.browser);
11557
- } catch {}
12076
+ if (handle.bunView) {
12077
+ try {
12078
+ await handle.bunView.close();
12079
+ } catch {}
12080
+ } else {
12081
+ try {
12082
+ await handle.page.context().close();
12083
+ } catch {}
12084
+ try {
12085
+ if (handle.browser)
12086
+ await closeBrowser(handle.browser);
12087
+ } catch {}
12088
+ }
11558
12089
  handles.delete(sessionId);
11559
12090
  }
11560
12091
  return closeSession(sessionId);
@@ -11890,9 +12421,19 @@ async function getLinks(page, baseUrl) {
11890
12421
  }, baseUrl ?? page.url());
11891
12422
  }
11892
12423
  async function getTitle(page) {
12424
+ if (typeof page.getNativeView === "function") {
12425
+ const nativeView = page.getNativeView();
12426
+ const t = nativeView?.title;
12427
+ return typeof t === "string" && t ? t : "";
12428
+ }
11893
12429
  return page.title();
11894
12430
  }
11895
12431
  async function getUrl(page) {
12432
+ if (typeof page.getNativeView === "function") {
12433
+ const nativeView = page.getNativeView();
12434
+ const u = nativeView?.url;
12435
+ return typeof u === "string" ? u : "";
12436
+ }
11896
12437
  return page.url();
11897
12438
  }
11898
12439
  async function findElements(page, selector) {
@@ -11983,9 +12524,9 @@ async function getPageInfo(page) {
11983
12524
 
11984
12525
  // src/lib/screenshot.ts
11985
12526
  var import_sharp = __toESM(require_lib(), 1);
11986
- import { join as join2 } from "path";
11987
- import { mkdirSync as mkdirSync2 } from "fs";
11988
- import { homedir as homedir2 } from "os";
12527
+ import { join as join3 } from "path";
12528
+ import { mkdirSync as mkdirSync3 } from "fs";
12529
+ import { homedir as homedir3 } from "os";
11989
12530
 
11990
12531
  // src/db/gallery.ts
11991
12532
  init_schema();
@@ -12131,13 +12672,13 @@ function getGalleryStats(projectId) {
12131
12672
 
12132
12673
  // src/lib/screenshot.ts
12133
12674
  function getDataDir2() {
12134
- return process.env["BROWSER_DATA_DIR"] ?? join2(homedir2(), ".browser");
12675
+ return process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser");
12135
12676
  }
12136
12677
  function getScreenshotDir(projectId) {
12137
- const base = join2(getDataDir2(), "screenshots");
12678
+ const base = join3(getDataDir2(), "screenshots");
12138
12679
  const date = new Date().toISOString().split("T")[0];
12139
- const dir = projectId ? join2(base, projectId, date) : join2(base, date);
12140
- mkdirSync2(dir, { recursive: true });
12680
+ const dir = projectId ? join3(base, projectId, date) : join3(base, date);
12681
+ mkdirSync3(dir, { recursive: true });
12141
12682
  return dir;
12142
12683
  }
12143
12684
  async function compressBuffer(raw, format, quality, maxWidth) {
@@ -12152,7 +12693,7 @@ async function compressBuffer(raw, format, quality, maxWidth) {
12152
12693
  }
12153
12694
  }
12154
12695
  async function generateThumbnail(raw, dir, stem) {
12155
- const thumbPath = join2(dir, `${stem}.thumb.webp`);
12696
+ const thumbPath = join3(dir, `${stem}.thumb.webp`);
12156
12697
  const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
12157
12698
  await Bun.write(thumbPath, thumbBuffer);
12158
12699
  return { path: thumbPath, base64: thumbBuffer.toString("base64") };
@@ -12171,11 +12712,20 @@ async function takeScreenshot(page, opts) {
12171
12712
  type: "png"
12172
12713
  };
12173
12714
  let rawBuffer;
12715
+ const isBunView = typeof page.getNativeView === "function";
12174
12716
  if (opts?.selector) {
12175
- const el = await page.$(opts.selector);
12176
- if (!el)
12177
- throw new BrowserError(`Element not found: ${opts.selector}`, "ELEMENT_NOT_FOUND");
12178
- rawBuffer = await el.screenshot(rawOpts);
12717
+ if (isBunView) {
12718
+ const uint8 = await page.screenshot();
12719
+ rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
12720
+ } else {
12721
+ const el = await page.$(opts.selector);
12722
+ if (!el)
12723
+ throw new BrowserError(`Element not found: ${opts.selector}`, "ELEMENT_NOT_FOUND");
12724
+ rawBuffer = await el.screenshot(rawOpts);
12725
+ }
12726
+ } else if (isBunView) {
12727
+ const uint8 = await page.screenshot();
12728
+ rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
12179
12729
  } else {
12180
12730
  rawBuffer = await page.screenshot(rawOpts);
12181
12731
  }
@@ -12200,7 +12750,7 @@ async function takeScreenshot(page, opts) {
12200
12750
  const compressedSizeBytes = finalBuffer.length;
12201
12751
  const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
12202
12752
  const ext = format;
12203
- const screenshotPath = opts?.path ?? join2(dir, `${stem}.${ext}`);
12753
+ const screenshotPath = opts?.path ?? join3(dir, `${stem}.${ext}`);
12204
12754
  await Bun.write(screenshotPath, finalBuffer);
12205
12755
  let thumbnailPath;
12206
12756
  let thumbnailBase64;
@@ -12260,12 +12810,12 @@ async function takeScreenshot(page, opts) {
12260
12810
  }
12261
12811
  async function generatePDF(page, opts) {
12262
12812
  try {
12263
- const base = join2(getDataDir2(), "pdfs");
12813
+ const base = join3(getDataDir2(), "pdfs");
12264
12814
  const date = new Date().toISOString().split("T")[0];
12265
- const dir = opts?.projectId ? join2(base, opts.projectId, date) : join2(base, date);
12266
- mkdirSync2(dir, { recursive: true });
12815
+ const dir = opts?.projectId ? join3(base, opts.projectId, date) : join3(base, date);
12816
+ mkdirSync3(dir, { recursive: true });
12267
12817
  const timestamp = Date.now();
12268
- const pdfPath = opts?.path ?? join2(dir, `${timestamp}.pdf`);
12818
+ const pdfPath = opts?.path ?? join3(dir, `${timestamp}.pdf`);
12269
12819
  const buffer = await page.pdf({
12270
12820
  path: pdfPath,
12271
12821
  format: opts?.format ?? "A4",
@@ -12788,16 +13338,16 @@ init_console_log();
12788
13338
 
12789
13339
  // src/lib/downloads.ts
12790
13340
  import { randomUUID as randomUUID9 } from "crypto";
12791
- import { join as join3, basename, extname } from "path";
12792
- import { mkdirSync as mkdirSync3, existsSync, readdirSync, statSync, unlinkSync, copyFileSync, writeFileSync, readFileSync } from "fs";
12793
- import { homedir as homedir3 } from "os";
13341
+ import { join as join4, basename, extname } from "path";
13342
+ import { mkdirSync as mkdirSync4, existsSync, readdirSync, statSync, unlinkSync, copyFileSync, writeFileSync, readFileSync } from "fs";
13343
+ import { homedir as homedir4 } from "os";
12794
13344
  function getDataDir3() {
12795
- return process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser");
13345
+ return process.env["BROWSER_DATA_DIR"] ?? join4(homedir4(), ".browser");
12796
13346
  }
12797
13347
  function getDownloadsDir(sessionId) {
12798
- const base = join3(getDataDir3(), "downloads");
12799
- const dir = sessionId ? join3(base, sessionId) : base;
12800
- mkdirSync3(dir, { recursive: true });
13348
+ const base = join4(getDataDir3(), "downloads");
13349
+ const dir = sessionId ? join4(base, sessionId) : base;
13350
+ mkdirSync4(dir, { recursive: true });
12801
13351
  return dir;
12802
13352
  }
12803
13353
  function metaPath(filePath) {
@@ -12809,7 +13359,7 @@ function saveToDownloads(buffer, filename, opts) {
12809
13359
  const ext = extname(filename) || "";
12810
13360
  const stem = basename(filename, ext);
12811
13361
  const uniqueName = `${stem}-${id.slice(0, 8)}${ext}`;
12812
- const filePath = join3(dir, uniqueName);
13362
+ const filePath = join4(dir, uniqueName);
12813
13363
  writeFileSync(filePath, buffer);
12814
13364
  const meta = {
12815
13365
  id,
@@ -12844,7 +13394,7 @@ function listDownloads(sessionId) {
12844
13394
  for (const entry of entries) {
12845
13395
  if (entry.endsWith(".meta.json"))
12846
13396
  continue;
12847
- const full = join3(d, entry);
13397
+ const full = join4(d, entry);
12848
13398
  const stat = statSync(full);
12849
13399
  if (stat.isDirectory()) {
12850
13400
  scanDir(full);
@@ -12929,9 +13479,9 @@ function detectType(filename) {
12929
13479
 
12930
13480
  // src/lib/gallery-diff.ts
12931
13481
  var import_sharp2 = __toESM(require_lib(), 1);
12932
- import { join as join4 } from "path";
12933
- import { mkdirSync as mkdirSync4 } from "fs";
12934
- import { homedir as homedir4 } from "os";
13482
+ import { join as join5 } from "path";
13483
+ import { mkdirSync as mkdirSync5 } from "fs";
13484
+ import { homedir as homedir5 } from "os";
12935
13485
  async function diffImages(path1, path2) {
12936
13486
  const img1 = import_sharp2.default(path1);
12937
13487
  const img2 = import_sharp2.default(path2);
@@ -12962,10 +13512,10 @@ async function diffImages(path1, path2) {
12962
13512
  diffBuffer[i + 2] = Math.round(raw1[i + 2] * 0.4);
12963
13513
  }
12964
13514
  }
12965
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join4(homedir4(), ".browser");
12966
- const diffDir = join4(dataDir, "diffs");
12967
- mkdirSync4(diffDir, { recursive: true });
12968
- const diffPath = join4(diffDir, `diff-${Date.now()}.webp`);
13515
+ const dataDir = process.env["BROWSER_DATA_DIR"] ?? join5(homedir5(), ".browser");
13516
+ const diffDir = join5(dataDir, "diffs");
13517
+ mkdirSync5(diffDir, { recursive: true });
13518
+ const diffPath = join5(diffDir, `diff-${Date.now()}.webp`);
12969
13519
  const diffImageBuffer = await import_sharp2.default(diffBuffer, { raw: { width: w, height: h, channels } }).webp({ quality: 85 }).toBuffer();
12970
13520
  await Bun.write(diffPath, diffImageBuffer);
12971
13521
  return {
@@ -12981,9 +13531,9 @@ async function diffImages(path1, path2) {
12981
13531
  init_snapshot();
12982
13532
 
12983
13533
  // src/lib/files-integration.ts
12984
- import { join as join5 } from "path";
12985
- import { mkdirSync as mkdirSync5, copyFileSync as copyFileSync2 } from "fs";
12986
- import { homedir as homedir5 } from "os";
13534
+ import { join as join6 } from "path";
13535
+ import { mkdirSync as mkdirSync6, copyFileSync as copyFileSync2 } from "fs";
13536
+ import { homedir as homedir6 } from "os";
12987
13537
  async function persistFile(localPath, opts) {
12988
13538
  try {
12989
13539
  const mod = await import("@hasna/files");
@@ -12992,12 +13542,12 @@ async function persistFile(localPath, opts) {
12992
13542
  return { id: ref.id, path: ref.path ?? localPath, permanent: true, provider: "open-files" };
12993
13543
  }
12994
13544
  } catch {}
12995
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join5(homedir5(), ".browser");
13545
+ const dataDir = process.env["BROWSER_DATA_DIR"] ?? join6(homedir6(), ".browser");
12996
13546
  const date = new Date().toISOString().split("T")[0];
12997
- const dir = join5(dataDir, "persistent", date);
12998
- mkdirSync5(dir, { recursive: true });
13547
+ const dir = join6(dataDir, "persistent", date);
13548
+ mkdirSync6(dir, { recursive: true });
12999
13549
  const filename = localPath.split("/").pop() ?? "file";
13000
- const targetPath = join5(dir, filename);
13550
+ const targetPath = join6(dir, filename);
13001
13551
  copyFileSync2(localPath, targetPath);
13002
13552
  return {
13003
13553
  id: `local-${Date.now()}`,
@@ -13089,23 +13639,23 @@ async function closeTab(page, index) {
13089
13639
  }
13090
13640
 
13091
13641
  // src/lib/profiles.ts
13092
- import { mkdirSync as mkdirSync6, existsSync as existsSync3, readdirSync as readdirSync2, rmSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
13093
- import { join as join6 } from "path";
13094
- import { homedir as homedir6 } from "os";
13642
+ import { mkdirSync as mkdirSync7, existsSync as existsSync3, readdirSync as readdirSync2, rmSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
13643
+ import { join as join7 } from "path";
13644
+ import { homedir as homedir7 } from "os";
13095
13645
  function getProfilesDir() {
13096
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join6(homedir6(), ".browser");
13097
- const dir = join6(dataDir, "profiles");
13098
- mkdirSync6(dir, { recursive: true });
13646
+ const dataDir = process.env["BROWSER_DATA_DIR"] ?? join7(homedir7(), ".browser");
13647
+ const dir = join7(dataDir, "profiles");
13648
+ mkdirSync7(dir, { recursive: true });
13099
13649
  return dir;
13100
13650
  }
13101
- function getProfileDir(name) {
13102
- return join6(getProfilesDir(), name);
13651
+ function getProfileDir2(name) {
13652
+ return join7(getProfilesDir(), name);
13103
13653
  }
13104
13654
  async function saveProfile(page, name) {
13105
- const dir = getProfileDir(name);
13106
- mkdirSync6(dir, { recursive: true });
13655
+ const dir = getProfileDir2(name);
13656
+ mkdirSync7(dir, { recursive: true });
13107
13657
  const cookies = await page.context().cookies();
13108
- writeFileSync2(join6(dir, "cookies.json"), JSON.stringify(cookies, null, 2));
13658
+ writeFileSync2(join7(dir, "cookies.json"), JSON.stringify(cookies, null, 2));
13109
13659
  let localStorage2 = {};
13110
13660
  try {
13111
13661
  localStorage2 = await page.evaluate(() => {
@@ -13117,11 +13667,11 @@ async function saveProfile(page, name) {
13117
13667
  return result;
13118
13668
  });
13119
13669
  } catch {}
13120
- writeFileSync2(join6(dir, "storage.json"), JSON.stringify(localStorage2, null, 2));
13670
+ writeFileSync2(join7(dir, "storage.json"), JSON.stringify(localStorage2, null, 2));
13121
13671
  const savedAt = new Date().toISOString();
13122
13672
  const url = page.url();
13123
13673
  const meta = { saved_at: savedAt, url };
13124
- writeFileSync2(join6(dir, "meta.json"), JSON.stringify(meta, null, 2));
13674
+ writeFileSync2(join7(dir, "meta.json"), JSON.stringify(meta, null, 2));
13125
13675
  return {
13126
13676
  name,
13127
13677
  saved_at: savedAt,
@@ -13131,13 +13681,13 @@ async function saveProfile(page, name) {
13131
13681
  };
13132
13682
  }
13133
13683
  function loadProfile(name) {
13134
- const dir = getProfileDir(name);
13684
+ const dir = getProfileDir2(name);
13135
13685
  if (!existsSync3(dir)) {
13136
13686
  throw new Error(`Profile not found: ${name}`);
13137
13687
  }
13138
- const cookiesPath = join6(dir, "cookies.json");
13139
- const storagePath = join6(dir, "storage.json");
13140
- const metaPath2 = join6(dir, "meta.json");
13688
+ const cookiesPath = join7(dir, "cookies.json");
13689
+ const storagePath = join7(dir, "storage.json");
13690
+ const metaPath2 = join7(dir, "meta.json");
13141
13691
  const cookies = existsSync3(cookiesPath) ? JSON.parse(readFileSync2(cookiesPath, "utf8")) : [];
13142
13692
  const localStorage2 = existsSync3(storagePath) ? JSON.parse(readFileSync2(storagePath, "utf8")) : {};
13143
13693
  let savedAt = new Date().toISOString();
@@ -13178,24 +13728,24 @@ function listProfiles() {
13178
13728
  if (!entry.isDirectory())
13179
13729
  continue;
13180
13730
  const name = entry.name;
13181
- const profileDir = join6(dir, name);
13731
+ const profileDir = join7(dir, name);
13182
13732
  let savedAt = "";
13183
13733
  let url;
13184
13734
  let cookieCount = 0;
13185
13735
  let storageKeyCount = 0;
13186
13736
  try {
13187
- const metaPath2 = join6(profileDir, "meta.json");
13737
+ const metaPath2 = join7(profileDir, "meta.json");
13188
13738
  if (existsSync3(metaPath2)) {
13189
13739
  const meta = JSON.parse(readFileSync2(metaPath2, "utf8"));
13190
13740
  savedAt = meta.saved_at ?? "";
13191
13741
  url = meta.url;
13192
13742
  }
13193
- const cookiesPath = join6(profileDir, "cookies.json");
13743
+ const cookiesPath = join7(profileDir, "cookies.json");
13194
13744
  if (existsSync3(cookiesPath)) {
13195
13745
  const cookies = JSON.parse(readFileSync2(cookiesPath, "utf8"));
13196
13746
  cookieCount = Array.isArray(cookies) ? cookies.length : 0;
13197
13747
  }
13198
- const storagePath = join6(profileDir, "storage.json");
13748
+ const storagePath = join7(profileDir, "storage.json");
13199
13749
  if (existsSync3(storagePath)) {
13200
13750
  const storage = JSON.parse(readFileSync2(storagePath, "utf8"));
13201
13751
  storageKeyCount = Object.keys(storage).length;
@@ -13212,7 +13762,7 @@ function listProfiles() {
13212
13762
  return profiles.sort((a, b) => b.saved_at.localeCompare(a.saved_at));
13213
13763
  }
13214
13764
  function deleteProfile(name) {
13215
- const dir = getProfileDir(name);
13765
+ const dir = getProfileDir2(name);
13216
13766
  if (!existsSync3(dir))
13217
13767
  return false;
13218
13768
  try {
@@ -13224,7 +13774,7 @@ function deleteProfile(name) {
13224
13774
  }
13225
13775
 
13226
13776
  // src/mcp/index.ts
13227
- var _pkg = JSON.parse(readFileSync3(join7(import.meta.dir, "../../package.json"), "utf8"));
13777
+ var _pkg = JSON.parse(readFileSync3(join8(import.meta.dir, "../../package.json"), "utf8"));
13228
13778
  var networkLogCleanup = new Map;
13229
13779
  var consoleCaptureCleanup = new Map;
13230
13780
  var harCaptures = new Map;
@@ -13244,7 +13794,7 @@ var server = new McpServer({
13244
13794
  version: "0.0.1"
13245
13795
  });
13246
13796
  server.tool("browser_session_create", "Create a new browser session with the specified engine", {
13247
- engine: exports_external.enum(["playwright", "cdp", "lightpanda", "auto"]).optional().default("auto"),
13797
+ engine: exports_external.enum(["playwright", "cdp", "lightpanda", "bun", "auto"]).optional().default("auto"),
13248
13798
  use_case: exports_external.string().optional(),
13249
13799
  project_id: exports_external.string().optional(),
13250
13800
  agent_id: exports_external.string().optional(),
@@ -13299,7 +13849,13 @@ server.tool("browser_navigate", "Navigate to a URL. Auto-detects redirects, auto
13299
13849
  }, async ({ session_id, url, timeout, auto_snapshot, auto_thumbnail }) => {
13300
13850
  try {
13301
13851
  const page = getSessionPage(session_id);
13302
- await navigate(page, url, timeout);
13852
+ if (isBunSession(session_id)) {
13853
+ const bunView = getSessionBunView(session_id);
13854
+ await bunView.goto(url, { timeout });
13855
+ await new Promise((r) => setTimeout(r, 500));
13856
+ } else {
13857
+ await navigate(page, url, timeout);
13858
+ }
13303
13859
  const title = await getTitle(page);
13304
13860
  const current_url = await getUrl(page);
13305
13861
  const redirected = current_url !== url && current_url !== url + "/" && url !== current_url.replace(/\/$/, "");
@@ -13340,6 +13896,9 @@ server.tool("browser_navigate", "Navigate to a URL. Auto-detects redirects, auto
13340
13896
  result.thumbnail_base64 = ss.base64.length > 50000 ? "" : ss.base64;
13341
13897
  } catch {}
13342
13898
  }
13899
+ if (isBunSession(session_id) && auto_snapshot) {
13900
+ await new Promise((r) => setTimeout(r, 200));
13901
+ }
13343
13902
  if (auto_snapshot) {
13344
13903
  try {
13345
13904
  const snap = await takeSnapshot(page, session_id);
@@ -13847,7 +14406,7 @@ server.tool("browser_crawl", "Crawl a URL recursively and return discovered page
13847
14406
  max_pages: exports_external.number().optional().default(50),
13848
14407
  same_domain: exports_external.boolean().optional().default(true),
13849
14408
  project_id: exports_external.string().optional(),
13850
- engine: exports_external.enum(["playwright", "cdp", "lightpanda", "auto"]).optional().default("auto")
14409
+ engine: exports_external.enum(["playwright", "cdp", "lightpanda", "bun", "auto"]).optional().default("auto")
13851
14410
  }, async ({ url, max_depth, max_pages, same_domain, project_id, engine }) => {
13852
14411
  try {
13853
14412
  const result = await crawl(url, {