@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/cli/index.js CHANGED
@@ -2515,11 +2515,420 @@ var init_lightpanda = __esm(() => {
2515
2515
  LIGHTPANDA_BINARY = process.env["LIGHTPANDA_BINARY"] ?? "lightpanda";
2516
2516
  });
2517
2517
 
2518
+ // src/engines/bun-webview.ts
2519
+ import { join as join2 } from "path";
2520
+ import { mkdirSync as mkdirSync2 } from "fs";
2521
+ import { homedir as homedir2 } from "os";
2522
+ function isBunWebViewAvailable() {
2523
+ return typeof globalThis.Bun !== "undefined" && typeof globalThis.Bun.WebView !== "undefined";
2524
+ }
2525
+ function getProfileDir(profileName) {
2526
+ const base = process.env["BROWSER_DATA_DIR"] ?? join2(homedir2(), ".browser");
2527
+ const dir = join2(base, "profiles", profileName);
2528
+ mkdirSync2(dir, { recursive: true });
2529
+ return dir;
2530
+ }
2531
+ var BunWebViewSession;
2532
+ var init_bun_webview = __esm(() => {
2533
+ BunWebViewSession = class BunWebViewSession {
2534
+ view;
2535
+ _sessionId;
2536
+ _eventListeners = new Map;
2537
+ constructor(opts = {}) {
2538
+ if (!isBunWebViewAvailable()) {
2539
+ throw new Error("Bun.WebView is not available. Install Bun canary: bun upgrade --canary");
2540
+ }
2541
+ const BunWebView = globalThis.Bun.WebView;
2542
+ const constructorOpts = {
2543
+ width: opts.width ?? 1280,
2544
+ height: opts.height ?? 720
2545
+ };
2546
+ if (opts.profile) {
2547
+ constructorOpts.dataStore = { directory: getProfileDir(opts.profile) };
2548
+ } else {
2549
+ constructorOpts.dataStore = "ephemeral";
2550
+ }
2551
+ if (opts.onConsole) {
2552
+ constructorOpts.console = opts.onConsole;
2553
+ }
2554
+ this.view = new BunWebView(constructorOpts);
2555
+ this.view.onNavigated = (url) => {
2556
+ this._emit("navigated", url);
2557
+ };
2558
+ this.view.onNavigationFailed = (error) => {
2559
+ this._emit("navigationfailed", error);
2560
+ };
2561
+ }
2562
+ async goto(url, opts) {
2563
+ await this.view.navigate(url);
2564
+ await new Promise((r) => setTimeout(r, 200));
2565
+ }
2566
+ async goBack() {
2567
+ await this.view.goBack();
2568
+ }
2569
+ async goForward() {
2570
+ await this.view.goForward();
2571
+ }
2572
+ async reload() {
2573
+ await this.view.reload();
2574
+ }
2575
+ async evaluate(fnOrExpr, ...args) {
2576
+ let expr;
2577
+ if (typeof fnOrExpr === "function") {
2578
+ const serializedArgs = args.map((a) => JSON.stringify(a)).join(", ");
2579
+ expr = `(${fnOrExpr.toString()})(${serializedArgs})`;
2580
+ } else {
2581
+ expr = fnOrExpr;
2582
+ }
2583
+ return this.view.evaluate(expr);
2584
+ }
2585
+ async screenshot(opts) {
2586
+ const uint8 = await this.view.screenshot();
2587
+ return Buffer.from(uint8);
2588
+ }
2589
+ async click(selector, opts) {
2590
+ await this.view.click(selector, opts ? { button: opts.button } : undefined);
2591
+ }
2592
+ async type(selector, text, opts) {
2593
+ try {
2594
+ await this.view.click(selector);
2595
+ } catch {}
2596
+ await this.view.type(text);
2597
+ }
2598
+ async fill(selector, value) {
2599
+ await this.view.evaluate(`
2600
+ (() => {
2601
+ const el = document.querySelector(${JSON.stringify(selector)});
2602
+ if (el) { el.value = ''; el.dispatchEvent(new Event('input')); }
2603
+ })()
2604
+ `);
2605
+ await this.type(selector, value);
2606
+ }
2607
+ async press(key, opts) {
2608
+ await this.view.press(key, opts);
2609
+ }
2610
+ async scroll(direction, amount) {
2611
+ const dx = direction === "left" ? -amount : direction === "right" ? amount : 0;
2612
+ const dy = direction === "up" ? -amount : direction === "down" ? amount : 0;
2613
+ await this.view.scroll(dx, dy);
2614
+ }
2615
+ async scrollIntoView(selector) {
2616
+ await this.view.scrollTo(selector);
2617
+ }
2618
+ async hover(selector) {
2619
+ try {
2620
+ await this.view.scrollTo(selector);
2621
+ } catch {}
2622
+ }
2623
+ async resize(width, height) {
2624
+ await this.view.resize(width, height);
2625
+ }
2626
+ async $(selector) {
2627
+ const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
2628
+ if (!exists)
2629
+ return null;
2630
+ return {
2631
+ textContent: async () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`)
2632
+ };
2633
+ }
2634
+ async $$(selector) {
2635
+ const count = await this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)}).length`);
2636
+ return Array.from({ length: count }, (_, i) => ({
2637
+ textContent: async () => this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)})[${i}]?.textContent ?? null`)
2638
+ }));
2639
+ }
2640
+ async inputValue(selector) {
2641
+ return this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.value ?? ''`);
2642
+ }
2643
+ async isChecked(selector) {
2644
+ return this.view.evaluate(`!!(document.querySelector(${JSON.stringify(selector)})?.checked)`);
2645
+ }
2646
+ async isVisible(selector) {
2647
+ return this.view.evaluate(`
2648
+ (() => {
2649
+ const el = document.querySelector(${JSON.stringify(selector)});
2650
+ if (!el) return false;
2651
+ const style = window.getComputedStyle(el);
2652
+ return style.display !== 'none' && style.visibility !== 'hidden' && el.offsetWidth > 0;
2653
+ })()
2654
+ `);
2655
+ }
2656
+ async isEnabled(selector) {
2657
+ return this.view.evaluate(`!(document.querySelector(${JSON.stringify(selector)})?.disabled)`);
2658
+ }
2659
+ async selectOption(selector, value) {
2660
+ await this.view.evaluate(`
2661
+ (() => {
2662
+ const el = document.querySelector(${JSON.stringify(selector)});
2663
+ if (el) {
2664
+ el.value = ${JSON.stringify(value)};
2665
+ el.dispatchEvent(new Event('change'));
2666
+ }
2667
+ })()
2668
+ `);
2669
+ return [value];
2670
+ }
2671
+ async check(selector) {
2672
+ await this.view.evaluate(`
2673
+ (() => {
2674
+ const el = document.querySelector(${JSON.stringify(selector)});
2675
+ if (el && !el.checked) { el.checked = true; el.dispatchEvent(new Event('change')); }
2676
+ })()
2677
+ `);
2678
+ }
2679
+ async uncheck(selector) {
2680
+ await this.view.evaluate(`
2681
+ (() => {
2682
+ const el = document.querySelector(${JSON.stringify(selector)});
2683
+ if (el && el.checked) { el.checked = false; el.dispatchEvent(new Event('change')); }
2684
+ })()
2685
+ `);
2686
+ }
2687
+ async setInputFiles(selector, files) {
2688
+ throw new Error("File upload not supported in Bun.WebView engine. Use engine: 'playwright' instead.");
2689
+ }
2690
+ getByRole(role, opts) {
2691
+ const name = opts?.name?.toString() ?? "";
2692
+ const selector = name ? `[role="${role}"][aria-label*="${name}"], ${role}[aria-label*="${name}"]` : `[role="${role}"], ${role}`;
2693
+ return {
2694
+ click: (clickOpts) => this.click(selector, clickOpts),
2695
+ fill: (value) => this.fill(selector, value),
2696
+ check: () => this.check(selector),
2697
+ uncheck: () => this.uncheck(selector),
2698
+ isVisible: () => this.isVisible(selector),
2699
+ textContent: () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`),
2700
+ inputValue: () => this.inputValue(selector),
2701
+ first: () => ({
2702
+ click: (clickOpts) => this.click(selector, clickOpts),
2703
+ fill: (value) => this.fill(selector, value),
2704
+ textContent: () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`),
2705
+ isVisible: () => this.isVisible(selector),
2706
+ hover: () => this.hover(selector),
2707
+ boundingBox: async () => null,
2708
+ scrollIntoViewIfNeeded: () => this.scrollIntoView(selector),
2709
+ evaluate: (fn) => this.view.evaluate(`(${fn.toString()})(document.querySelector(${JSON.stringify(selector)}))`),
2710
+ waitFor: (opts2) => {
2711
+ return new Promise((resolve, reject) => {
2712
+ const timeout = opts2?.timeout ?? 1e4;
2713
+ const start = Date.now();
2714
+ const check = async () => {
2715
+ const visible = await this.isVisible(selector);
2716
+ if (visible)
2717
+ return resolve();
2718
+ if (Date.now() - start > timeout)
2719
+ return reject(new Error(`Timeout waiting for ${selector}`));
2720
+ setTimeout(check, 100);
2721
+ };
2722
+ check();
2723
+ });
2724
+ }
2725
+ }),
2726
+ count: async () => {
2727
+ const count = await this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)}).length`);
2728
+ return count;
2729
+ },
2730
+ nth: (n) => ({
2731
+ click: (clickOpts) => this.click(selector, clickOpts),
2732
+ textContent: () => this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)})[${n}]?.textContent ?? null`),
2733
+ isVisible: () => this.isVisible(selector)
2734
+ })
2735
+ };
2736
+ }
2737
+ getByText(text, opts) {
2738
+ const selector = opts?.exact ? `*:is(button, a, span, div, p, h1, h2, h3, h4, label)` : "*";
2739
+ return {
2740
+ first: () => ({
2741
+ click: async (clickOpts) => {
2742
+ await this.view.evaluate(`
2743
+ (() => {
2744
+ const text = ${JSON.stringify(text)};
2745
+ const all = document.querySelectorAll('*');
2746
+ for (const el of all) {
2747
+ if (el.children.length === 0 && el.textContent?.trim() === text) {
2748
+ el.click(); return;
2749
+ }
2750
+ }
2751
+ for (const el of all) {
2752
+ if (el.textContent?.includes(text)) { el.click(); return; }
2753
+ }
2754
+ })()
2755
+ `);
2756
+ },
2757
+ waitFor: (waitOpts) => {
2758
+ const timeout = waitOpts?.timeout ?? 1e4;
2759
+ return new Promise((resolve, reject) => {
2760
+ const start = Date.now();
2761
+ const check = async () => {
2762
+ const found = await this.view.evaluate(`document.body?.textContent?.includes(${JSON.stringify(text)})`);
2763
+ if (found)
2764
+ return resolve();
2765
+ if (Date.now() - start > timeout)
2766
+ return reject(new Error(`Timeout: text "${text}" not found`));
2767
+ setTimeout(check, 100);
2768
+ };
2769
+ check();
2770
+ });
2771
+ }
2772
+ })
2773
+ };
2774
+ }
2775
+ locator(selector) {
2776
+ return {
2777
+ click: (opts) => this.click(selector, opts),
2778
+ fill: (value) => this.fill(selector, value),
2779
+ scrollIntoViewIfNeeded: () => this.scrollIntoView(selector),
2780
+ first: () => this.getByRole("*").first(),
2781
+ evaluate: (fn) => this.view.evaluate(`(${fn.toString()})(document.querySelector(${JSON.stringify(selector)}))`),
2782
+ waitFor: (opts) => {
2783
+ const timeout = opts?.timeout ?? 1e4;
2784
+ return new Promise((resolve, reject) => {
2785
+ const start = Date.now();
2786
+ const check = async () => {
2787
+ const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
2788
+ if (exists)
2789
+ return resolve();
2790
+ if (Date.now() - start > timeout)
2791
+ return reject(new Error(`Timeout: ${selector}`));
2792
+ setTimeout(check, 100);
2793
+ };
2794
+ check();
2795
+ });
2796
+ }
2797
+ };
2798
+ }
2799
+ url() {
2800
+ return this.view.url;
2801
+ }
2802
+ async title() {
2803
+ return this.view.title || await this.evaluate("document.title");
2804
+ }
2805
+ viewportSize() {
2806
+ return { width: 1280, height: 720 };
2807
+ }
2808
+ async waitForLoadState(state, opts) {
2809
+ await new Promise((r) => setTimeout(r, 200));
2810
+ }
2811
+ async waitForURL(pattern, opts) {
2812
+ const timeout = opts?.timeout ?? 30000;
2813
+ const start = Date.now();
2814
+ while (Date.now() - start < timeout) {
2815
+ const url = this.view.url;
2816
+ const matches = pattern instanceof RegExp ? pattern.test(url) : url.includes(pattern);
2817
+ if (matches)
2818
+ return;
2819
+ await new Promise((r) => setTimeout(r, 100));
2820
+ }
2821
+ throw new Error(`Timeout waiting for URL to match ${pattern}`);
2822
+ }
2823
+ async waitForSelector(selector, opts) {
2824
+ const timeout = opts?.timeout ?? 1e4;
2825
+ const start = Date.now();
2826
+ while (Date.now() - start < timeout) {
2827
+ const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
2828
+ if (exists)
2829
+ return;
2830
+ await new Promise((r) => setTimeout(r, 100));
2831
+ }
2832
+ throw new Error(`Timeout waiting for ${selector}`);
2833
+ }
2834
+ async setContent(html) {
2835
+ await this.view.navigate(`data:text/html,${encodeURIComponent(html)}`);
2836
+ await new Promise((r) => setTimeout(r, 100));
2837
+ }
2838
+ async content() {
2839
+ return this.view.evaluate("document.documentElement.outerHTML");
2840
+ }
2841
+ async addInitScript(script) {
2842
+ const expr = typeof script === "function" ? `(${script.toString()})()` : script;
2843
+ await this.view.evaluate(expr);
2844
+ }
2845
+ keyboard = {
2846
+ press: (key) => this.view.press(key)
2847
+ };
2848
+ context() {
2849
+ return {
2850
+ close: async () => {
2851
+ await this.close();
2852
+ },
2853
+ newPage: async () => {
2854
+ throw new Error("Multi-tab not supported in Bun.WebView. Use engine: 'playwright'");
2855
+ },
2856
+ cookies: async () => [],
2857
+ addCookies: async (_) => {},
2858
+ clearCookies: async () => {},
2859
+ newCDPSession: async () => {
2860
+ throw new Error("CDP session via context not available in Bun.WebView. Use view.cdp() when shipped.");
2861
+ },
2862
+ route: async (_pattern, _handler) => {
2863
+ throw new Error("Network interception not supported in Bun.WebView. Use engine: 'cdp' or 'playwright'.");
2864
+ },
2865
+ unrouteAll: async () => {},
2866
+ pages: () => [],
2867
+ addInitScript: async (script) => {
2868
+ await this.addInitScript(script);
2869
+ }
2870
+ };
2871
+ }
2872
+ on(event, handler) {
2873
+ if (!this._eventListeners.has(event))
2874
+ this._eventListeners.set(event, []);
2875
+ this._eventListeners.get(event).push(handler);
2876
+ return this;
2877
+ }
2878
+ off(event, handler) {
2879
+ const listeners = this._eventListeners.get(event) ?? [];
2880
+ this._eventListeners.set(event, listeners.filter((l) => l !== handler));
2881
+ return this;
2882
+ }
2883
+ _emit(event, ...args) {
2884
+ for (const handler of this._eventListeners.get(event) ?? []) {
2885
+ try {
2886
+ handler(...args);
2887
+ } catch {}
2888
+ }
2889
+ }
2890
+ async pdf(_opts) {
2891
+ throw new Error("PDF generation not supported in Bun.WebView. Use engine: 'playwright'.");
2892
+ }
2893
+ coverage = {
2894
+ startJSCoverage: async () => {},
2895
+ stopJSCoverage: async () => [],
2896
+ startCSSCoverage: async () => {},
2897
+ stopCSSCoverage: async () => []
2898
+ };
2899
+ setSessionId(id) {
2900
+ this._sessionId = id;
2901
+ }
2902
+ getSessionId() {
2903
+ return this._sessionId;
2904
+ }
2905
+ getNativeView() {
2906
+ return this.view;
2907
+ }
2908
+ async close() {
2909
+ try {
2910
+ await this.view.close();
2911
+ } catch {}
2912
+ }
2913
+ [Symbol.asyncDispose]() {
2914
+ return this.close();
2915
+ }
2916
+ };
2917
+ });
2918
+
2518
2919
  // src/engines/selector.ts
2519
2920
  function selectEngine(useCase, explicit) {
2520
2921
  if (explicit && explicit !== "auto")
2521
2922
  return explicit;
2522
2923
  const preferred = ENGINE_MAP[useCase];
2924
+ if (preferred === "bun") {
2925
+ if (isBunWebViewAvailable())
2926
+ return "bun";
2927
+ if (useCase === "scrape" /* SCRAPE */ || useCase === "extract_links" /* EXTRACT_LINKS */ || useCase === "status_check" /* STATUS_CHECK */) {
2928
+ return isLightpandaAvailable() ? "lightpanda" : "playwright";
2929
+ }
2930
+ return "playwright";
2931
+ }
2523
2932
  if (preferred === "lightpanda" && !isLightpandaAvailable()) {
2524
2933
  return "playwright";
2525
2934
  }
@@ -2529,13 +2938,14 @@ var ENGINE_MAP;
2529
2938
  var init_selector = __esm(() => {
2530
2939
  init_types();
2531
2940
  init_lightpanda();
2941
+ init_bun_webview();
2532
2942
  ENGINE_MAP = {
2533
- ["scrape" /* SCRAPE */]: "lightpanda",
2534
- ["extract_links" /* EXTRACT_LINKS */]: "lightpanda",
2535
- ["status_check" /* STATUS_CHECK */]: "lightpanda",
2943
+ ["scrape" /* SCRAPE */]: "bun",
2944
+ ["extract_links" /* EXTRACT_LINKS */]: "bun",
2945
+ ["status_check" /* STATUS_CHECK */]: "bun",
2946
+ ["screenshot" /* SCREENSHOT */]: "bun",
2947
+ ["spa_navigate" /* SPA_NAVIGATE */]: "bun",
2536
2948
  ["form_fill" /* FORM_FILL */]: "playwright",
2537
- ["spa_navigate" /* SPA_NAVIGATE */]: "playwright",
2538
- ["screenshot" /* SCREENSHOT */]: "playwright",
2539
2949
  ["auth_flow" /* AUTH_FLOW */]: "playwright",
2540
2950
  ["multi_tab" /* MULTI_TAB */]: "playwright",
2541
2951
  ["record_replay" /* RECORD_REPLAY */]: "playwright",
@@ -2878,12 +3288,30 @@ var init_dialogs = __esm(() => {
2878
3288
  });
2879
3289
 
2880
3290
  // src/lib/session.ts
3291
+ function createBunProxy(view) {
3292
+ return view;
3293
+ }
2881
3294
  async function createSession2(opts = {}) {
2882
3295
  const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
2883
3296
  const resolvedEngine = engine === "auto" ? "playwright" : engine;
2884
- let browser;
3297
+ let browser = null;
3298
+ let bunView = null;
2885
3299
  let page;
2886
- if (resolvedEngine === "lightpanda") {
3300
+ if (resolvedEngine === "bun") {
3301
+ if (!isBunWebViewAvailable()) {
3302
+ console.warn("[browser] Bun.WebView requested but not available \u2014 falling back to playwright. Run: bun upgrade --canary");
3303
+ browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
3304
+ page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
3305
+ } else {
3306
+ bunView = new BunWebViewSession({
3307
+ width: opts.viewport?.width ?? 1280,
3308
+ height: opts.viewport?.height ?? 720,
3309
+ profile: opts.name ?? undefined
3310
+ });
3311
+ if (opts.stealth) {}
3312
+ page = createBunProxy(bunView);
3313
+ }
3314
+ } else if (resolvedEngine === "lightpanda") {
2887
3315
  browser = await connectLightpanda();
2888
3316
  const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
2889
3317
  page = await context.newPage();
@@ -2893,12 +3321,9 @@ async function createSession2(opts = {}) {
2893
3321
  viewport: opts.viewport,
2894
3322
  userAgent: opts.userAgent
2895
3323
  });
2896
- page = await getPage(browser, {
2897
- viewport: opts.viewport,
2898
- userAgent: opts.userAgent
2899
- });
3324
+ page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
2900
3325
  }
2901
- let sessionName = opts.name ?? (opts.startUrl ? (() => {
3326
+ const sessionName = opts.name ?? (opts.startUrl ? (() => {
2902
3327
  try {
2903
3328
  return new URL(opts.startUrl).hostname;
2904
3329
  } catch {
@@ -2906,35 +3331,57 @@ async function createSession2(opts = {}) {
2906
3331
  }
2907
3332
  })() : undefined);
2908
3333
  const session = createSession({
2909
- engine: resolvedEngine,
3334
+ engine: bunView ? "bun" : browser ? resolvedEngine : resolvedEngine,
2910
3335
  projectId: opts.projectId,
2911
3336
  agentId: opts.agentId,
2912
3337
  startUrl: opts.startUrl,
2913
3338
  name: sessionName
2914
3339
  });
2915
- if (opts.stealth) {
3340
+ if (opts.stealth && !bunView) {
2916
3341
  try {
2917
3342
  await applyStealthPatches(page);
2918
3343
  } catch {}
2919
3344
  }
2920
3345
  const cleanups = [];
2921
- if (opts.captureNetwork !== false) {
2922
- try {
2923
- cleanups.push(enableNetworkLogging(page, session.id));
2924
- } catch {}
2925
- }
2926
- if (opts.captureConsole !== false) {
3346
+ if (!bunView) {
3347
+ if (opts.captureNetwork !== false) {
3348
+ try {
3349
+ cleanups.push(enableNetworkLogging(page, session.id));
3350
+ } catch {}
3351
+ }
3352
+ if (opts.captureConsole !== false) {
3353
+ try {
3354
+ cleanups.push(enableConsoleCapture(page, session.id));
3355
+ } catch {}
3356
+ }
2927
3357
  try {
2928
- cleanups.push(enableConsoleCapture(page, session.id));
3358
+ cleanups.push(setupDialogHandler(page, session.id));
2929
3359
  } catch {}
3360
+ } else {
3361
+ if (opts.captureConsole !== false) {
3362
+ try {
3363
+ const { logConsoleMessage: logConsoleMessage2 } = await Promise.resolve().then(() => (init_console_log(), exports_console_log));
3364
+ await bunView.addInitScript(`
3365
+ (() => {
3366
+ const orig = { log: console.log, warn: console.warn, error: console.error, debug: console.debug, info: console.info };
3367
+ ['log','warn','error','debug','info'].forEach(level => {
3368
+ console[level] = (...args) => {
3369
+ orig[level](...args);
3370
+ };
3371
+ });
3372
+ })()
3373
+ `);
3374
+ } catch {}
3375
+ }
2930
3376
  }
2931
- try {
2932
- cleanups.push(setupDialogHandler(page, session.id));
2933
- } catch {}
2934
- handles.set(session.id, { browser, page, engine: resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
3377
+ handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
2935
3378
  if (opts.startUrl) {
2936
3379
  try {
2937
- await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
3380
+ if (bunView) {
3381
+ await bunView.goto(opts.startUrl);
3382
+ } else {
3383
+ await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
3384
+ }
2938
3385
  } catch {}
2939
3386
  }
2940
3387
  return { session, page };
@@ -2944,13 +3391,23 @@ function getSessionPage(sessionId) {
2944
3391
  if (!handle)
2945
3392
  throw new SessionNotFoundError(sessionId);
2946
3393
  try {
2947
- handle.page.url();
3394
+ if (handle.bunView) {
3395
+ handle.bunView.url();
3396
+ } else {
3397
+ handle.page.url();
3398
+ }
2948
3399
  } catch {
2949
3400
  handles.delete(sessionId);
2950
3401
  throw new SessionNotFoundError(sessionId);
2951
3402
  }
2952
3403
  return handle.page;
2953
3404
  }
3405
+ function getSessionBunView(sessionId) {
3406
+ return handles.get(sessionId)?.bunView ?? null;
3407
+ }
3408
+ function isBunSession(sessionId) {
3409
+ return handles.get(sessionId)?.engine === "bun";
3410
+ }
2954
3411
  function setSessionPage(sessionId, page) {
2955
3412
  const handle = handles.get(sessionId);
2956
3413
  if (!handle)
@@ -2965,12 +3422,19 @@ async function closeSession2(sessionId) {
2965
3422
  cleanup();
2966
3423
  } catch {}
2967
3424
  }
2968
- try {
2969
- await handle.page.context().close();
2970
- } catch {}
2971
- try {
2972
- await closeBrowser(handle.browser);
2973
- } catch {}
3425
+ if (handle.bunView) {
3426
+ try {
3427
+ await handle.bunView.close();
3428
+ } catch {}
3429
+ } else {
3430
+ try {
3431
+ await handle.page.context().close();
3432
+ } catch {}
3433
+ try {
3434
+ if (handle.browser)
3435
+ await closeBrowser(handle.browser);
3436
+ } catch {}
3437
+ }
2974
3438
  handles.delete(sessionId);
2975
3439
  }
2976
3440
  return closeSession(sessionId);
@@ -2998,6 +3462,7 @@ var init_session = __esm(() => {
2998
3462
  init_sessions();
2999
3463
  init_playwright();
3000
3464
  init_lightpanda();
3465
+ init_bun_webview();
3001
3466
  init_selector();
3002
3467
  init_network();
3003
3468
  init_console();
@@ -3010,6 +3475,7 @@ var init_session = __esm(() => {
3010
3475
  var exports_snapshot = {};
3011
3476
  __export(exports_snapshot, {
3012
3477
  takeSnapshot: () => takeSnapshot,
3478
+ takeBunSnapshot: () => takeBunSnapshot,
3013
3479
  setLastSnapshot: () => setLastSnapshot,
3014
3480
  hasRefs: () => hasRefs,
3015
3481
  getSessionRefs: () => getSessionRefs,
@@ -3030,6 +3496,10 @@ function clearLastSnapshot(sessionId) {
3030
3496
  lastSnapshots.delete(sessionId);
3031
3497
  }
3032
3498
  async function takeSnapshot(page, sessionId) {
3499
+ const isBunView = typeof page.getNativeView === "function" || typeof page.bunView !== "undefined";
3500
+ if (isBunView) {
3501
+ return takeBunSnapshot(page, sessionId);
3502
+ }
3033
3503
  let ariaTree;
3034
3504
  try {
3035
3505
  ariaTree = await page.locator("body").ariaSnapshot();
@@ -3184,6 +3654,71 @@ function diffSnapshots(before, after) {
3184
3654
  const title_changed = before.tree !== after.tree && (added.length > 0 || removed.length > 0 || modified.length > 0);
3185
3655
  return { added, removed, modified, url_changed, title_changed };
3186
3656
  }
3657
+ async function takeBunSnapshot(page, sessionId) {
3658
+ const refs = {};
3659
+ const refMap = new Map;
3660
+ let refCounter = 0;
3661
+ const lines = [];
3662
+ try {
3663
+ const elements = await page.evaluate(`
3664
+ (() => {
3665
+ 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]';
3666
+ const els = Array.from(document.querySelectorAll(SELECTOR));
3667
+ return els.slice(0, 100).map(el => {
3668
+ const tag = el.tagName.toLowerCase();
3669
+ const inputType = el.getAttribute('type') ?? '';
3670
+ 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);
3671
+ const name = (el.getAttribute('aria-label') || el.textContent?.trim() || el.getAttribute('placeholder') || el.getAttribute('title') || el.getAttribute('value') || el.id || '').slice(0, 80);
3672
+ const enabled = !el.disabled && !el.getAttribute('disabled');
3673
+ const style = window.getComputedStyle(el);
3674
+ const visible = style.display !== 'none' && style.visibility !== 'hidden' && el.offsetWidth > 0;
3675
+ const checked = el.type === 'checkbox' || el.type === 'radio' ? el.checked : undefined;
3676
+ const value = ['input', 'select', 'textarea'].includes(tag) && el.type !== 'checkbox' && el.type !== 'radio' ? el.value : undefined;
3677
+ const selector = el.id ? '#' + el.id : (el.getAttribute('aria-label') ? '[aria-label="' + el.getAttribute('aria-label') + '"]' : tag);
3678
+ return { role, name, enabled, visible, checked, value, selector };
3679
+ }).filter(e => e.visible && e.name);
3680
+ })()
3681
+ `);
3682
+ const pageTitle = await page.evaluate("document.title");
3683
+ const pageUrl = typeof page.url === "function" ? page.url() : "";
3684
+ lines.push(`# ${pageTitle || "Page"} (${pageUrl})`);
3685
+ for (const el of elements) {
3686
+ if (!el.name)
3687
+ continue;
3688
+ const ref = `@e${refCounter}`;
3689
+ refCounter++;
3690
+ refs[ref] = {
3691
+ role: el.role,
3692
+ name: el.name,
3693
+ visible: el.visible,
3694
+ enabled: el.enabled,
3695
+ value: el.value,
3696
+ checked: el.checked
3697
+ };
3698
+ refMap.set(ref, { role: el.role, name: el.name, locatorSelector: el.selector });
3699
+ const extras = [];
3700
+ if (el.checked !== undefined)
3701
+ extras.push(`checked=${el.checked}`);
3702
+ if (!el.enabled)
3703
+ extras.push("disabled");
3704
+ if (el.value && el.value !== el.name)
3705
+ extras.push(`value="${el.value.slice(0, 30)}"`);
3706
+ const extrasStr = extras.length ? ` (${extras.join(", ")})` : "";
3707
+ lines.push(`${el.role} "${el.name}" [${ref}]${extrasStr}`);
3708
+ }
3709
+ } catch (err) {
3710
+ lines.push(`# (snapshot error: ${err instanceof Error ? err.message : String(err)})`);
3711
+ }
3712
+ if (sessionId) {
3713
+ sessionRefMaps.set(sessionId, refMap);
3714
+ }
3715
+ return {
3716
+ tree: lines.join(`
3717
+ `),
3718
+ refs,
3719
+ interactive_count: refCounter
3720
+ };
3721
+ }
3187
3722
  var lastSnapshots, sessionRefMaps, INTERACTIVE_ROLES;
3188
3723
  var init_snapshot = __esm(() => {
3189
3724
  lastSnapshots = new Map;
@@ -3528,9 +4063,19 @@ async function getLinks(page, baseUrl) {
3528
4063
  }, baseUrl ?? page.url());
3529
4064
  }
3530
4065
  async function getTitle(page) {
4066
+ if (typeof page.getNativeView === "function") {
4067
+ const nativeView = page.getNativeView();
4068
+ const t = nativeView?.title;
4069
+ return typeof t === "string" && t ? t : "";
4070
+ }
3531
4071
  return page.title();
3532
4072
  }
3533
4073
  async function getUrl(page) {
4074
+ if (typeof page.getNativeView === "function") {
4075
+ const nativeView = page.getNativeView();
4076
+ const u = nativeView?.url;
4077
+ return typeof u === "string" ? u : "";
4078
+ }
3534
4079
  return page.url();
3535
4080
  }
3536
4081
  async function findElements(page, selector) {
@@ -10172,17 +10717,17 @@ var init_gallery = __esm(() => {
10172
10717
  });
10173
10718
 
10174
10719
  // src/lib/screenshot.ts
10175
- import { join as join2 } from "path";
10176
- import { mkdirSync as mkdirSync2 } from "fs";
10177
- import { homedir as homedir2 } from "os";
10720
+ import { join as join3 } from "path";
10721
+ import { mkdirSync as mkdirSync3 } from "fs";
10722
+ import { homedir as homedir3 } from "os";
10178
10723
  function getDataDir2() {
10179
- return process.env["BROWSER_DATA_DIR"] ?? join2(homedir2(), ".browser");
10724
+ return process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser");
10180
10725
  }
10181
10726
  function getScreenshotDir(projectId) {
10182
- const base = join2(getDataDir2(), "screenshots");
10727
+ const base = join3(getDataDir2(), "screenshots");
10183
10728
  const date = new Date().toISOString().split("T")[0];
10184
- const dir = projectId ? join2(base, projectId, date) : join2(base, date);
10185
- mkdirSync2(dir, { recursive: true });
10729
+ const dir = projectId ? join3(base, projectId, date) : join3(base, date);
10730
+ mkdirSync3(dir, { recursive: true });
10186
10731
  return dir;
10187
10732
  }
10188
10733
  async function compressBuffer(raw, format, quality, maxWidth) {
@@ -10197,7 +10742,7 @@ async function compressBuffer(raw, format, quality, maxWidth) {
10197
10742
  }
10198
10743
  }
10199
10744
  async function generateThumbnail(raw, dir, stem) {
10200
- const thumbPath = join2(dir, `${stem}.thumb.webp`);
10745
+ const thumbPath = join3(dir, `${stem}.thumb.webp`);
10201
10746
  const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
10202
10747
  await Bun.write(thumbPath, thumbBuffer);
10203
10748
  return { path: thumbPath, base64: thumbBuffer.toString("base64") };
@@ -10216,11 +10761,20 @@ async function takeScreenshot(page, opts) {
10216
10761
  type: "png"
10217
10762
  };
10218
10763
  let rawBuffer;
10764
+ const isBunView = typeof page.getNativeView === "function";
10219
10765
  if (opts?.selector) {
10220
- const el = await page.$(opts.selector);
10221
- if (!el)
10222
- throw new BrowserError(`Element not found: ${opts.selector}`, "ELEMENT_NOT_FOUND");
10223
- rawBuffer = await el.screenshot(rawOpts);
10766
+ if (isBunView) {
10767
+ const uint8 = await page.screenshot();
10768
+ rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
10769
+ } else {
10770
+ const el = await page.$(opts.selector);
10771
+ if (!el)
10772
+ throw new BrowserError(`Element not found: ${opts.selector}`, "ELEMENT_NOT_FOUND");
10773
+ rawBuffer = await el.screenshot(rawOpts);
10774
+ }
10775
+ } else if (isBunView) {
10776
+ const uint8 = await page.screenshot();
10777
+ rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
10224
10778
  } else {
10225
10779
  rawBuffer = await page.screenshot(rawOpts);
10226
10780
  }
@@ -10245,7 +10799,7 @@ async function takeScreenshot(page, opts) {
10245
10799
  const compressedSizeBytes = finalBuffer.length;
10246
10800
  const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
10247
10801
  const ext = format;
10248
- const screenshotPath = opts?.path ?? join2(dir, `${stem}.${ext}`);
10802
+ const screenshotPath = opts?.path ?? join3(dir, `${stem}.${ext}`);
10249
10803
  await Bun.write(screenshotPath, finalBuffer);
10250
10804
  let thumbnailPath;
10251
10805
  let thumbnailBase64;
@@ -10305,12 +10859,12 @@ async function takeScreenshot(page, opts) {
10305
10859
  }
10306
10860
  async function generatePDF(page, opts) {
10307
10861
  try {
10308
- const base = join2(getDataDir2(), "pdfs");
10862
+ const base = join3(getDataDir2(), "pdfs");
10309
10863
  const date = new Date().toISOString().split("T")[0];
10310
- const dir = opts?.projectId ? join2(base, opts.projectId, date) : join2(base, date);
10311
- mkdirSync2(dir, { recursive: true });
10864
+ const dir = opts?.projectId ? join3(base, opts.projectId, date) : join3(base, date);
10865
+ mkdirSync3(dir, { recursive: true });
10312
10866
  const timestamp = Date.now();
10313
- const pdfPath = opts?.path ?? join2(dir, `${timestamp}.pdf`);
10867
+ const pdfPath = opts?.path ?? join3(dir, `${timestamp}.pdf`);
10314
10868
  const buffer = await page.pdf({
10315
10869
  path: pdfPath,
10316
10870
  format: opts?.format ?? "A4",
@@ -14909,16 +15463,16 @@ __export(exports_downloads, {
14909
15463
  cleanStaleDownloads: () => cleanStaleDownloads
14910
15464
  });
14911
15465
  import { randomUUID as randomUUID9 } from "crypto";
14912
- import { join as join3, basename, extname } from "path";
14913
- import { mkdirSync as mkdirSync3, existsSync, readdirSync, statSync, unlinkSync, copyFileSync, writeFileSync, readFileSync } from "fs";
14914
- import { homedir as homedir3 } from "os";
15466
+ import { join as join4, basename, extname } from "path";
15467
+ import { mkdirSync as mkdirSync4, existsSync, readdirSync, statSync, unlinkSync, copyFileSync, writeFileSync, readFileSync } from "fs";
15468
+ import { homedir as homedir4 } from "os";
14915
15469
  function getDataDir3() {
14916
- return process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser");
15470
+ return process.env["BROWSER_DATA_DIR"] ?? join4(homedir4(), ".browser");
14917
15471
  }
14918
15472
  function getDownloadsDir(sessionId) {
14919
- const base = join3(getDataDir3(), "downloads");
14920
- const dir = sessionId ? join3(base, sessionId) : base;
14921
- mkdirSync3(dir, { recursive: true });
15473
+ const base = join4(getDataDir3(), "downloads");
15474
+ const dir = sessionId ? join4(base, sessionId) : base;
15475
+ mkdirSync4(dir, { recursive: true });
14922
15476
  return dir;
14923
15477
  }
14924
15478
  function ensureDownloadsDir() {
@@ -14933,7 +15487,7 @@ function saveToDownloads(buffer, filename, opts) {
14933
15487
  const ext = extname(filename) || "";
14934
15488
  const stem = basename(filename, ext);
14935
15489
  const uniqueName = `${stem}-${id.slice(0, 8)}${ext}`;
14936
- const filePath = join3(dir, uniqueName);
15490
+ const filePath = join4(dir, uniqueName);
14937
15491
  writeFileSync(filePath, buffer);
14938
15492
  const meta = {
14939
15493
  id,
@@ -14968,7 +15522,7 @@ function listDownloads(sessionId) {
14968
15522
  for (const entry of entries) {
14969
15523
  if (entry.endsWith(".meta.json"))
14970
15524
  continue;
14971
- const full = join3(d, entry);
15525
+ const full = join4(d, entry);
14972
15526
  const stat = statSync(full);
14973
15527
  if (stat.isDirectory()) {
14974
15528
  scanDir(full);
@@ -15057,9 +15611,9 @@ var exports_gallery_diff = {};
15057
15611
  __export(exports_gallery_diff, {
15058
15612
  diffImages: () => diffImages
15059
15613
  });
15060
- import { join as join4 } from "path";
15061
- import { mkdirSync as mkdirSync4 } from "fs";
15062
- import { homedir as homedir4 } from "os";
15614
+ import { join as join5 } from "path";
15615
+ import { mkdirSync as mkdirSync5 } from "fs";
15616
+ import { homedir as homedir5 } from "os";
15063
15617
  async function diffImages(path1, path2) {
15064
15618
  const img1 = import_sharp2.default(path1);
15065
15619
  const img2 = import_sharp2.default(path2);
@@ -15090,10 +15644,10 @@ async function diffImages(path1, path2) {
15090
15644
  diffBuffer[i + 2] = Math.round(raw1[i + 2] * 0.4);
15091
15645
  }
15092
15646
  }
15093
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join4(homedir4(), ".browser");
15094
- const diffDir = join4(dataDir, "diffs");
15095
- mkdirSync4(diffDir, { recursive: true });
15096
- const diffPath = join4(diffDir, `diff-${Date.now()}.webp`);
15647
+ const dataDir = process.env["BROWSER_DATA_DIR"] ?? join5(homedir5(), ".browser");
15648
+ const diffDir = join5(dataDir, "diffs");
15649
+ mkdirSync5(diffDir, { recursive: true });
15650
+ const diffPath = join5(diffDir, `diff-${Date.now()}.webp`);
15097
15651
  const diffImageBuffer = await import_sharp2.default(diffBuffer, { raw: { width: w, height: h, channels } }).webp({ quality: 85 }).toBuffer();
15098
15652
  await Bun.write(diffPath, diffImageBuffer);
15099
15653
  return {
@@ -15110,9 +15664,9 @@ var init_gallery_diff = __esm(() => {
15110
15664
  });
15111
15665
 
15112
15666
  // src/lib/files-integration.ts
15113
- import { join as join5 } from "path";
15114
- import { mkdirSync as mkdirSync5, copyFileSync as copyFileSync2 } from "fs";
15115
- import { homedir as homedir5 } from "os";
15667
+ import { join as join6 } from "path";
15668
+ import { mkdirSync as mkdirSync6, copyFileSync as copyFileSync2 } from "fs";
15669
+ import { homedir as homedir6 } from "os";
15116
15670
  async function persistFile(localPath, opts) {
15117
15671
  try {
15118
15672
  const mod = await import("@hasna/files");
@@ -15121,12 +15675,12 @@ async function persistFile(localPath, opts) {
15121
15675
  return { id: ref.id, path: ref.path ?? localPath, permanent: true, provider: "open-files" };
15122
15676
  }
15123
15677
  } catch {}
15124
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join5(homedir5(), ".browser");
15678
+ const dataDir = process.env["BROWSER_DATA_DIR"] ?? join6(homedir6(), ".browser");
15125
15679
  const date = new Date().toISOString().split("T")[0];
15126
- const dir = join5(dataDir, "persistent", date);
15127
- mkdirSync5(dir, { recursive: true });
15680
+ const dir = join6(dataDir, "persistent", date);
15681
+ mkdirSync6(dir, { recursive: true });
15128
15682
  const filename = localPath.split("/").pop() ?? "file";
15129
- const targetPath = join5(dir, filename);
15683
+ const targetPath = join6(dir, filename);
15130
15684
  copyFileSync2(localPath, targetPath);
15131
15685
  return {
15132
15686
  id: `local-${Date.now()}`,
@@ -15219,23 +15773,23 @@ async function closeTab(page, index) {
15219
15773
  }
15220
15774
 
15221
15775
  // src/lib/profiles.ts
15222
- import { mkdirSync as mkdirSync6, existsSync as existsSync3, readdirSync as readdirSync2, rmSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
15223
- import { join as join6 } from "path";
15224
- import { homedir as homedir6 } from "os";
15776
+ import { mkdirSync as mkdirSync7, existsSync as existsSync3, readdirSync as readdirSync2, rmSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
15777
+ import { join as join7 } from "path";
15778
+ import { homedir as homedir7 } from "os";
15225
15779
  function getProfilesDir() {
15226
- const dataDir = process.env["BROWSER_DATA_DIR"] ?? join6(homedir6(), ".browser");
15227
- const dir = join6(dataDir, "profiles");
15228
- mkdirSync6(dir, { recursive: true });
15780
+ const dataDir = process.env["BROWSER_DATA_DIR"] ?? join7(homedir7(), ".browser");
15781
+ const dir = join7(dataDir, "profiles");
15782
+ mkdirSync7(dir, { recursive: true });
15229
15783
  return dir;
15230
15784
  }
15231
- function getProfileDir(name) {
15232
- return join6(getProfilesDir(), name);
15785
+ function getProfileDir2(name) {
15786
+ return join7(getProfilesDir(), name);
15233
15787
  }
15234
15788
  async function saveProfile(page, name) {
15235
- const dir = getProfileDir(name);
15236
- mkdirSync6(dir, { recursive: true });
15789
+ const dir = getProfileDir2(name);
15790
+ mkdirSync7(dir, { recursive: true });
15237
15791
  const cookies = await page.context().cookies();
15238
- writeFileSync2(join6(dir, "cookies.json"), JSON.stringify(cookies, null, 2));
15792
+ writeFileSync2(join7(dir, "cookies.json"), JSON.stringify(cookies, null, 2));
15239
15793
  let localStorage2 = {};
15240
15794
  try {
15241
15795
  localStorage2 = await page.evaluate(() => {
@@ -15247,11 +15801,11 @@ async function saveProfile(page, name) {
15247
15801
  return result;
15248
15802
  });
15249
15803
  } catch {}
15250
- writeFileSync2(join6(dir, "storage.json"), JSON.stringify(localStorage2, null, 2));
15804
+ writeFileSync2(join7(dir, "storage.json"), JSON.stringify(localStorage2, null, 2));
15251
15805
  const savedAt = new Date().toISOString();
15252
15806
  const url = page.url();
15253
15807
  const meta = { saved_at: savedAt, url };
15254
- writeFileSync2(join6(dir, "meta.json"), JSON.stringify(meta, null, 2));
15808
+ writeFileSync2(join7(dir, "meta.json"), JSON.stringify(meta, null, 2));
15255
15809
  return {
15256
15810
  name,
15257
15811
  saved_at: savedAt,
@@ -15261,13 +15815,13 @@ async function saveProfile(page, name) {
15261
15815
  };
15262
15816
  }
15263
15817
  function loadProfile(name) {
15264
- const dir = getProfileDir(name);
15818
+ const dir = getProfileDir2(name);
15265
15819
  if (!existsSync3(dir)) {
15266
15820
  throw new Error(`Profile not found: ${name}`);
15267
15821
  }
15268
- const cookiesPath = join6(dir, "cookies.json");
15269
- const storagePath = join6(dir, "storage.json");
15270
- const metaPath2 = join6(dir, "meta.json");
15822
+ const cookiesPath = join7(dir, "cookies.json");
15823
+ const storagePath = join7(dir, "storage.json");
15824
+ const metaPath2 = join7(dir, "meta.json");
15271
15825
  const cookies = existsSync3(cookiesPath) ? JSON.parse(readFileSync2(cookiesPath, "utf8")) : [];
15272
15826
  const localStorage2 = existsSync3(storagePath) ? JSON.parse(readFileSync2(storagePath, "utf8")) : {};
15273
15827
  let savedAt = new Date().toISOString();
@@ -15308,24 +15862,24 @@ function listProfiles() {
15308
15862
  if (!entry.isDirectory())
15309
15863
  continue;
15310
15864
  const name = entry.name;
15311
- const profileDir = join6(dir, name);
15865
+ const profileDir = join7(dir, name);
15312
15866
  let savedAt = "";
15313
15867
  let url;
15314
15868
  let cookieCount = 0;
15315
15869
  let storageKeyCount = 0;
15316
15870
  try {
15317
- const metaPath2 = join6(profileDir, "meta.json");
15871
+ const metaPath2 = join7(profileDir, "meta.json");
15318
15872
  if (existsSync3(metaPath2)) {
15319
15873
  const meta = JSON.parse(readFileSync2(metaPath2, "utf8"));
15320
15874
  savedAt = meta.saved_at ?? "";
15321
15875
  url = meta.url;
15322
15876
  }
15323
- const cookiesPath = join6(profileDir, "cookies.json");
15877
+ const cookiesPath = join7(profileDir, "cookies.json");
15324
15878
  if (existsSync3(cookiesPath)) {
15325
15879
  const cookies = JSON.parse(readFileSync2(cookiesPath, "utf8"));
15326
15880
  cookieCount = Array.isArray(cookies) ? cookies.length : 0;
15327
15881
  }
15328
- const storagePath = join6(profileDir, "storage.json");
15882
+ const storagePath = join7(profileDir, "storage.json");
15329
15883
  if (existsSync3(storagePath)) {
15330
15884
  const storage = JSON.parse(readFileSync2(storagePath, "utf8"));
15331
15885
  storageKeyCount = Object.keys(storage).length;
@@ -15342,7 +15896,7 @@ function listProfiles() {
15342
15896
  return profiles.sort((a, b) => b.saved_at.localeCompare(a.saved_at));
15343
15897
  }
15344
15898
  function deleteProfile(name) {
15345
- const dir = getProfileDir(name);
15899
+ const dir = getProfileDir2(name);
15346
15900
  if (!existsSync3(dir))
15347
15901
  return false;
15348
15902
  try {
@@ -15419,7 +15973,7 @@ var exports_mcp = {};
15419
15973
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
15420
15974
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15421
15975
  import { readFileSync as readFileSync3 } from "fs";
15422
- import { join as join7 } from "path";
15976
+ import { join as join8 } from "path";
15423
15977
  function json(data) {
15424
15978
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
15425
15979
  }
@@ -15455,7 +16009,7 @@ var init_mcp = __esm(async () => {
15455
16009
  init_dialogs();
15456
16010
  init_profiles();
15457
16011
  init_types();
15458
- _pkg = JSON.parse(readFileSync3(join7(import.meta.dir, "../../package.json"), "utf8"));
16012
+ _pkg = JSON.parse(readFileSync3(join8(import.meta.dir, "../../package.json"), "utf8"));
15459
16013
  networkLogCleanup = new Map;
15460
16014
  consoleCaptureCleanup = new Map;
15461
16015
  harCaptures = new Map;
@@ -15464,7 +16018,7 @@ var init_mcp = __esm(async () => {
15464
16018
  version: "0.0.1"
15465
16019
  });
15466
16020
  server.tool("browser_session_create", "Create a new browser session with the specified engine", {
15467
- engine: exports_external.enum(["playwright", "cdp", "lightpanda", "auto"]).optional().default("auto"),
16021
+ engine: exports_external.enum(["playwright", "cdp", "lightpanda", "bun", "auto"]).optional().default("auto"),
15468
16022
  use_case: exports_external.string().optional(),
15469
16023
  project_id: exports_external.string().optional(),
15470
16024
  agent_id: exports_external.string().optional(),
@@ -15519,7 +16073,13 @@ var init_mcp = __esm(async () => {
15519
16073
  }, async ({ session_id, url, timeout, auto_snapshot, auto_thumbnail }) => {
15520
16074
  try {
15521
16075
  const page = getSessionPage(session_id);
15522
- await navigate(page, url, timeout);
16076
+ if (isBunSession(session_id)) {
16077
+ const bunView = getSessionBunView(session_id);
16078
+ await bunView.goto(url, { timeout });
16079
+ await new Promise((r) => setTimeout(r, 500));
16080
+ } else {
16081
+ await navigate(page, url, timeout);
16082
+ }
15523
16083
  const title = await getTitle(page);
15524
16084
  const current_url = await getUrl(page);
15525
16085
  const redirected = current_url !== url && current_url !== url + "/" && url !== current_url.replace(/\/$/, "");
@@ -15560,6 +16120,9 @@ var init_mcp = __esm(async () => {
15560
16120
  result.thumbnail_base64 = ss.base64.length > 50000 ? "" : ss.base64;
15561
16121
  } catch {}
15562
16122
  }
16123
+ if (isBunSession(session_id) && auto_snapshot) {
16124
+ await new Promise((r) => setTimeout(r, 200));
16125
+ }
15563
16126
  if (auto_snapshot) {
15564
16127
  try {
15565
16128
  const snap = await takeSnapshot(page, session_id);
@@ -16067,7 +16630,7 @@ var init_mcp = __esm(async () => {
16067
16630
  max_pages: exports_external.number().optional().default(50),
16068
16631
  same_domain: exports_external.boolean().optional().default(true),
16069
16632
  project_id: exports_external.string().optional(),
16070
- engine: exports_external.enum(["playwright", "cdp", "lightpanda", "auto"]).optional().default("auto")
16633
+ engine: exports_external.enum(["playwright", "cdp", "lightpanda", "bun", "auto"]).optional().default("auto")
16071
16634
  }, async ({ url, max_depth, max_pages, same_domain, project_id, engine }) => {
16072
16635
  try {
16073
16636
  const result = await crawl(url, {
@@ -16835,7 +17398,7 @@ var init_snapshots = __esm(() => {
16835
17398
 
16836
17399
  // src/server/index.ts
16837
17400
  var exports_server = {};
16838
- import { join as join8 } from "path";
17401
+ import { join as join9 } from "path";
16839
17402
  import { existsSync as existsSync4 } from "fs";
16840
17403
  function ok(data, status = 200) {
16841
17404
  return new Response(JSON.stringify(data), {
@@ -17129,13 +17692,13 @@ var init_server = __esm(() => {
17129
17692
  const id = path.split("/")[3];
17130
17693
  return ok({ deleted: deleteDownload(id) });
17131
17694
  }
17132
- const dashboardDist = join8(import.meta.dir, "../../dashboard/dist");
17695
+ const dashboardDist = join9(import.meta.dir, "../../dashboard/dist");
17133
17696
  if (existsSync4(dashboardDist)) {
17134
- const filePath = path === "/" ? join8(dashboardDist, "index.html") : join8(dashboardDist, path);
17697
+ const filePath = path === "/" ? join9(dashboardDist, "index.html") : join9(dashboardDist, path);
17135
17698
  if (existsSync4(filePath)) {
17136
17699
  return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
17137
17700
  }
17138
- return new Response(Bun.file(join8(dashboardDist, "index.html")), { headers: CORS_HEADERS });
17701
+ return new Response(Bun.file(join9(dashboardDist, "index.html")), { headers: CORS_HEADERS });
17139
17702
  }
17140
17703
  if (path === "/" || path === "") {
17141
17704
  return new Response("@hasna/browser REST API running. Dashboard not built.", {
@@ -17178,9 +17741,9 @@ init_recorder();
17178
17741
  init_recordings();
17179
17742
  init_lightpanda();
17180
17743
  import { readFileSync as readFileSync4 } from "fs";
17181
- import { join as join9 } from "path";
17744
+ import { join as join10 } from "path";
17182
17745
  import chalk from "chalk";
17183
- var pkg = JSON.parse(readFileSync4(join9(import.meta.dir, "../../package.json"), "utf8"));
17746
+ var pkg = JSON.parse(readFileSync4(join10(import.meta.dir, "../../package.json"), "utf8"));
17184
17747
  var program2 = new Command;
17185
17748
  program2.name("browser").description("@hasna/browser \u2014 general-purpose browser agent CLI").version(pkg.version);
17186
17749
  program2.command("navigate <url>").description("Navigate to a URL and optionally take a screenshot").option("--engine <engine>", "Browser engine: playwright|cdp|lightpanda|auto", "auto").option("--screenshot", "Take a screenshot after navigation").option("--extract", "Extract page text after navigation").option("--headless", "Run in headless mode (default: true)", true).action(async (url, opts) => {