@hasna/browser 0.4.5 → 0.4.7

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/index.js CHANGED
@@ -3,7 +3,6 @@ var __create = Object.create;
3
3
  var __getProtoOf = Object.getPrototypeOf;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
7
  function __accessProp(key) {
9
8
  return this[key];
@@ -30,23 +29,6 @@ var __toESM = (mod, isNodeMode, target) => {
30
29
  cache.set(mod, to);
31
30
  return to;
32
31
  };
33
- var __toCommonJS = (from) => {
34
- var entry = (__moduleCache ??= new WeakMap).get(from), desc;
35
- if (entry)
36
- return entry;
37
- entry = __defProp({}, "__esModule", { value: true });
38
- if (from && typeof from === "object" || typeof from === "function") {
39
- for (var key of __getOwnPropNames(from))
40
- if (!__hasOwnProp.call(entry, key))
41
- __defProp(entry, key, {
42
- get: __accessProp.bind(from, key),
43
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
44
- });
45
- }
46
- __moduleCache.set(from, entry);
47
- return entry;
48
- };
49
- var __moduleCache;
50
32
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
51
33
  var __returnValue = (v) => v;
52
34
  function __exportSetter(name, newValue) {
@@ -10851,6 +10833,7 @@ function watchPage(page, opts) {
10851
10833
  const changes = [];
10852
10834
  const intervalMs = opts?.intervalMs ?? 500;
10853
10835
  const maxChanges = opts?.maxChanges ?? 50;
10836
+ const sessionId = opts?.sessionId;
10854
10837
  const interval = setInterval(async () => {
10855
10838
  if (changes.length >= maxChanges)
10856
10839
  return;
@@ -10864,7 +10847,7 @@ function watchPage(page, opts) {
10864
10847
  }
10865
10848
  } catch {}
10866
10849
  }, intervalMs);
10867
- activeWatches.set(id, { interval, changes });
10850
+ activeWatches.set(id, { interval, changes, sessionId });
10868
10851
  return {
10869
10852
  id,
10870
10853
  stop: () => {
@@ -10883,10 +10866,12 @@ function stopWatch(watchId) {
10883
10866
  activeWatches.delete(watchId);
10884
10867
  }
10885
10868
  }
10886
- function stopAllWatchesForSession(_sessionId) {
10887
- for (const [id, w] of activeWatches) {
10888
- clearInterval(w.interval);
10889
- activeWatches.delete(id);
10869
+ function stopAllWatchesForSession(sessionId) {
10870
+ for (const [id, w] of [...activeWatches]) {
10871
+ if (!sessionId || w.sessionId === sessionId) {
10872
+ clearInterval(w.interval);
10873
+ activeWatches.delete(id);
10874
+ }
10890
10875
  }
10891
10876
  }
10892
10877
  async function clickRef(page, sessionId, ref, opts) {
@@ -14623,7 +14608,7 @@ var require_operation = __commonJS((exports, module) => {
14623
14608
  // node_modules/@img/colour/color.cjs
14624
14609
  var require_color = __commonJS((exports, module) => {
14625
14610
  var __defProp3 = Object.defineProperty;
14626
- var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
14611
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
14627
14612
  var __getOwnPropNames3 = Object.getOwnPropertyNames;
14628
14613
  var __hasOwnProp3 = Object.prototype.hasOwnProperty;
14629
14614
  var __export3 = (target, all) => {
@@ -14634,16 +14619,16 @@ var require_color = __commonJS((exports, module) => {
14634
14619
  if (from && typeof from === "object" || typeof from === "function") {
14635
14620
  for (let key of __getOwnPropNames3(from))
14636
14621
  if (!__hasOwnProp3.call(to, key) && key !== except)
14637
- __defProp3(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
14622
+ __defProp3(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14638
14623
  }
14639
14624
  return to;
14640
14625
  };
14641
- var __toCommonJS2 = (mod) => __copyProps(__defProp3({}, "__esModule", { value: true }), mod);
14626
+ var __toCommonJS = (mod) => __copyProps(__defProp3({}, "__esModule", { value: true }), mod);
14642
14627
  var index_exports = {};
14643
14628
  __export3(index_exports, {
14644
14629
  default: () => index_default
14645
14630
  });
14646
- module.exports = __toCommonJS2(index_exports);
14631
+ module.exports = __toCommonJS(index_exports);
14647
14632
  var colors = {
14648
14633
  aliceblue: [240, 248, 255],
14649
14634
  antiquewhite: [250, 235, 215],
@@ -18441,6 +18426,207 @@ var THEMES = {
18441
18426
  brightWhite: "#ffffff"
18442
18427
  }
18443
18428
  };
18429
+ async function configureDomRenderer(page, options) {
18430
+ await page.evaluate((opts) => {
18431
+ const runtimeKey = "__takumiTuiDomRenderer";
18432
+ const rootId = "takumi-tui-dom-root";
18433
+ const styleId = "takumi-tui-dom-style";
18434
+ const win = window;
18435
+ const ensureStyle = () => {
18436
+ let style = document.getElementById(styleId);
18437
+ if (!style) {
18438
+ style = document.createElement("style");
18439
+ style.id = styleId;
18440
+ document.head.appendChild(style);
18441
+ }
18442
+ style.textContent = `
18443
+ #${rootId} {
18444
+ position: absolute;
18445
+ inset: 0;
18446
+ overflow: hidden;
18447
+ display: flex;
18448
+ flex-direction: column;
18449
+ white-space: pre;
18450
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
18451
+ line-height: 1.2;
18452
+ background: var(--takumi-tui-bg, #1e1e1e);
18453
+ color: var(--takumi-tui-fg, #d4d4d4);
18454
+ z-index: 4;
18455
+ pointer-events: none;
18456
+ user-select: text;
18457
+ }
18458
+ #${rootId}[data-active="0"] {
18459
+ display: none;
18460
+ }
18461
+ #${rootId} .takumi-tui-dom-row {
18462
+ display: flex;
18463
+ min-height: 1.2em;
18464
+ }
18465
+ #${rootId} .takumi-tui-dom-cell {
18466
+ display: inline-flex;
18467
+ align-items: center;
18468
+ justify-content: center;
18469
+ min-width: 0.62em;
18470
+ height: 1.2em;
18471
+ }
18472
+ #${rootId} .takumi-tui-dom-cell[data-cursor="true"] {
18473
+ outline: 1px solid currentColor;
18474
+ outline-offset: -1px;
18475
+ }
18476
+ body[data-takumi-dom-render="1"] .xterm-rows,
18477
+ body[data-takumi-dom-render="1"] .xterm-text-layer,
18478
+ body[data-takumi-dom-render="1"] .xterm-cursor-layer,
18479
+ body[data-takumi-dom-render="1"] .xterm-selection-layer {
18480
+ opacity: 0 !important;
18481
+ }
18482
+ `;
18483
+ };
18484
+ const ensureRoot = () => {
18485
+ let root = document.getElementById(rootId);
18486
+ if (!root) {
18487
+ root = document.createElement("div");
18488
+ root.id = rootId;
18489
+ root.setAttribute("role", "grid");
18490
+ root.setAttribute("aria-label", "Terminal DOM renderer");
18491
+ const host = document.getElementById("terminal-container") ?? document.querySelector(".xterm") ?? document.body;
18492
+ if (getComputedStyle(host).position === "static") {
18493
+ host.style.position = "relative";
18494
+ }
18495
+ host.appendChild(root);
18496
+ }
18497
+ root.style.setProperty("--takumi-tui-bg", opts.theme === "light" ? "#ffffff" : "#1e1e1e");
18498
+ root.style.setProperty("--takumi-tui-fg", opts.theme === "light" ? "#1e1e1e" : "#d4d4d4");
18499
+ root.style.fontSize = `${opts.fontSize ?? 14}px`;
18500
+ root.dataset.active = opts.active ? "1" : "0";
18501
+ if (opts.active)
18502
+ root.removeAttribute("aria-hidden");
18503
+ else
18504
+ root.setAttribute("aria-hidden", "true");
18505
+ document.body.dataset.takumiDomRender = opts.active ? "1" : "0";
18506
+ return root;
18507
+ };
18508
+ const readCellChars = (line, col) => {
18509
+ try {
18510
+ const cell = typeof line?.getCell === "function" ? line.getCell(col) : null;
18511
+ const chars = typeof cell?.getChars === "function" ? cell.getChars() : "";
18512
+ if (chars)
18513
+ return chars;
18514
+ } catch {}
18515
+ try {
18516
+ const text = typeof line?.translateToString === "function" ? line.translateToString(false, col, col + 1) : "";
18517
+ if (text)
18518
+ return text;
18519
+ } catch {}
18520
+ return " ";
18521
+ };
18522
+ const buildState = (activeOnly) => {
18523
+ const term = win.term ?? win.terminal;
18524
+ if (!term?.buffer?.active) {
18525
+ return {
18526
+ text: "",
18527
+ rows: [],
18528
+ row_count: 0,
18529
+ cols: null,
18530
+ total_rows: 0,
18531
+ buffer_length: null,
18532
+ cursor_row: -1,
18533
+ cursor_col: -1,
18534
+ font_size: null,
18535
+ theme: opts.theme
18536
+ };
18537
+ }
18538
+ const buf = term.buffer.active;
18539
+ const rows = [];
18540
+ const root = ensureRoot();
18541
+ const fragment = document.createDocumentFragment();
18542
+ for (let row = 0;row < buf.length; row++) {
18543
+ const line = buf.getLine(row);
18544
+ if (!line)
18545
+ continue;
18546
+ const rowEl = document.createElement("div");
18547
+ rowEl.className = "takumi-tui-dom-row";
18548
+ rowEl.setAttribute("role", "row");
18549
+ rowEl.dataset.row = String(row);
18550
+ rowEl.setAttribute("aria-rowindex", String(row + 1));
18551
+ let rowText = "";
18552
+ for (let col = 0;col < term.cols; col++) {
18553
+ const char = readCellChars(line, col) || " ";
18554
+ rowText += char;
18555
+ const cellEl = document.createElement("span");
18556
+ cellEl.className = "takumi-tui-dom-cell";
18557
+ cellEl.setAttribute("role", "gridcell");
18558
+ cellEl.dataset.row = String(row);
18559
+ cellEl.dataset.col = String(col);
18560
+ cellEl.setAttribute("aria-colindex", String(col + 1));
18561
+ cellEl.textContent = char;
18562
+ if (buf.cursorY === row && buf.cursorX === col) {
18563
+ cellEl.dataset.cursor = "true";
18564
+ }
18565
+ rowEl.appendChild(cellEl);
18566
+ }
18567
+ rows.push(rowText.replace(/\s+$/g, ""));
18568
+ rowEl.setAttribute("aria-label", rows[rows.length - 1] || " ");
18569
+ fragment.appendChild(rowEl);
18570
+ }
18571
+ root.replaceChildren(fragment);
18572
+ root.setAttribute("aria-rowcount", String(rows.length));
18573
+ root.dataset.method = "dom";
18574
+ return {
18575
+ text: rows.join(`
18576
+ `).trimEnd(),
18577
+ rows,
18578
+ row_count: rows.length,
18579
+ cols: term.cols,
18580
+ total_rows: term.rows,
18581
+ buffer_length: buf.length,
18582
+ cursor_row: buf.cursorY,
18583
+ cursor_col: buf.cursorX,
18584
+ font_size: term.options?.fontSize ?? null,
18585
+ theme: term.options?.theme?.background === "#ffffff" ? "light" : "dark"
18586
+ };
18587
+ };
18588
+ ensureStyle();
18589
+ ensureRoot();
18590
+ if (!win[runtimeKey]) {
18591
+ win[runtimeKey] = {
18592
+ sync: () => buildState(false),
18593
+ activate: (active) => {
18594
+ const root = ensureRoot();
18595
+ root.dataset.active = active ? "1" : "0";
18596
+ if (active)
18597
+ root.removeAttribute("aria-hidden");
18598
+ else
18599
+ root.setAttribute("aria-hidden", "true");
18600
+ document.body.dataset.takumiDomRender = active ? "1" : "0";
18601
+ }
18602
+ };
18603
+ const intervalId = window.setInterval(() => {
18604
+ try {
18605
+ win[runtimeKey]?.sync?.();
18606
+ } catch {}
18607
+ }, 50);
18608
+ win[runtimeKey].intervalId = intervalId;
18609
+ }
18610
+ win[runtimeKey].activate(opts.active);
18611
+ win[runtimeKey].sync();
18612
+ }, options);
18613
+ }
18614
+ async function destroyDomRenderer(page) {
18615
+ await page.evaluate(() => {
18616
+ const runtimeKey = "__takumiTuiDomRenderer";
18617
+ const win = window;
18618
+ if (win[runtimeKey]?.intervalId) {
18619
+ clearInterval(win[runtimeKey].intervalId);
18620
+ }
18621
+ delete win[runtimeKey];
18622
+ document.getElementById("takumi-tui-dom-root")?.remove();
18623
+ document.getElementById("takumi-tui-dom-style")?.remove();
18624
+ delete document.body.dataset.takumiDomRender;
18625
+ }).catch(() => {});
18626
+ }
18627
+ function isDomMethod(method) {
18628
+ return method === "dom";
18629
+ }
18444
18630
  function isTuiAvailable() {
18445
18631
  try {
18446
18632
  execSync2("which ttyd", { stdio: "ignore" });
@@ -18453,7 +18639,7 @@ async function findAvailablePort(startPort) {
18453
18639
  let port = startPort;
18454
18640
  for (let i = 0;i < 100; i++) {
18455
18641
  try {
18456
- const resp = await fetch(`http://localhost:${port}`);
18642
+ await fetch(`http://localhost:${port}`);
18457
18643
  port++;
18458
18644
  } catch {
18459
18645
  return port;
@@ -18479,24 +18665,16 @@ async function launchTui(command, options = {}) {
18479
18665
  }
18480
18666
  const port = await findAvailablePort(nextPort);
18481
18667
  nextPort = port + 1;
18482
- const ttydProcess = spawn2("ttyd", ["--writable", "--port", String(port), "/bin/sh", "-c", command], {
18483
- stdio: "ignore",
18484
- detached: false
18485
- });
18668
+ const ttydProcess = spawn2("ttyd", ["--writable", "--port", String(port), "/bin/sh", "-c", command], { stdio: "ignore", detached: false });
18486
18669
  ttydProcess.on("error", (err) => {
18487
18670
  console.error(`[tui] ttyd process error: ${err.message}`);
18488
18671
  });
18489
18672
  try {
18490
18673
  await waitForTtyd(port);
18491
18674
  const viewport = options.viewport ?? { width: 1280, height: 720 };
18492
- const browser = await launchPlaywright({
18493
- headless: options.headless ?? true,
18494
- viewport
18495
- });
18675
+ const browser = await launchPlaywright({ headless: options.headless ?? true, viewport });
18496
18676
  const page = await getPage(browser, { viewport });
18497
- await page.goto(`http://localhost:${port}`, {
18498
- waitUntil: "domcontentloaded"
18499
- });
18677
+ await page.goto(`http://localhost:${port}`, { waitUntil: "domcontentloaded" });
18500
18678
  await page.waitForSelector(".xterm-screen", { timeout: 1e4 });
18501
18679
  let resolvedTheme = "dark";
18502
18680
  const requestedTheme = options.theme ?? "system";
@@ -18515,16 +18693,15 @@ async function launchTui(command, options = {}) {
18515
18693
  const themeColors = THEMES[resolvedTheme];
18516
18694
  await page.evaluate((theme) => {
18517
18695
  const term = window.term ?? window.terminal;
18518
- if (term?.options) {
18696
+ if (term?.options)
18519
18697
  term.options.theme = theme;
18520
- }
18521
18698
  document.body.style.backgroundColor = theme.background;
18522
18699
  const container = document.getElementById("terminal-container");
18523
18700
  if (container)
18524
18701
  container.style.backgroundColor = theme.background;
18525
- const viewport2 = document.querySelector(".xterm-viewport");
18526
- if (viewport2)
18527
- viewport2.style.backgroundColor = theme.background;
18702
+ const vp = document.querySelector(".xterm-viewport");
18703
+ if (vp)
18704
+ vp.style.backgroundColor = theme.background;
18528
18705
  }, themeColors);
18529
18706
  if (options.fontSize) {
18530
18707
  await page.evaluate((size) => {
@@ -18533,13 +18710,31 @@ async function launchTui(command, options = {}) {
18533
18710
  term.options.fontSize = size;
18534
18711
  }, options.fontSize);
18535
18712
  }
18536
- return { ttydProcess, port, browser, page, theme: resolvedTheme };
18713
+ const method = options.method ?? "buffer";
18714
+ await configureDomRenderer(page, {
18715
+ active: isDomMethod(method),
18716
+ theme: resolvedTheme,
18717
+ fontSize: options.fontSize
18718
+ });
18719
+ return {
18720
+ ttydProcess,
18721
+ port,
18722
+ browser,
18723
+ page,
18724
+ theme: resolvedTheme,
18725
+ method,
18726
+ lastHealthCheck: Date.now(),
18727
+ reconnectCount: 0
18728
+ };
18537
18729
  } catch (err) {
18538
- ttydProcess.kill();
18730
+ try {
18731
+ ttydProcess.kill("SIGTERM");
18732
+ } catch {}
18539
18733
  throw err;
18540
18734
  }
18541
18735
  }
18542
18736
  async function closeTui(session) {
18737
+ await destroyDomRenderer(session.page);
18543
18738
  try {
18544
18739
  await session.page.close();
18545
18740
  } catch {}
@@ -18549,6 +18744,9 @@ async function closeTui(session) {
18549
18744
  try {
18550
18745
  session.ttydProcess.kill("SIGTERM");
18551
18746
  } catch {}
18747
+ try {
18748
+ session.ttydProcess.kill("SIGKILL");
18749
+ } catch {}
18552
18750
  }
18553
18751
 
18554
18752
  // src/engines/selector.ts
@@ -18816,19 +19014,13 @@ Object.defineProperty(navigator, 'plugins', {
18816
19014
  { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '', length: 1 },
18817
19015
  { name: 'Native Client', filename: 'internal-nacl-plugin', description: '', length: 2 },
18818
19016
  ];
18819
- // Mimic PluginArray interface
18820
- const pluginArray = Object.create(PluginArray.prototype);
19017
+ // Mimic PluginArray interface \u2014 guard against removed prototypes
19018
+ const pluginArray = {};
18821
19019
  plugins.forEach((p, i) => {
18822
- const plugin = Object.create(Plugin.prototype);
18823
- Object.defineProperties(plugin, {
18824
- name: { value: p.name, enumerable: true },
18825
- filename: { value: p.filename, enumerable: true },
18826
- description: { value: p.description, enumerable: true },
18827
- length: { value: p.length, enumerable: true },
18828
- });
19020
+ const plugin = { ...p, item: () => null };
18829
19021
  pluginArray[i] = plugin;
18830
19022
  });
18831
- Object.defineProperty(pluginArray, 'length', { value: plugins.length });
19023
+ pluginArray.length = plugins.length;
18832
19024
  pluginArray.item = (i) => pluginArray[i] || null;
18833
19025
  pluginArray.namedItem = (name) => plugins.find(p => p.name === name) ? pluginArray[plugins.findIndex(p => p.name === name)] : null;
18834
19026
  pluginArray.refresh = () => {};
@@ -18883,14 +19075,16 @@ if (ttlInterval.unref)
18883
19075
  var DB_PRUNE_INTERVAL_MS = 30 * 60000;
18884
19076
  var DB_RETENTION_HOURS = 24;
18885
19077
  var dbPruneInterval = setInterval(() => {
18886
- try {
18887
- const { getDatabase: getDatabase2 } = (init_schema(), __toCommonJS(exports_schema));
18888
- const db = getDatabase2();
18889
- const cutoff = new Date(Date.now() - DB_RETENTION_HOURS * 3600000).toISOString();
18890
- db.prepare("DELETE FROM network_log WHERE session_id IN (SELECT id FROM sessions WHERE status != 'active') AND timestamp < ?").run(cutoff);
18891
- db.prepare("DELETE FROM console_log WHERE session_id IN (SELECT id FROM sessions WHERE status != 'active') AND timestamp < ?").run(cutoff);
18892
- db.prepare("DELETE FROM snapshots WHERE session_id IN (SELECT id FROM sessions WHERE status != 'active') AND timestamp < ?").run(cutoff);
18893
- } catch {}
19078
+ (async () => {
19079
+ try {
19080
+ const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
19081
+ const db = getDatabase2();
19082
+ const cutoff = new Date(Date.now() - DB_RETENTION_HOURS * 3600000).toISOString();
19083
+ db.prepare("DELETE FROM network_log WHERE session_id IN (SELECT id FROM sessions WHERE status != 'active') AND timestamp < ?").run(cutoff);
19084
+ db.prepare("DELETE FROM console_log WHERE session_id IN (SELECT id FROM sessions WHERE status != 'active') AND timestamp < ?").run(cutoff);
19085
+ db.prepare("DELETE FROM snapshots WHERE session_id IN (SELECT id FROM sessions WHERE status != 'active') AND timestamp < ?").run(cutoff);
19086
+ } catch {}
19087
+ })();
18894
19088
  }, DB_PRUNE_INTERVAL_MS);
18895
19089
  if (dbPruneInterval.unref)
18896
19090
  dbPruneInterval.unref();
@@ -18926,7 +19120,7 @@ async function createSession2(opts = {}) {
18926
19120
  try {
18927
19121
  cleanups2.push(setupDialogHandler(page2, session2.id));
18928
19122
  } catch {}
18929
- handles.set(session2.id, { browser: cdpBrowser, bunView: null, tuiSession: null, page: page2, engine: "cdp", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
19123
+ handles.set(session2.id, { browser: cdpBrowser, bunView: null, tuiSession: null, page: page2, engine: "cdp", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false, startUrl: opts.startUrl ?? "" });
18930
19124
  return { session: session2, page: page2 };
18931
19125
  }
18932
19126
  const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
@@ -18940,13 +19134,29 @@ async function createSession2(opts = {}) {
18940
19134
  browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
18941
19135
  page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
18942
19136
  } else {
18943
- bunView = new BunWebViewSession({
19137
+ const testView = new BunWebViewSession({
18944
19138
  width: opts.viewport?.width ?? 1280,
18945
19139
  height: opts.viewport?.height ?? 720,
18946
19140
  profile: opts.name ?? undefined
18947
19141
  });
18948
- if (opts.stealth) {}
18949
- page = createBunProxy(bunView);
19142
+ let bunWorks = true;
19143
+ try {
19144
+ await testView.goto("data:text/html,<html></html>");
19145
+ } catch {
19146
+ bunWorks = false;
19147
+ try {
19148
+ await testView.close();
19149
+ } catch {}
19150
+ }
19151
+ if (!bunWorks) {
19152
+ console.warn("[browser] Bun.WebView exists but Chrome not available \u2014 falling back to playwright");
19153
+ browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
19154
+ page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
19155
+ } else {
19156
+ bunView = testView;
19157
+ if (opts.stealth) {}
19158
+ page = createBunProxy(bunView);
19159
+ }
18950
19160
  }
18951
19161
  } else if (resolvedEngine === "lightpanda") {
18952
19162
  browser = await connectLightpanda();
@@ -18958,7 +19168,8 @@ async function createSession2(opts = {}) {
18958
19168
  headless: opts.headless ?? true,
18959
19169
  viewport: opts.viewport,
18960
19170
  theme: opts.tuiTheme ?? "system",
18961
- fontSize: opts.tuiFontSize
19171
+ fontSize: opts.tuiFontSize,
19172
+ method: opts.tuiMethod ?? "buffer"
18962
19173
  });
18963
19174
  browser = tuiSess.browser;
18964
19175
  page = tuiSess.page;
@@ -18984,7 +19195,7 @@ async function createSession2(opts = {}) {
18984
19195
  try {
18985
19196
  cleanups2.push(setupDialogHandler(page, session2.id));
18986
19197
  } catch {}
18987
- handles.set(session2.id, { browser, bunView: null, tuiSession: tuiSess, page, engine: "tui", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
19198
+ handles.set(session2.id, { browser, bunView: null, tuiSession: tuiSess, page, engine: "tui", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false, startUrl: opts.startUrl ?? "bash" });
18988
19199
  return { session: session2, page };
18989
19200
  } else {
18990
19201
  browser = await pool.acquire(opts.headless ?? true);
@@ -19056,7 +19267,7 @@ async function createSession2(opts = {}) {
19056
19267
  } catch {}
19057
19268
  }
19058
19269
  }
19059
- handles.set(session.id, { browser, bunView, tuiSession: null, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
19270
+ handles.set(session.id, { browser, bunView, tuiSession: null, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false, startUrl: opts.startUrl ?? "" });
19060
19271
  if (opts.startUrl) {
19061
19272
  try {
19062
19273
  if (bunView) {
@@ -19108,6 +19319,23 @@ function getSessionEngine(sessionId) {
19108
19319
  function hasActiveHandle(sessionId) {
19109
19320
  return handles.has(sessionId);
19110
19321
  }
19322
+ function getSessionTuiSession(sessionId) {
19323
+ return handles.get(sessionId)?.tuiSession ?? null;
19324
+ }
19325
+ function setSessionTui(sessionId, tuiSess) {
19326
+ const handle = handles.get(sessionId);
19327
+ if (!handle)
19328
+ throw new SessionNotFoundError(sessionId);
19329
+ handle.tuiSession = tuiSess;
19330
+ handle.page = tuiSess.page;
19331
+ if (tuiSess.browser !== handle.browser) {
19332
+ handle.browser = tuiSess.browser;
19333
+ }
19334
+ handle.lastActivity = Date.now();
19335
+ }
19336
+ function getSessionCommand(sessionId) {
19337
+ return handles.get(sessionId)?.startUrl ?? "bash";
19338
+ }
19111
19339
  function setSessionPage(sessionId, page) {
19112
19340
  const handle = handles.get(sessionId);
19113
19341
  if (!handle)
@@ -19116,38 +19344,43 @@ function setSessionPage(sessionId, page) {
19116
19344
  }
19117
19345
  async function closeSession2(sessionId) {
19118
19346
  const handle = handles.get(sessionId);
19119
- if (handle) {
19120
- for (const cleanup of handle.cleanups) {
19121
- try {
19122
- cleanup();
19123
- } catch {}
19124
- }
19125
- if (handle.bunView) {
19126
- try {
19127
- await handle.bunView.close();
19128
- } catch {}
19129
- } else if (handle.tuiSession) {} else {
19130
- try {
19131
- await handle.page.context().close();
19132
- } catch {}
19133
- if (handle.browser)
19134
- pool.release(handle.browser);
19347
+ try {
19348
+ if (handle) {
19349
+ for (const cleanup of handle.cleanups) {
19350
+ try {
19351
+ cleanup();
19352
+ } catch {}
19353
+ }
19354
+ if (handle.bunView) {
19355
+ try {
19356
+ await handle.bunView.close();
19357
+ } catch {}
19358
+ } else if (handle.tuiSession) {} else {
19359
+ try {
19360
+ await handle.page.context().close();
19361
+ } catch {}
19362
+ try {
19363
+ if (handle.browser)
19364
+ pool.release(handle.browser);
19365
+ } catch {}
19366
+ }
19135
19367
  }
19368
+ try {
19369
+ const { clearLastSnapshot: clearLastSnapshot2, clearSessionRefs: clearSessionRefs2 } = await Promise.resolve().then(() => (init_snapshot(), exports_snapshot));
19370
+ clearLastSnapshot2(sessionId);
19371
+ clearSessionRefs2(sessionId);
19372
+ } catch {}
19373
+ try {
19374
+ const { stopAllWatchesForSession: stopAllWatchesForSession2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
19375
+ stopAllWatchesForSession2(sessionId);
19376
+ } catch {}
19377
+ try {
19378
+ const { clearDialogs: clearDialogs2 } = await Promise.resolve().then(() => (init_dialogs(), exports_dialogs));
19379
+ clearDialogs2(sessionId);
19380
+ } catch {}
19381
+ } finally {
19136
19382
  handles.delete(sessionId);
19137
19383
  }
19138
- try {
19139
- const { clearLastSnapshot: clearLastSnapshot2, clearSessionRefs: clearSessionRefs2 } = await Promise.resolve().then(() => (init_snapshot(), exports_snapshot));
19140
- clearLastSnapshot2(sessionId);
19141
- clearSessionRefs2(sessionId);
19142
- } catch {}
19143
- try {
19144
- const { stopAllWatchesForSession: stopAllWatchesForSession2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
19145
- stopAllWatchesForSession2(sessionId);
19146
- } catch {}
19147
- try {
19148
- const { clearDialogs: clearDialogs2 } = await Promise.resolve().then(() => (init_dialogs(), exports_dialogs));
19149
- clearDialogs2(sessionId);
19150
- } catch {}
19151
19384
  return closeSession(sessionId);
19152
19385
  }
19153
19386
  function listSessions2(filter) {
@@ -19479,6 +19712,12 @@ import { mkdirSync as mkdirSync7 } from "fs";
19479
19712
  // src/db/gallery.ts
19480
19713
  init_schema();
19481
19714
  import { randomUUID as randomUUID10 } from "crypto";
19715
+ function validateDataPath(filePath) {
19716
+ if (filePath.includes("..")) {
19717
+ throw new Error(`File path must not contain '..': ${filePath}`);
19718
+ }
19719
+ return filePath;
19720
+ }
19482
19721
  function deserialize3(row) {
19483
19722
  return {
19484
19723
  id: row.id,
@@ -19503,6 +19742,9 @@ function deserialize3(row) {
19503
19742
  function createEntry(data) {
19504
19743
  const db = getDatabase();
19505
19744
  const id = randomUUID10();
19745
+ validateDataPath(data.path);
19746
+ if (data.thumbnail_path)
19747
+ validateDataPath(data.thumbnail_path);
19506
19748
  db.prepare(`
19507
19749
  INSERT INTO gallery_entries
19508
19750
  (id, session_id, project_id, url, title, path, thumbnail_path, format,
@@ -20017,6 +20259,7 @@ export {
20017
20259
  startRecording,
20018
20260
  startHAR,
20019
20261
  startCoverage,
20262
+ setSessionTui,
20020
20263
  setSessionStorage,
20021
20264
  setSessionPage,
20022
20265
  setLocalStorage,
@@ -20065,9 +20308,11 @@ export {
20065
20308
  getTimingEntries,
20066
20309
  getText,
20067
20310
  getSnapshot,
20311
+ getSessionTuiSession,
20068
20312
  getSessionStorage,
20069
20313
  getSessionPage,
20070
20314
  getSessionEngine,
20315
+ getSessionCommand,
20071
20316
  getSessionByName2 as getSessionByName,
20072
20317
  getSessionBunView,
20073
20318
  getSessionBrowser,
@@ -73,10 +73,11 @@ export declare function watchPage(page: Page, opts?: {
73
73
  selector?: string;
74
74
  intervalMs?: number;
75
75
  maxChanges?: number;
76
+ sessionId?: string;
76
77
  }): WatchHandle;
77
78
  export declare function getWatchChanges(watchId: string): string[];
78
79
  export declare function stopWatch(watchId: string): void;
79
- export declare function stopAllWatchesForSession(_sessionId?: string): void;
80
+ export declare function stopAllWatchesForSession(sessionId?: string): void;
80
81
  export declare function clickRef(page: Page, sessionId: string, ref: string, opts?: {
81
82
  timeout?: number;
82
83
  }): Promise<void>;