@hasna/browser 0.3.8 → 0.4.1

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
@@ -6,60 +6,39 @@ var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- function __accessProp(key) {
10
- return this[key];
11
- }
12
- var __toESMCache_node;
13
- var __toESMCache_esm;
14
9
  var __toESM = (mod, isNodeMode, target) => {
15
- var canCache = mod != null && typeof mod === "object";
16
- if (canCache) {
17
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
18
- var cached = cache.get(mod);
19
- if (cached)
20
- return cached;
21
- }
22
10
  target = mod != null ? __create(__getProtoOf(mod)) : {};
23
11
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
24
12
  for (let key of __getOwnPropNames(mod))
25
13
  if (!__hasOwnProp.call(to, key))
26
14
  __defProp(to, key, {
27
- get: __accessProp.bind(mod, key),
15
+ get: () => mod[key],
28
16
  enumerable: true
29
17
  });
30
- if (canCache)
31
- cache.set(mod, to);
32
18
  return to;
33
19
  };
20
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
34
21
  var __toCommonJS = (from) => {
35
- var entry = (__moduleCache ??= new WeakMap).get(from), desc;
22
+ var entry = __moduleCache.get(from), desc;
36
23
  if (entry)
37
24
  return entry;
38
25
  entry = __defProp({}, "__esModule", { value: true });
39
- if (from && typeof from === "object" || typeof from === "function") {
40
- for (var key of __getOwnPropNames(from))
41
- if (!__hasOwnProp.call(entry, key))
42
- __defProp(entry, key, {
43
- get: __accessProp.bind(from, key),
44
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
45
- });
46
- }
26
+ if (from && typeof from === "object" || typeof from === "function")
27
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
28
+ get: () => from[key],
29
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
+ }));
47
31
  __moduleCache.set(from, entry);
48
32
  return entry;
49
33
  };
50
- var __moduleCache;
51
34
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
52
- var __returnValue = (v) => v;
53
- function __exportSetter(name, newValue) {
54
- this[name] = __returnValue.bind(null, newValue);
55
- }
56
35
  var __export = (target, all) => {
57
36
  for (var name in all)
58
37
  __defProp(target, name, {
59
38
  get: all[name],
60
39
  enumerable: true,
61
40
  configurable: true,
62
- set: __exportSetter.bind(all, name)
41
+ set: (newValue) => all[name] = () => newValue
63
42
  });
64
43
  };
65
44
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -2177,11 +2156,11 @@ import { homedir as homedir4 } from "os";
2177
2156
  import { join as join4 } from "path";
2178
2157
  import { join as join6, dirname } from "path";
2179
2158
  import { homedir as homedir5, platform } from "os";
2180
- function __accessProp2(key) {
2159
+ function __accessProp(key) {
2181
2160
  return this[key];
2182
2161
  }
2183
- function __exportSetter2(name, newValue) {
2184
- this[name] = __returnValue2.bind(null, newValue);
2162
+ function __exportSetter(name, newValue) {
2163
+ this[name] = __returnValue.bind(null, newValue);
2185
2164
  }
2186
2165
  function translateSql(sql, dialect) {
2187
2166
  if (dialect === "sqlite")
@@ -3211,10 +3190,10 @@ class SyncProgressTracker {
3211
3190
  }
3212
3191
  }
3213
3192
  }
3214
- var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __toESMCache_node2, __toESMCache_esm2, __toESM2 = (mod, isNodeMode, target) => {
3193
+ var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __toESMCache_node, __toESMCache_esm, __toESM2 = (mod, isNodeMode, target) => {
3215
3194
  var canCache = mod != null && typeof mod === "object";
3216
3195
  if (canCache) {
3217
- var cache = isNodeMode ? __toESMCache_node2 ??= new WeakMap : __toESMCache_esm2 ??= new WeakMap;
3196
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
3218
3197
  var cached = cache.get(mod);
3219
3198
  if (cached)
3220
3199
  return cached;
@@ -3224,19 +3203,19 @@ var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __t
3224
3203
  for (let key of __getOwnPropNames2(mod))
3225
3204
  if (!__hasOwnProp2.call(to, key))
3226
3205
  __defProp2(to, key, {
3227
- get: __accessProp2.bind(mod, key),
3206
+ get: __accessProp.bind(mod, key),
3228
3207
  enumerable: true
3229
3208
  });
3230
3209
  if (canCache)
3231
3210
  cache.set(mod, to);
3232
3211
  return to;
3233
- }, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue2 = (v) => v, __export2 = (target, all) => {
3212
+ }, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue = (v) => v, __export2 = (target, all) => {
3234
3213
  for (var name in all)
3235
3214
  __defProp2(target, name, {
3236
3215
  get: all[name],
3237
3216
  enumerable: true,
3238
3217
  configurable: true,
3239
- set: __exportSetter2.bind(all, name)
3218
+ set: __exportSetter.bind(all, name)
3240
3219
  });
3241
3220
  }, __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), __require2, require_postgres_array, require_arrayParser, require_postgres_date, require_mutable, require_postgres_interval, require_postgres_bytea, require_textParsers, require_pg_int8, require_binaryParsers, require_builtins, require_pg_types, require_defaults, require_utils, require_utils_legacy, require_utils_webcrypto, require_utils2, require_cert_signatures, require_sasl, require_type_overrides, require_pg_connection_string, require_connection_parameters, require_result, require_query, require_messages, require_buffer_writer, require_serializer, require_buffer_reader, require_parser, require_dist, require_empty, require_stream, require_connection, require_split2, require_helper, require_lib, require_client, require_pg_pool, require_query2, require_client2, require_lib2, import_lib, Client, Pool, Connection, types, Query, DatabaseError, escapeIdentifier, escapeLiteral, Result, TypeOverrides, defaults, esm_default, init_esm, init_adapter, util, objectUtil, ZodParsedType, getParsedType = (data) => {
3242
3221
  const t = typeof data;
@@ -12653,6 +12632,89 @@ var init_bun_webview = __esm(() => {
12653
12632
  };
12654
12633
  });
12655
12634
 
12635
+ // src/engines/tui.ts
12636
+ import { execSync as execSync2, spawn as spawn2 } from "child_process";
12637
+ function isTuiAvailable() {
12638
+ try {
12639
+ execSync2("which ttyd", { stdio: "ignore" });
12640
+ return true;
12641
+ } catch {
12642
+ return false;
12643
+ }
12644
+ }
12645
+ async function findAvailablePort(startPort) {
12646
+ let port = startPort;
12647
+ for (let i = 0;i < 100; i++) {
12648
+ try {
12649
+ const resp = await fetch(`http://localhost:${port}`);
12650
+ port++;
12651
+ } catch {
12652
+ return port;
12653
+ }
12654
+ }
12655
+ throw new BrowserError("No available port found for ttyd", "TUI_PORT_EXHAUSTED");
12656
+ }
12657
+ async function waitForTtyd(port, timeoutMs = 1e4) {
12658
+ const start = Date.now();
12659
+ while (Date.now() - start < timeoutMs) {
12660
+ try {
12661
+ const resp = await fetch(`http://localhost:${port}`);
12662
+ if (resp.ok || resp.status === 200)
12663
+ return;
12664
+ } catch {}
12665
+ await new Promise((r) => setTimeout(r, 150));
12666
+ }
12667
+ throw new BrowserError(`ttyd did not start within ${timeoutMs}ms`, "TUI_TIMEOUT");
12668
+ }
12669
+ async function launchTui(command, options = {}) {
12670
+ if (!isTuiAvailable()) {
12671
+ throw new BrowserError("ttyd not found \u2014 install with: brew install ttyd", "TUI_NOT_AVAILABLE");
12672
+ }
12673
+ const port = await findAvailablePort(nextPort);
12674
+ nextPort = port + 1;
12675
+ const ttydProcess = spawn2("ttyd", ["--writable", "--port", String(port), "/bin/sh", "-c", command], {
12676
+ stdio: "ignore",
12677
+ detached: false
12678
+ });
12679
+ ttydProcess.on("error", (err) => {
12680
+ console.error(`[tui] ttyd process error: ${err.message}`);
12681
+ });
12682
+ try {
12683
+ await waitForTtyd(port);
12684
+ const viewport = options.viewport ?? { width: 1280, height: 720 };
12685
+ const browser = await launchPlaywright({
12686
+ headless: options.headless ?? true,
12687
+ viewport
12688
+ });
12689
+ const page = await getPage(browser, { viewport });
12690
+ await page.goto(`http://localhost:${port}`, {
12691
+ waitUntil: "domcontentloaded"
12692
+ });
12693
+ await page.waitForSelector(".xterm-screen", { timeout: 1e4 });
12694
+ return { ttydProcess, port, browser, page };
12695
+ } catch (err) {
12696
+ ttydProcess.kill();
12697
+ throw err;
12698
+ }
12699
+ }
12700
+ async function closeTui(session) {
12701
+ try {
12702
+ await session.page.close();
12703
+ } catch {}
12704
+ try {
12705
+ await session.browser.close();
12706
+ } catch {}
12707
+ try {
12708
+ session.ttydProcess.kill("SIGTERM");
12709
+ } catch {}
12710
+ }
12711
+ var DEFAULT_TTYD_PORT_START = 7780, nextPort;
12712
+ var init_tui = __esm(() => {
12713
+ init_types();
12714
+ init_playwright();
12715
+ nextPort = DEFAULT_TTYD_PORT_START;
12716
+ });
12717
+
12656
12718
  // src/engines/selector.ts
12657
12719
  function selectEngine(useCase, explicit) {
12658
12720
  if (explicit && explicit !== "auto")
@@ -12676,6 +12738,7 @@ var init_selector = __esm(() => {
12676
12738
  init_types();
12677
12739
  init_lightpanda();
12678
12740
  init_bun_webview();
12741
+ init_tui();
12679
12742
  ENGINE_MAP = {
12680
12743
  ["scrape" /* SCRAPE */]: "bun",
12681
12744
  ["extract_links" /* EXTRACT_LINKS */]: "bun",
@@ -12686,6 +12749,7 @@ var init_selector = __esm(() => {
12686
12749
  ["auth_flow" /* AUTH_FLOW */]: "playwright",
12687
12750
  ["multi_tab" /* MULTI_TAB */]: "playwright",
12688
12751
  ["record_replay" /* RECORD_REPLAY */]: "playwright",
12752
+ ["terminal_test" /* TERMINAL_TEST */]: "tui",
12689
12753
  ["network_monitor" /* NETWORK_MONITOR */]: "cdp",
12690
12754
  ["har_capture" /* HAR_CAPTURE */]: "cdp",
12691
12755
  ["perf_profile" /* PERF_PROFILE */]: "cdp",
@@ -14055,7 +14119,7 @@ async function createSession2(opts = {}) {
14055
14119
  try {
14056
14120
  cleanups2.push(setupDialogHandler(page2, session2.id));
14057
14121
  } catch {}
14058
- handles.set(session2.id, { browser: cdpBrowser, bunView: null, page: page2, engine: "cdp", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
14122
+ 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 });
14059
14123
  return { session: session2, page: page2 };
14060
14124
  }
14061
14125
  const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
@@ -14081,6 +14145,38 @@ async function createSession2(opts = {}) {
14081
14145
  browser = await connectLightpanda();
14082
14146
  const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
14083
14147
  page = await context.newPage();
14148
+ } else if (resolvedEngine === "tui") {
14149
+ const command = opts.startUrl ?? "bash";
14150
+ const tuiSess = await launchTui(command, {
14151
+ headless: opts.headless ?? true,
14152
+ viewport: opts.viewport
14153
+ });
14154
+ browser = tuiSess.browser;
14155
+ page = tuiSess.page;
14156
+ const session2 = createSession({
14157
+ engine: "tui",
14158
+ projectId: opts.projectId,
14159
+ agentId: opts.agentId,
14160
+ startUrl: opts.startUrl,
14161
+ name: opts.name ?? "tui"
14162
+ });
14163
+ const cleanups2 = [];
14164
+ cleanups2.push(() => closeTui(tuiSess));
14165
+ if (opts.captureNetwork !== false) {
14166
+ try {
14167
+ cleanups2.push(enableNetworkLogging(page, session2.id));
14168
+ } catch {}
14169
+ }
14170
+ if (opts.captureConsole !== false) {
14171
+ try {
14172
+ cleanups2.push(enableConsoleCapture(page, session2.id));
14173
+ } catch {}
14174
+ }
14175
+ try {
14176
+ cleanups2.push(setupDialogHandler(page, session2.id));
14177
+ } catch {}
14178
+ 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 });
14179
+ return { session: session2, page };
14084
14180
  } else {
14085
14181
  browser = await pool.acquire(opts.headless ?? true);
14086
14182
  if (opts.storageState) {
@@ -14151,7 +14247,7 @@ async function createSession2(opts = {}) {
14151
14247
  } catch {}
14152
14248
  }
14153
14249
  }
14154
- handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
14250
+ 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 });
14155
14251
  if (opts.startUrl) {
14156
14252
  try {
14157
14253
  if (bunView) {
@@ -14221,7 +14317,7 @@ async function closeSession2(sessionId) {
14221
14317
  try {
14222
14318
  await handle.bunView.close();
14223
14319
  } catch {}
14224
- } else {
14320
+ } else if (handle.tuiSession) {} else {
14225
14321
  try {
14226
14322
  await handle.page.context().close();
14227
14323
  } catch {}
@@ -14321,6 +14417,7 @@ var init_session = __esm(() => {
14321
14417
  init_lightpanda();
14322
14418
  init_bun_webview();
14323
14419
  init_selector();
14420
+ init_tui();
14324
14421
  init_network();
14325
14422
  init_console();
14326
14423
  init_stealth();
@@ -27481,7 +27578,7 @@ var init_helpers = __esm(() => {
27481
27578
  // src/mcp/sessions.ts
27482
27579
  function register4(server) {
27483
27580
  server.tool("browser_session_create", "Create a new browser session. If agent_id is set and already has an active session, returns the existing one (use force_new to override). If session_id is omitted on other tools, the single active session is auto-selected. Use cdp_url to attach to an already-running Chrome instance.", {
27484
- engine: exports_external2.enum(["playwright", "cdp", "lightpanda", "bun", "auto"]).optional().default("auto"),
27581
+ engine: exports_external2.enum(["playwright", "cdp", "lightpanda", "bun", "tui", "auto"]).optional().default("auto"),
27485
27582
  use_case: exports_external2.string().optional(),
27486
27583
  project_id: exports_external2.string().optional(),
27487
27584
  agent_id: exports_external2.string().optional(),
@@ -28710,6 +28807,40 @@ function register6(server) {
28710
28807
  return err(e);
28711
28808
  }
28712
28809
  });
28810
+ server.tool("browser_diff", "Visual diff between two URLs or a URL and a gallery entry. Screenshots both, returns a pixel diff image highlighting changes in red.", {
28811
+ session_id: exports_external2.string().optional(),
28812
+ url1: exports_external2.string().describe("First URL to screenshot"),
28813
+ url2: exports_external2.string().describe("Second URL to screenshot"),
28814
+ threshold: exports_external2.number().optional().default(10).describe("Pixel difference threshold (0-255, default 10)"),
28815
+ wait_ms: exports_external2.number().optional().default(1000).describe("Wait time after navigation before screenshot (ms)")
28816
+ }, async ({ session_id, url1, url2, threshold, wait_ms }) => {
28817
+ try {
28818
+ const sid = resolveSessionId(session_id);
28819
+ const page = getSessionPage(sid);
28820
+ await page.goto(url1, { waitUntil: "domcontentloaded" });
28821
+ await new Promise((r) => setTimeout(r, wait_ms));
28822
+ const ss1 = await takeScreenshot(page, { maxWidth: 1280, compress: true, track: false });
28823
+ await page.goto(url2, { waitUntil: "domcontentloaded" });
28824
+ await new Promise((r) => setTimeout(r, wait_ms));
28825
+ const ss2 = await takeScreenshot(page, { maxWidth: 1280, compress: true, track: false });
28826
+ const { diffImages: diffImages2 } = await Promise.resolve().then(() => (init_gallery_diff(), exports_gallery_diff));
28827
+ const diff = await diffImages2(ss1.path, ss2.path);
28828
+ logEvent(sid, "diff", { url1, url2, changed_percent: diff.changed_percent });
28829
+ return json({
28830
+ url1,
28831
+ url2,
28832
+ changed_pixels: diff.changed_pixels,
28833
+ total_pixels: diff.total_pixels,
28834
+ changed_percent: Math.round(diff.changed_percent * 100) / 100,
28835
+ diff_path: diff.diff_path,
28836
+ diff_base64: diff.diff_base64.length > 50000 ? undefined : diff.diff_base64,
28837
+ screenshot1_path: ss1.path,
28838
+ screenshot2_path: ss2.path
28839
+ });
28840
+ } catch (e) {
28841
+ return err(e);
28842
+ }
28843
+ });
28713
28844
  }
28714
28845
  var init_capture = __esm(() => {
28715
28846
  init_helpers();
@@ -31845,6 +31976,53 @@ function register7(server) {
31845
31976
  return err(e);
31846
31977
  }
31847
31978
  });
31979
+ server.tool("browser_performance_budget", "Check page performance against a budget. Set thresholds for LCP, FCP, CLS, TTFB, DOM complete, and load event. Returns pass/fail per metric with actual values.", {
31980
+ session_id: exports_external2.string().optional(),
31981
+ lcp_ms: exports_external2.number().optional().describe("Largest Contentful Paint budget in ms (good: <2500)"),
31982
+ fcp_ms: exports_external2.number().optional().describe("First Contentful Paint budget in ms (good: <1800)"),
31983
+ cls: exports_external2.number().optional().describe("Cumulative Layout Shift budget (good: <0.1)"),
31984
+ ttfb_ms: exports_external2.number().optional().describe("Time to First Byte budget in ms (good: <800)"),
31985
+ dom_complete_ms: exports_external2.number().optional().describe("DOM complete budget in ms"),
31986
+ load_event_ms: exports_external2.number().optional().describe("Load event budget in ms"),
31987
+ js_heap_mb: exports_external2.number().optional().describe("JS heap size budget in MB")
31988
+ }, async ({ session_id, lcp_ms, fcp_ms, cls, ttfb_ms, dom_complete_ms, load_event_ms, js_heap_mb }) => {
31989
+ try {
31990
+ const sid = resolveSessionId(session_id);
31991
+ const page = getSessionPage(sid);
31992
+ const metrics = await getPerformanceMetrics(page);
31993
+ const checks = [];
31994
+ let allPassed = true;
31995
+ const check = (name, budget, actual) => {
31996
+ if (budget === undefined)
31997
+ return;
31998
+ const passed = actual !== undefined && actual <= budget;
31999
+ if (!passed)
32000
+ allPassed = false;
32001
+ checks.push({ metric: name, budget, actual, passed });
32002
+ };
32003
+ check("lcp", lcp_ms, metrics.lcp);
32004
+ check("fcp", fcp_ms, metrics.fcp);
32005
+ check("cls", cls, metrics.cls);
32006
+ check("ttfb", ttfb_ms, metrics.ttfb);
32007
+ check("dom_complete", dom_complete_ms, metrics.dom_complete);
32008
+ check("load_event", load_event_ms, metrics.load_event);
32009
+ if (js_heap_mb !== undefined && metrics.js_heap_size_used !== undefined) {
32010
+ const heapMb = metrics.js_heap_size_used / (1024 * 1024);
32011
+ const passed = heapMb <= js_heap_mb;
32012
+ if (!passed)
32013
+ allPassed = false;
32014
+ checks.push({ metric: "js_heap_mb", budget: js_heap_mb, actual: Math.round(heapMb * 100) / 100, passed });
32015
+ }
32016
+ return json({
32017
+ passed: allPassed,
32018
+ checks,
32019
+ metrics,
32020
+ url: page.url()
32021
+ });
32022
+ } catch (e) {
32023
+ return err(e);
32024
+ }
32025
+ });
31848
32026
  }
31849
32027
  var init_network2 = __esm(() => {
31850
32028
  init_helpers();
@@ -32218,7 +32396,7 @@ function register8(server) {
32218
32396
  max_pages: exports_external2.number().optional().default(50),
32219
32397
  same_domain: exports_external2.boolean().optional().default(true),
32220
32398
  project_id: exports_external2.string().optional(),
32221
- engine: exports_external2.enum(["playwright", "cdp", "lightpanda", "bun", "auto"]).optional().default("auto")
32399
+ engine: exports_external2.enum(["playwright", "cdp", "lightpanda", "bun", "tui", "auto"]).optional().default("auto")
32222
32400
  }, async ({ url, max_depth, max_pages, same_domain, project_id, engine }) => {
32223
32401
  try {
32224
32402
  const result = await crawl(url, {
@@ -32293,6 +32471,19 @@ function register8(server) {
32293
32471
  return err(e);
32294
32472
  }
32295
32473
  });
32474
+ server.tool("browser_record_export", "Export a recording as a Playwright test (.spec.ts), Puppeteer script, or JSON. Returns the generated code as text.", {
32475
+ recording_id: exports_external2.string().describe("ID of the recording to export"),
32476
+ format: exports_external2.enum(["playwright", "puppeteer", "json"]).optional().default("playwright").describe("Export format")
32477
+ }, async ({ recording_id, format }) => {
32478
+ try {
32479
+ const { exportRecording: exportRecording2 } = await Promise.resolve().then(() => (init_recorder(), exports_recorder));
32480
+ const code = exportRecording2(recording_id, format);
32481
+ const ext = format === "json" ? ".json" : format === "playwright" ? ".spec.ts" : ".js";
32482
+ return json({ format, filename: `recording-${recording_id}${ext}`, code });
32483
+ } catch (e) {
32484
+ return err(e);
32485
+ }
32486
+ });
32296
32487
  }
32297
32488
  var init_recordings2 = __esm(() => {
32298
32489
  init_helpers();
@@ -32386,7 +32577,7 @@ function register9(server) {
32386
32577
  server.tool("browser_script_run", "Run a saved script asynchronously. Returns run_id immediately \u2014 poll with browser_script_status for step-by-step progress. Scripts combine browser actions + connector calls + AI reasoning. Works with any engine (Bun.WebView, Playwright, CDP).", {
32387
32578
  name: exports_external2.string().describe("Script name"),
32388
32579
  session_id: exports_external2.string().optional(),
32389
- engine: exports_external2.enum(["playwright", "cdp", "lightpanda", "bun", "auto"]).optional().default("auto"),
32580
+ engine: exports_external2.enum(["playwright", "cdp", "lightpanda", "bun", "tui", "auto"]).optional().default("auto"),
32390
32581
  variables: exports_external2.record(exports_external2.string()).optional().describe("Override script variables")
32391
32582
  }, async ({ name, session_id, engine, variables }) => {
32392
32583
  try {
@@ -32641,8 +32832,8 @@ import { join as join33 } from "path";
32641
32832
  import { existsSync as existsSync43, mkdirSync as mkdirSync4, readFileSync as readFileSync33, writeFileSync as writeFileSync33 } from "fs";
32642
32833
  import { homedir as homedir33 } from "os";
32643
32834
  import { join as join43 } from "path";
32644
- function __exportSetter3(name, newValue) {
32645
- this[name] = __returnValue3.bind(null, newValue);
32835
+ function __exportSetter2(name, newValue) {
32836
+ this[name] = __returnValue2.bind(null, newValue);
32646
32837
  }
32647
32838
  function isInMemoryDb(path) {
32648
32839
  return path === ":memory:" || path.startsWith("file::memory:");
@@ -35756,13 +35947,13 @@ function clearActiveModel() {
35756
35947
  delete config.activeModel;
35757
35948
  writeConfig(config);
35758
35949
  }
35759
- var __defProp3, __returnValue3 = (v) => v, __export3 = (target, all) => {
35950
+ var __defProp3, __returnValue2 = (v) => v, __export3 = (target, all) => {
35760
35951
  for (var name in all)
35761
35952
  __defProp3(target, name, {
35762
35953
  get: all[name],
35763
35954
  enumerable: true,
35764
35955
  configurable: true,
35765
- set: __exportSetter3.bind(all, name)
35956
+ set: __exportSetter2.bind(all, name)
35766
35957
  });
35767
35958
  }, __esm3 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), exports_database, MIGRATIONS, _db2 = null, init_database, AgentConflictError, EntityNotFoundError, MemoryNotFoundError, DuplicateMemoryError, MemoryExpiredError, InvalidScopeError, VersionConflictError, MemoryConflictError, OPENAI_EMBED_URL = "https://api.openai.com/v1/embeddings", EMBED_MODEL = "text-embedding-3-small", EMBED_DIMENSIONS = 1536, REDACTED = "[REDACTED]", SECRET_PATTERNS, _idCounter = 0, hookRegistry, INSTRUCTION_PATTERNS, PROMOTIONAL_PATTERNS, RECALL_PROMOTE_THRESHOLD = 3, CONFLICT_WINDOW_MS, MEMORY_WRITE_TTL = 30, MemoryLockConflictError, sessionFocus, STOP_WORDS, DEFAULT_CONFIG, VALID_SCOPES, VALID_CATEGORIES, defaultSyncAgents, DEFAULT_AUTO_MEMORY_CONFIG, MEMORY_EXTRACTION_SYSTEM_PROMPT = `You are a precise memory extraction engine for an AI agent.
35768
35959
  Given text, extract facts worth remembering as structured JSON.
@@ -37285,11 +37476,11 @@ import { randomUUID as randomUUID22 } from "crypto";
37285
37476
  import { readFileSync as readFileSync24, writeFileSync as writeFileSync7, mkdirSync as mkdirSync34 } from "fs";
37286
37477
  import { join as join44, dirname as dirname24 } from "path";
37287
37478
  import { homedir as homedir42 } from "os";
37288
- function __accessProp3(key) {
37479
+ function __accessProp2(key) {
37289
37480
  return this[key];
37290
37481
  }
37291
- function __exportSetter4(name, newValue) {
37292
- this[name] = __returnValue4.bind(null, newValue);
37482
+ function __exportSetter3(name, newValue) {
37483
+ this[name] = __returnValue3.bind(null, newValue);
37293
37484
  }
37294
37485
  function getDbPath3() {
37295
37486
  if (process.env.CONVERSATIONS_DB_PATH)
@@ -39274,10 +39465,10 @@ function getGraphStats() {
39274
39465
  map[r.relation] = r.c;
39275
39466
  return { total_edges: total, by_relation: map };
39276
39467
  }
39277
- var __create3, __getProtoOf3, __defProp4, __getOwnPropNames3, __getOwnPropDesc2, __hasOwnProp3, __toESMCache_node3, __toESMCache_esm3, __toESM3 = (mod, isNodeMode, target) => {
39468
+ var __create3, __getProtoOf3, __defProp4, __getOwnPropNames3, __getOwnPropDesc2, __hasOwnProp3, __toESMCache_node2, __toESMCache_esm2, __toESM3 = (mod, isNodeMode, target) => {
39278
39469
  var canCache = mod != null && typeof mod === "object";
39279
39470
  if (canCache) {
39280
- var cache = isNodeMode ? __toESMCache_node3 ??= new WeakMap : __toESMCache_esm3 ??= new WeakMap;
39471
+ var cache = isNodeMode ? __toESMCache_node2 ??= new WeakMap : __toESMCache_esm2 ??= new WeakMap;
39281
39472
  var cached = cache.get(mod);
39282
39473
  if (cached)
39283
39474
  return cached;
@@ -39287,7 +39478,7 @@ var __create3, __getProtoOf3, __defProp4, __getOwnPropNames3, __getOwnPropDesc2,
39287
39478
  for (let key of __getOwnPropNames3(mod))
39288
39479
  if (!__hasOwnProp3.call(to, key))
39289
39480
  __defProp4(to, key, {
39290
- get: __accessProp3.bind(mod, key),
39481
+ get: __accessProp2.bind(mod, key),
39291
39482
  enumerable: true
39292
39483
  });
39293
39484
  if (canCache)
@@ -39302,19 +39493,19 @@ var __create3, __getProtoOf3, __defProp4, __getOwnPropNames3, __getOwnPropDesc2,
39302
39493
  for (var key of __getOwnPropNames3(from))
39303
39494
  if (!__hasOwnProp3.call(entry, key))
39304
39495
  __defProp4(entry, key, {
39305
- get: __accessProp3.bind(from, key),
39496
+ get: __accessProp2.bind(from, key),
39306
39497
  enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable
39307
39498
  });
39308
39499
  }
39309
39500
  __moduleCache2.set(from, entry);
39310
39501
  return entry;
39311
- }, __moduleCache2, __commonJS3 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue4 = (v) => v, __export4 = (target, all) => {
39502
+ }, __moduleCache2, __commonJS3 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue3 = (v) => v, __export4 = (target, all) => {
39312
39503
  for (var name in all)
39313
39504
  __defProp4(target, name, {
39314
39505
  get: all[name],
39315
39506
  enumerable: true,
39316
39507
  configurable: true,
39317
- set: __exportSetter4.bind(all, name)
39508
+ set: __exportSetter3.bind(all, name)
39318
39509
  });
39319
39510
  }, exports_db, db = null, init_db = () => {}, require_react_development, require_react, cachedConfig = null, configLoadedAt = 0, CONFIG_CACHE_MS = 1e4, import_react, AGENT_NAMES, AGENT_ID_FILE, cachedAutoName = null, ONLINE_THRESHOLD_SECONDS = 60, CONFLICT_THRESHOLD_SECONDS, DEFAULT_LOCK_EXPIRY_MS, STALE_HEARTBEAT_SECONDS, STOPWORDS;
39320
39511
  var init_dist4 = __esm(() => {
@@ -41928,7 +42119,7 @@ import { existsSync as existsSync62 } from "fs";
41928
42119
  import { join as join62 } from "path";
41929
42120
  import { readFileSync as readFileSync43, statSync as statSync22 } from "fs";
41930
42121
  import { relative as relative2, resolve as resolve23, join as join72 } from "path";
41931
- import { execSync as execSync2 } from "child_process";
42122
+ import { execSync as execSync3 } from "child_process";
41932
42123
 
41933
42124
  class TodosClient {
41934
42125
  baseUrl;
@@ -46276,7 +46467,7 @@ function parseGitHubUrl(url) {
46276
46467
  return { owner: match[1], repo: match[2], number: parseInt(match[3], 10) };
46277
46468
  }
46278
46469
  function fetchGitHubIssue(owner, repo, number) {
46279
- const json2 = execSync2(`gh api repos/${owner}/${repo}/issues/${number}`, { encoding: "utf-8", timeout: 15000 });
46470
+ const json2 = execSync3(`gh api repos/${owner}/${repo}/issues/${number}`, { encoding: "utf-8", timeout: 15000 });
46280
46471
  const data = JSON.parse(json2);
46281
46472
  return {
46282
46473
  number: data.number,
@@ -47870,7 +48061,8 @@ function register10(server) {
47870
48061
  { tool: "browser_screenshot", description: "Take a screenshot (PNG/JPEG/WebP, annotate=true for labels)" },
47871
48062
  { tool: "browser_pdf", description: "Generate a PDF of the page" },
47872
48063
  { tool: "browser_scroll_and_screenshot", description: "Scroll then screenshot in one call" },
47873
- { tool: "browser_scroll_to_element", description: "Scroll element into view + screenshot" }
48064
+ { tool: "browser_scroll_to_element", description: "Scroll element into view + screenshot" },
48065
+ { tool: "browser_diff", description: "Visual diff between two URLs \u2014 highlights changes in red" }
47874
48066
  ],
47875
48067
  Storage: [
47876
48068
  { tool: "browser_cookies_get", description: "Get cookies" },
@@ -47895,7 +48087,8 @@ function register10(server) {
47895
48087
  { tool: "browser_intercept_clear", description: "Remove all response intercepts" }
47896
48088
  ],
47897
48089
  Performance: [
47898
- { tool: "browser_performance", description: "Get performance metrics" }
48090
+ { tool: "browser_performance", description: "Get performance metrics" },
48091
+ { tool: "browser_performance_budget", description: "Check perf against budget thresholds (LCP, FCP, CLS, TTFB)" }
47899
48092
  ],
47900
48093
  Console: [
47901
48094
  { tool: "browser_console_log", description: "Get console messages" },
@@ -47908,6 +48101,7 @@ function register10(server) {
47908
48101
  { tool: "browser_record_step", description: "Add a step to recording" },
47909
48102
  { tool: "browser_record_stop", description: "Stop and save recording" },
47910
48103
  { tool: "browser_record_replay", description: "Replay a recorded sequence" },
48104
+ { tool: "browser_record_export", description: "Export recording as Playwright test, Puppeteer script, or JSON" },
47911
48105
  { tool: "browser_recordings_list", description: "List all recordings" }
47912
48106
  ],
47913
48107
  Auth: [
@@ -49506,9 +49700,9 @@ ${entries.length} entries`));
49506
49700
  });
49507
49701
  program2.command("install-browser").description("Install a browser engine").option("--engine <engine>", "Engine to install: lightpanda|chromium", "chromium").action(async (opts) => {
49508
49702
  if (opts.engine === "chromium") {
49509
- const { execSync: execSync3 } = await import("child_process");
49703
+ const { execSync: execSync4 } = await import("child_process");
49510
49704
  console.log(chalk4.gray("Installing Chromium via Playwright..."));
49511
- execSync3("bunx playwright install chromium", { stdio: "inherit" });
49705
+ execSync4("bunx playwright install chromium", { stdio: "inherit" });
49512
49706
  console.log(chalk4.green("\u2713 Chromium installed"));
49513
49707
  } else if (opts.engine === "lightpanda") {
49514
49708
  console.log(chalk4.yellow("Lightpanda must be installed manually."));
@@ -49528,12 +49722,12 @@ ${entries.length} entries`));
49528
49722
  console.log(chalk4.gray(` PID: ${status.pid}, Port: ${status.port}, Sessions: ${status.sessions ?? "?"}`));
49529
49723
  return;
49530
49724
  }
49531
- const { spawn: spawn2 } = await import("child_process");
49725
+ const { spawn: spawn3 } = await import("child_process");
49532
49726
  const { writeFileSync: writeFileSync9, mkdirSync: mkdirSync17 } = await import("fs");
49533
49727
  const { dirname: dirname6 } = await import("path");
49534
49728
  const pidFile = getDaemonPidFile2();
49535
49729
  mkdirSync17(dirname6(pidFile), { recursive: true });
49536
- const child = spawn2(process.execPath, [import.meta.dir + "/../../server/index.js"], {
49730
+ const child = spawn3(process.execPath, [import.meta.dir + "/../../server/index.js"], {
49537
49731
  detached: true,
49538
49732
  stdio: "ignore",
49539
49733
  env: { ...process.env, BROWSER_SERVER_PORT: opts.port }
@@ -1 +1 @@
1
- {"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../../src/engines/selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AA+B5C;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,aAAa,GACvB,aAAa,CAqBf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAOhE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CA0BnD"}
1
+ {"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../../src/engines/selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAkC5C;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,aAAa,GACvB,aAAa,CAqBf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAQhE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CA4BnD"}
@@ -0,0 +1,49 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ import type { Browser, Page } from "playwright";
3
+ export interface TuiSession {
4
+ ttydProcess: ChildProcess;
5
+ port: number;
6
+ browser: Browser;
7
+ page: Page;
8
+ }
9
+ /**
10
+ * Check if ttyd is installed on this system.
11
+ */
12
+ export declare function isTuiAvailable(): boolean;
13
+ /**
14
+ * Launch a terminal app via ttyd and connect Playwright to it.
15
+ *
16
+ * @param command - The shell command to run (e.g. "htop", "npm start", "python app.py")
17
+ * @param options - Viewport and headless options
18
+ * @returns TuiSession with Playwright page connected to the ttyd web UI
19
+ */
20
+ export declare function launchTui(command: string, options?: {
21
+ headless?: boolean;
22
+ viewport?: {
23
+ width: number;
24
+ height: number;
25
+ };
26
+ }): Promise<TuiSession>;
27
+ /**
28
+ * Send keystrokes to the TUI app via the Playwright page.
29
+ * Types into the terminal's xterm.js input.
30
+ */
31
+ export declare function sendKeys(page: Page, keys: string): Promise<void>;
32
+ /**
33
+ * Send a special key (Enter, Escape, ArrowUp, etc.) to the TUI.
34
+ */
35
+ export declare function sendSpecialKey(page: Page, key: string): Promise<void>;
36
+ /**
37
+ * Get the visible text content from the terminal.
38
+ * Works with both canvas-based and DOM-based xterm.js renderers.
39
+ */
40
+ export declare function getTerminalText(page: Page): Promise<string>;
41
+ /**
42
+ * Wait for specific text to appear in the terminal output.
43
+ */
44
+ export declare function waitForTerminalText(page: Page, text: string, timeoutMs?: number): Promise<boolean>;
45
+ /**
46
+ * Close TUI session — kill ttyd and close Playwright.
47
+ */
48
+ export declare function closeTui(session: TuiSession): Promise<void>;
49
+ //# sourceMappingURL=tui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../../src/engines/tui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAYhD,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,YAAY,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAOxC;AAqCD;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACP,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC,GACL,OAAO,CAAC,UAAU,CAAC,CAqDrB;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAStE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO3E;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBjE;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,MAAe,GACzB,OAAO,CAAC,OAAO,CAAC,CAQlB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tui.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.test.d.ts","sourceRoot":"","sources":["../../src/engines/tui.test.ts"],"names":[],"mappings":""}