@hasna/browser 0.0.3 → 0.0.4

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
@@ -2121,6 +2121,12 @@ var init_types = __esm(() => {
2121
2121
  });
2122
2122
 
2123
2123
  // src/db/schema.ts
2124
+ var exports_schema = {};
2125
+ __export(exports_schema, {
2126
+ resetDatabase: () => resetDatabase,
2127
+ getDatabase: () => getDatabase,
2128
+ getDataDir: () => getDataDir
2129
+ });
2124
2130
  import { Database } from "bun:sqlite";
2125
2131
  import { join } from "path";
2126
2132
  import { mkdirSync } from "fs";
@@ -2146,6 +2152,15 @@ function getDatabase(path) {
2146
2152
  runMigrations(_db);
2147
2153
  return _db;
2148
2154
  }
2155
+ function resetDatabase() {
2156
+ if (_db) {
2157
+ try {
2158
+ _db.close();
2159
+ } catch {}
2160
+ }
2161
+ _db = null;
2162
+ _dbPath = null;
2163
+ }
2149
2164
  function runMigrations(db) {
2150
2165
  db.exec(`
2151
2166
  CREATE TABLE IF NOT EXISTS schema_migrations (
@@ -2313,7 +2328,14 @@ import { randomUUID } from "crypto";
2313
2328
  function createSession(data) {
2314
2329
  const db = getDatabase();
2315
2330
  const id = randomUUID();
2316
- db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, data.name ?? null);
2331
+ let name = data.name ?? null;
2332
+ if (name) {
2333
+ const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
2334
+ if (existing) {
2335
+ name = `${name}-${id.slice(0, 6)}`;
2336
+ }
2337
+ }
2338
+ db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
2317
2339
  return getSession(id);
2318
2340
  }
2319
2341
  function getSessionByName(name) {
@@ -2876,12 +2898,19 @@ async function createSession2(opts = {}) {
2876
2898
  userAgent: opts.userAgent
2877
2899
  });
2878
2900
  }
2901
+ let sessionName = opts.name ?? (opts.startUrl ? (() => {
2902
+ try {
2903
+ return new URL(opts.startUrl).hostname;
2904
+ } catch {
2905
+ return;
2906
+ }
2907
+ })() : undefined);
2879
2908
  const session = createSession({
2880
2909
  engine: resolvedEngine,
2881
2910
  projectId: opts.projectId,
2882
2911
  agentId: opts.agentId,
2883
2912
  startUrl: opts.startUrl,
2884
- name: opts.name ?? (opts.startUrl ? new URL(opts.startUrl).hostname : undefined)
2913
+ name: sessionName
2885
2914
  });
2886
2915
  if (opts.stealth) {
2887
2916
  try {
@@ -2978,12 +3007,28 @@ var init_session = __esm(() => {
2978
3007
  });
2979
3008
 
2980
3009
  // src/lib/snapshot.ts
3010
+ var exports_snapshot = {};
3011
+ __export(exports_snapshot, {
3012
+ takeSnapshot: () => takeSnapshot,
3013
+ setLastSnapshot: () => setLastSnapshot,
3014
+ hasRefs: () => hasRefs,
3015
+ getSessionRefs: () => getSessionRefs,
3016
+ getRefLocator: () => getRefLocator,
3017
+ getRefInfo: () => getRefInfo,
3018
+ getLastSnapshot: () => getLastSnapshot,
3019
+ diffSnapshots: () => diffSnapshots,
3020
+ clearSessionRefs: () => clearSessionRefs,
3021
+ clearLastSnapshot: () => clearLastSnapshot
3022
+ });
2981
3023
  function getLastSnapshot(sessionId) {
2982
3024
  return lastSnapshots.get(sessionId) ?? null;
2983
3025
  }
2984
3026
  function setLastSnapshot(sessionId, snapshot) {
2985
3027
  lastSnapshots.set(sessionId, snapshot);
2986
3028
  }
3029
+ function clearLastSnapshot(sessionId) {
3030
+ lastSnapshots.delete(sessionId);
3031
+ }
2987
3032
  async function takeSnapshot(page, sessionId) {
2988
3033
  let ariaTree;
2989
3034
  try {
@@ -3086,6 +3131,21 @@ function getRefLocator(page, sessionId, ref) {
3086
3131
  throw new Error(`Ref ${ref} not found. Available refs: ${[...refMap.keys()].slice(0, 20).join(", ")}`);
3087
3132
  return page.getByRole(entry.role, { name: entry.name }).first();
3088
3133
  }
3134
+ function getRefInfo(sessionId, ref) {
3135
+ const refMap = sessionRefMaps.get(sessionId);
3136
+ if (!refMap)
3137
+ return null;
3138
+ return refMap.get(ref) ?? null;
3139
+ }
3140
+ function getSessionRefs(sessionId) {
3141
+ return sessionRefMaps.get(sessionId) ?? null;
3142
+ }
3143
+ function clearSessionRefs(sessionId) {
3144
+ sessionRefMaps.delete(sessionId);
3145
+ }
3146
+ function hasRefs(sessionId) {
3147
+ return sessionRefMaps.has(sessionId) && (sessionRefMaps.get(sessionId)?.size ?? 0) > 0;
3148
+ }
3089
3149
  function refKey(info) {
3090
3150
  return `${info.role}::${info.name}`;
3091
3151
  }
@@ -10166,11 +10226,20 @@ async function takeScreenshot(page, opts) {
10166
10226
  }
10167
10227
  const originalSizeBytes = rawBuffer.length;
10168
10228
  let finalBuffer;
10169
- if (compress && format !== "png") {
10170
- finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
10171
- } else if (compress && format === "png") {
10172
- finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
10173
- } else {
10229
+ let compressed = true;
10230
+ let fallback = false;
10231
+ try {
10232
+ if (compress && format !== "png") {
10233
+ finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
10234
+ } else if (compress && format === "png") {
10235
+ finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
10236
+ } else {
10237
+ finalBuffer = rawBuffer;
10238
+ compressed = false;
10239
+ }
10240
+ } catch (sharpErr) {
10241
+ fallback = true;
10242
+ compressed = false;
10174
10243
  finalBuffer = rawBuffer;
10175
10244
  }
10176
10245
  const compressedSizeBytes = finalBuffer.length;
@@ -10198,7 +10267,8 @@ async function takeScreenshot(page, opts) {
10198
10267
  compressed_size_bytes: compressedSizeBytes,
10199
10268
  compression_ratio: compressionRatio,
10200
10269
  thumbnail_path: thumbnailPath,
10201
- thumbnail_base64: thumbnailBase64
10270
+ thumbnail_base64: thumbnailBase64,
10271
+ ...fallback ? { fallback: true, compressed: false } : {}
10202
10272
  };
10203
10273
  if (opts?.track !== false) {
10204
10274
  try {
@@ -15298,7 +15368,8 @@ async function annotateScreenshot(page, sessionId) {
15298
15368
  const annotations = [];
15299
15369
  const labelToRef = {};
15300
15370
  let labelCounter = 1;
15301
- for (const [ref, info] of Object.entries(snapshot.refs)) {
15371
+ const refsToAnnotate = Object.entries(snapshot.refs).slice(0, MAX_ANNOTATIONS);
15372
+ for (const [ref, info] of refsToAnnotate) {
15302
15373
  try {
15303
15374
  const locator = page.getByRole(info.role, { name: info.name }).first();
15304
15375
  const box = await locator.boundingBox();
@@ -15337,7 +15408,7 @@ async function annotateScreenshot(page, sessionId) {
15337
15408
  const annotatedBuffer = await import_sharp3.default(rawBuffer).composite([{ input: Buffer.from(svg), top: 0, left: 0 }]).webp({ quality: 85 }).toBuffer();
15338
15409
  return { buffer: annotatedBuffer, annotations, labelToRef };
15339
15410
  }
15340
- var import_sharp3;
15411
+ var import_sharp3, MAX_ANNOTATIONS = 40;
15341
15412
  var init_annotate = __esm(() => {
15342
15413
  init_snapshot();
15343
15414
  import_sharp3 = __toESM(require_lib(), 1);
@@ -15347,6 +15418,8 @@ var init_annotate = __esm(() => {
15347
15418
  var exports_mcp = {};
15348
15419
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
15349
15420
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15421
+ import { readFileSync as readFileSync3 } from "fs";
15422
+ import { join as join7 } from "path";
15350
15423
  function json(data) {
15351
15424
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
15352
15425
  }
@@ -15358,7 +15431,7 @@ function err(e) {
15358
15431
  isError: true
15359
15432
  };
15360
15433
  }
15361
- var networkLogCleanup, consoleCaptureCleanup, harCaptures, server, activeWatchHandles, transport;
15434
+ var _pkg, networkLogCleanup, consoleCaptureCleanup, harCaptures, server, activeWatchHandles, _startupToolCount, transport;
15362
15435
  var init_mcp = __esm(async () => {
15363
15436
  init_zod();
15364
15437
  init_session();
@@ -15382,6 +15455,7 @@ var init_mcp = __esm(async () => {
15382
15455
  init_dialogs();
15383
15456
  init_profiles();
15384
15457
  init_types();
15458
+ _pkg = JSON.parse(readFileSync3(join7(import.meta.dir, "../../package.json"), "utf8"));
15385
15459
  networkLogCleanup = new Map;
15386
15460
  consoleCaptureCleanup = new Map;
15387
15461
  harCaptures = new Map;
@@ -15436,13 +15510,50 @@ var init_mcp = __esm(async () => {
15436
15510
  return err(e);
15437
15511
  }
15438
15512
  });
15439
- server.tool("browser_navigate", "Navigate to a URL. Returns title + thumbnail + accessibility snapshot preview with refs.", { session_id: exports_external.string(), url: exports_external.string(), timeout: exports_external.number().optional().default(30000), auto_snapshot: exports_external.boolean().optional().default(true), auto_thumbnail: exports_external.boolean().optional().default(true) }, async ({ session_id, url, timeout, auto_snapshot, auto_thumbnail }) => {
15513
+ server.tool("browser_navigate", "Navigate to a URL. Auto-detects redirects, auto-names session, returns compact refs + thumbnail.", {
15514
+ session_id: exports_external.string(),
15515
+ url: exports_external.string(),
15516
+ timeout: exports_external.number().optional().default(30000),
15517
+ auto_snapshot: exports_external.boolean().optional().default(true),
15518
+ auto_thumbnail: exports_external.boolean().optional().default(true)
15519
+ }, async ({ session_id, url, timeout, auto_snapshot, auto_thumbnail }) => {
15440
15520
  try {
15441
15521
  const page = getSessionPage(session_id);
15442
15522
  await navigate(page, url, timeout);
15443
15523
  const title = await getTitle(page);
15444
15524
  const current_url = await getUrl(page);
15445
- const result = { url, title, current_url };
15525
+ const redirected = current_url !== url && current_url !== url + "/" && url !== current_url.replace(/\/$/, "");
15526
+ let redirect_type;
15527
+ if (redirected) {
15528
+ try {
15529
+ const reqHost = new URL(url).hostname;
15530
+ const resHost = new URL(current_url).hostname;
15531
+ const reqPath = new URL(url).pathname;
15532
+ const resPath = new URL(current_url).pathname;
15533
+ if (reqHost !== resHost)
15534
+ redirect_type = "canonical";
15535
+ else if (resPath.match(/\/[a-z]{2}-[a-z]{2}\//))
15536
+ redirect_type = "geo";
15537
+ else if (current_url.includes("login") || current_url.includes("signin"))
15538
+ redirect_type = "auth";
15539
+ else
15540
+ redirect_type = "unknown";
15541
+ } catch {}
15542
+ }
15543
+ try {
15544
+ const session = getSession2(session_id);
15545
+ if (!session.name) {
15546
+ const hostname = new URL(current_url).hostname;
15547
+ renameSession2(session_id, hostname);
15548
+ }
15549
+ } catch {}
15550
+ const result = {
15551
+ url,
15552
+ title,
15553
+ current_url,
15554
+ redirected,
15555
+ ...redirect_type ? { redirect_type } : {}
15556
+ };
15446
15557
  if (auto_thumbnail) {
15447
15558
  try {
15448
15559
  const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
@@ -15452,7 +15563,9 @@ var init_mcp = __esm(async () => {
15452
15563
  if (auto_snapshot) {
15453
15564
  try {
15454
15565
  const snap = await takeSnapshot(page, session_id);
15455
- result.snapshot_preview = snap.tree.slice(0, 3000);
15566
+ setLastSnapshot(session_id, snap);
15567
+ const refEntries = Object.entries(snap.refs).slice(0, 30);
15568
+ result.snapshot_refs = refEntries.map(([ref, info]) => `${info.role}:${info.name.slice(0, 50)} [${ref}]`).join(", ");
15456
15569
  result.interactive_count = snap.interactive_count;
15457
15570
  result.has_errors = getConsoleLog(session_id, "error").length > 0;
15458
15571
  } catch {}
@@ -15558,7 +15671,7 @@ var init_mcp = __esm(async () => {
15558
15671
  return err(e);
15559
15672
  }
15560
15673
  });
15561
- server.tool("browser_check", "Check or uncheck a checkbox by ref or selector", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), checked: exports_external.boolean() }, async ({ session_id, selector, ref, checked }) => {
15674
+ server.tool("browser_toggle", "Check or uncheck a checkbox by ref or selector", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), checked: exports_external.boolean() }, async ({ session_id, selector, ref, checked }) => {
15562
15675
  try {
15563
15676
  const page = getSessionPage(session_id);
15564
15677
  if (ref) {
@@ -15649,12 +15762,33 @@ var init_mcp = __esm(async () => {
15649
15762
  return err(e);
15650
15763
  }
15651
15764
  });
15652
- server.tool("browser_snapshot", "Get a structured accessibility snapshot with element refs (@e0, @e1...). Use refs in browser_click, browser_type, etc.", { session_id: exports_external.string() }, async ({ session_id }) => {
15765
+ server.tool("browser_snapshot", "Get accessibility snapshot with element refs (@e0, @e1...). Use compact=true (default) for token-efficient output. Use refs in browser_click, browser_type, etc.", {
15766
+ session_id: exports_external.string(),
15767
+ compact: exports_external.boolean().optional().default(true),
15768
+ max_refs: exports_external.number().optional().default(50),
15769
+ full_tree: exports_external.boolean().optional().default(false)
15770
+ }, async ({ session_id, compact, max_refs, full_tree }) => {
15653
15771
  try {
15654
15772
  const page = getSessionPage(session_id);
15655
15773
  const result = await takeSnapshot(page, session_id);
15656
15774
  setLastSnapshot(session_id, result);
15657
- return json({ snapshot: result.tree, refs: result.refs, interactive_count: result.interactive_count });
15775
+ const refEntries = Object.entries(result.refs).slice(0, max_refs);
15776
+ const limitedRefs = Object.fromEntries(refEntries);
15777
+ const truncated = Object.keys(result.refs).length > max_refs;
15778
+ if (compact && !full_tree) {
15779
+ const compactRefs = refEntries.map(([ref, info]) => `${info.role}:${info.name.slice(0, 60)} [${ref}]${info.checked !== undefined ? ` checked=${info.checked}` : ""}${!info.enabled ? " disabled" : ""}`).join(`
15780
+ `);
15781
+ return json({
15782
+ snapshot_compact: compactRefs,
15783
+ interactive_count: result.interactive_count,
15784
+ shown_count: refEntries.length,
15785
+ truncated,
15786
+ refs: limitedRefs
15787
+ });
15788
+ }
15789
+ const tree = full_tree ? result.tree : result.tree.slice(0, 5000) + (result.tree.length > 5000 ? `
15790
+ ... (truncated \u2014 use full_tree=true for complete)` : "");
15791
+ return json({ snapshot: tree, refs: limitedRefs, interactive_count: result.interactive_count, truncated });
15658
15792
  } catch (e) {
15659
15793
  return err(e);
15660
15794
  }
@@ -16136,40 +16270,6 @@ var init_mcp = __esm(async () => {
16136
16270
  return err(e);
16137
16271
  }
16138
16272
  });
16139
- server.tool("browser_page_check", "One-call page summary: page info + console errors + performance metrics + thumbnail + accessibility snapshot preview. Replaces 4-5 separate tool calls.", { session_id: exports_external.string() }, async ({ session_id }) => {
16140
- try {
16141
- const page = getSessionPage(session_id);
16142
- const info = await getPageInfo(page);
16143
- const errors2 = getConsoleLog(session_id, "error");
16144
- info.has_console_errors = errors2.length > 0;
16145
- let perf = {};
16146
- try {
16147
- perf = await getPerformanceMetrics(page);
16148
- } catch {}
16149
- let thumbnail_base64 = "";
16150
- try {
16151
- const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
16152
- thumbnail_base64 = ss.base64;
16153
- } catch {}
16154
- let snapshot_preview = "";
16155
- let interactive_count = 0;
16156
- try {
16157
- const snap = await takeSnapshot(page, session_id);
16158
- snapshot_preview = snap.tree.slice(0, 2000);
16159
- interactive_count = snap.interactive_count;
16160
- } catch {}
16161
- return json({
16162
- ...info,
16163
- error_count: errors2.length,
16164
- performance: perf,
16165
- thumbnail_base64: thumbnail_base64.length > 50000 ? "" : thumbnail_base64,
16166
- snapshot_preview,
16167
- interactive_count
16168
- });
16169
- } catch (e) {
16170
- return err(e);
16171
- }
16172
- });
16173
16273
  server.tool("browser_gallery_list", "List screenshot gallery entries with optional filters", {
16174
16274
  project_id: exports_external.string().optional(),
16175
16275
  session_id: exports_external.string().optional(),
@@ -16494,7 +16594,7 @@ var init_mcp = __esm(async () => {
16494
16594
  { tool: "browser_hover", description: "Hover over an element" },
16495
16595
  { tool: "browser_scroll", description: "Scroll the page" },
16496
16596
  { tool: "browser_select", description: "Select a dropdown option" },
16497
- { tool: "browser_check", description: "Check/uncheck a checkbox" },
16597
+ { tool: "browser_toggle", description: "Check/uncheck a checkbox" },
16498
16598
  { tool: "browser_upload", description: "Upload a file to an input" },
16499
16599
  { tool: "browser_press_key", description: "Press a keyboard key" },
16500
16600
  { tool: "browser_wait", description: "Wait for a selector to appear" },
@@ -16514,9 +16614,10 @@ var init_mcp = __esm(async () => {
16514
16614
  { tool: "browser_evaluate", description: "Execute JavaScript in page context" }
16515
16615
  ],
16516
16616
  Capture: [
16517
- { tool: "browser_screenshot", description: "Take a screenshot (PNG/JPEG/WebP)" },
16617
+ { tool: "browser_screenshot", description: "Take a screenshot (PNG/JPEG/WebP, annotate=true for labels)" },
16518
16618
  { tool: "browser_pdf", description: "Generate a PDF of the page" },
16519
- { tool: "browser_scroll_and_screenshot", description: "Scroll then screenshot in one call" }
16619
+ { tool: "browser_scroll_and_screenshot", description: "Scroll then screenshot in one call" },
16620
+ { tool: "browser_scroll_to_element", description: "Scroll element into view + screenshot" }
16520
16621
  ],
16521
16622
  Storage: [
16522
16623
  { tool: "browser_cookies_get", description: "Get cookies" },
@@ -16595,7 +16696,8 @@ var init_mcp = __esm(async () => {
16595
16696
  { tool: "browser_tab_close", description: "Close a tab by index" }
16596
16697
  ],
16597
16698
  Meta: [
16598
- { tool: "browser_page_check", description: "One-call page summary with diagnostics" },
16699
+ { tool: "browser_check", description: "RECOMMENDED: One-call page summary with diagnostics" },
16700
+ { tool: "browser_version", description: "Show running binary version and tool count" },
16599
16701
  { tool: "browser_help", description: "Show this help (all tools)" },
16600
16702
  { tool: "browser_snapshot_diff", description: "Diff current snapshot vs previous" },
16601
16703
  { tool: "browser_watch_start", description: "Watch page for DOM changes" },
@@ -16609,6 +16711,88 @@ var init_mcp = __esm(async () => {
16609
16711
  return err(e);
16610
16712
  }
16611
16713
  });
16714
+ server.tool("browser_version", "Get the running browser MCP version, tool count, and environment info. Use this to verify which binary is active.", {}, async () => {
16715
+ try {
16716
+ const { getDataDir: getDataDir4 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
16717
+ const toolCount = Object.keys(server._registeredTools ?? {}).length;
16718
+ return json({
16719
+ version: _pkg.version,
16720
+ mcp_tools_count: toolCount,
16721
+ bun_version: Bun.version,
16722
+ data_dir: getDataDir4(),
16723
+ node_env: process.env["NODE_ENV"] ?? "production"
16724
+ });
16725
+ } catch (e) {
16726
+ return err(e);
16727
+ }
16728
+ });
16729
+ server.tool("browser_scroll_to_element", "Scroll an element into view (by ref or selector) then optionally take a screenshot of it. Replaces scroll + wait + screenshot pattern.", {
16730
+ session_id: exports_external.string(),
16731
+ selector: exports_external.string().optional(),
16732
+ ref: exports_external.string().optional(),
16733
+ screenshot: exports_external.boolean().optional().default(true),
16734
+ wait_ms: exports_external.number().optional().default(200)
16735
+ }, async ({ session_id, selector, ref, screenshot: doScreenshot, wait_ms }) => {
16736
+ try {
16737
+ const page = getSessionPage(session_id);
16738
+ let locator;
16739
+ if (ref) {
16740
+ const { getRefLocator: getRefLocator2 } = await Promise.resolve().then(() => (init_snapshot(), exports_snapshot));
16741
+ locator = getRefLocator2(page, session_id, ref);
16742
+ } else if (selector) {
16743
+ locator = page.locator(selector).first();
16744
+ } else {
16745
+ return err(new Error("Either ref or selector is required"));
16746
+ }
16747
+ await locator.scrollIntoViewIfNeeded();
16748
+ await new Promise((r) => setTimeout(r, wait_ms));
16749
+ const result = { scrolled: ref ?? selector };
16750
+ if (doScreenshot) {
16751
+ try {
16752
+ const ss = await takeScreenshot(page, { selector, track: false });
16753
+ ss.url = page.url();
16754
+ if (ss.base64.length > 50000) {
16755
+ ss.base64_truncated = true;
16756
+ ss.base64 = ss.thumbnail_base64 ?? "";
16757
+ }
16758
+ result.screenshot = ss;
16759
+ } catch {}
16760
+ }
16761
+ return json(result);
16762
+ } catch (e) {
16763
+ return err(e);
16764
+ }
16765
+ });
16766
+ server.tool("browser_check", "RECOMMENDED FIRST CALL: one-shot page summary \u2014 url, title, errors, performance, thumbnail, refs. Replaces 4+ separate tool calls.", { session_id: exports_external.string() }, async ({ session_id }) => {
16767
+ try {
16768
+ const page = getSessionPage(session_id);
16769
+ const info = await getPageInfo(page);
16770
+ const errors2 = getConsoleLog(session_id, "error");
16771
+ info.has_console_errors = errors2.length > 0;
16772
+ let perf = {};
16773
+ try {
16774
+ perf = await getPerformanceMetrics(page);
16775
+ } catch {}
16776
+ let thumbnail_base64 = "";
16777
+ try {
16778
+ const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
16779
+ thumbnail_base64 = ss.base64.length > 50000 ? "" : ss.base64;
16780
+ } catch {}
16781
+ let snapshot_refs = "";
16782
+ let interactive_count = 0;
16783
+ try {
16784
+ const snap = await takeSnapshot(page, session_id);
16785
+ setLastSnapshot(session_id, snap);
16786
+ interactive_count = snap.interactive_count;
16787
+ snapshot_refs = Object.entries(snap.refs).slice(0, 30).map(([ref, i]) => `${i.role}:${i.name.slice(0, 50)} [${ref}]`).join(", ");
16788
+ } catch {}
16789
+ return json({ ...info, error_count: errors2.length, performance: perf, thumbnail_base64, snapshot_refs, interactive_count });
16790
+ } catch (e) {
16791
+ return err(e);
16792
+ }
16793
+ });
16794
+ _startupToolCount = Object.keys(server._registeredTools ?? {}).length;
16795
+ console.error(`@hasna/browser v${_pkg.version} \u2014 ${_startupToolCount} tools | data: ${(await Promise.resolve().then(() => (init_schema(), exports_schema))).getDataDir()}`);
16612
16796
  transport = new StdioServerTransport;
16613
16797
  await server.connect(transport);
16614
16798
  });
@@ -16651,7 +16835,7 @@ var init_snapshots = __esm(() => {
16651
16835
 
16652
16836
  // src/server/index.ts
16653
16837
  var exports_server = {};
16654
- import { join as join7 } from "path";
16838
+ import { join as join8 } from "path";
16655
16839
  import { existsSync as existsSync4 } from "fs";
16656
16840
  function ok(data, status = 200) {
16657
16841
  return new Response(JSON.stringify(data), {
@@ -16945,13 +17129,13 @@ var init_server = __esm(() => {
16945
17129
  const id = path.split("/")[3];
16946
17130
  return ok({ deleted: deleteDownload(id) });
16947
17131
  }
16948
- const dashboardDist = join7(import.meta.dir, "../../dashboard/dist");
17132
+ const dashboardDist = join8(import.meta.dir, "../../dashboard/dist");
16949
17133
  if (existsSync4(dashboardDist)) {
16950
- const filePath = path === "/" ? join7(dashboardDist, "index.html") : join7(dashboardDist, path);
17134
+ const filePath = path === "/" ? join8(dashboardDist, "index.html") : join8(dashboardDist, path);
16951
17135
  if (existsSync4(filePath)) {
16952
17136
  return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
16953
17137
  }
16954
- return new Response(Bun.file(join7(dashboardDist, "index.html")), { headers: CORS_HEADERS });
17138
+ return new Response(Bun.file(join8(dashboardDist, "index.html")), { headers: CORS_HEADERS });
16955
17139
  }
16956
17140
  if (path === "/" || path === "") {
16957
17141
  return new Response("@hasna/browser REST API running. Dashboard not built.", {
@@ -16993,10 +17177,10 @@ init_projects();
16993
17177
  init_recorder();
16994
17178
  init_recordings();
16995
17179
  init_lightpanda();
16996
- import { readFileSync as readFileSync3 } from "fs";
16997
- import { join as join8 } from "path";
17180
+ import { readFileSync as readFileSync4 } from "fs";
17181
+ import { join as join9 } from "path";
16998
17182
  import chalk from "chalk";
16999
- var pkg = JSON.parse(readFileSync3(join8(import.meta.dir, "../../package.json"), "utf8"));
17183
+ var pkg = JSON.parse(readFileSync4(join9(import.meta.dir, "../../package.json"), "utf8"));
17000
17184
  var program2 = new Command;
17001
17185
  program2.name("browser").description("@hasna/browser \u2014 general-purpose browser agent CLI").version(pkg.version);
17002
17186
  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) => {
@@ -1 +1 @@
1
- {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/db/sessions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG/E,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAO9D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAG7D;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAI/D;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAK9C;AAED,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,aAAa,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,EAAE,CAQ/F;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAO9E;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAG9C"}
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/db/sessions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG/E,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAiB9D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAG7D;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAI/D;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAK9C;AAED,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,aAAa,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,EAAE,CAQ/F;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAO9E;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAG9C"}
package/dist/index.js CHANGED
@@ -6827,7 +6827,14 @@ import { randomUUID as randomUUID3 } from "crypto";
6827
6827
  function createSession(data) {
6828
6828
  const db = getDatabase();
6829
6829
  const id = randomUUID3();
6830
- db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, data.name ?? null);
6830
+ let name = data.name ?? null;
6831
+ if (name) {
6832
+ const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
6833
+ if (existing) {
6834
+ name = `${name}-${id.slice(0, 6)}`;
6835
+ }
6836
+ }
6837
+ db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
6831
6838
  return getSession(id);
6832
6839
  }
6833
6840
  function getSessionByName(name) {
@@ -7728,12 +7735,19 @@ async function createSession2(opts = {}) {
7728
7735
  userAgent: opts.userAgent
7729
7736
  });
7730
7737
  }
7738
+ let sessionName = opts.name ?? (opts.startUrl ? (() => {
7739
+ try {
7740
+ return new URL(opts.startUrl).hostname;
7741
+ } catch {
7742
+ return;
7743
+ }
7744
+ })() : undefined);
7731
7745
  const session = createSession({
7732
7746
  engine: resolvedEngine,
7733
7747
  projectId: opts.projectId,
7734
7748
  agentId: opts.agentId,
7735
7749
  startUrl: opts.startUrl,
7736
- name: opts.name ?? (opts.startUrl ? new URL(opts.startUrl).hostname : undefined)
7750
+ name: sessionName
7737
7751
  });
7738
7752
  if (opts.stealth) {
7739
7753
  try {
@@ -8496,11 +8510,20 @@ async function takeScreenshot(page, opts) {
8496
8510
  }
8497
8511
  const originalSizeBytes = rawBuffer.length;
8498
8512
  let finalBuffer;
8499
- if (compress && format !== "png") {
8500
- finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
8501
- } else if (compress && format === "png") {
8502
- finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
8503
- } else {
8513
+ let compressed = true;
8514
+ let fallback = false;
8515
+ try {
8516
+ if (compress && format !== "png") {
8517
+ finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
8518
+ } else if (compress && format === "png") {
8519
+ finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
8520
+ } else {
8521
+ finalBuffer = rawBuffer;
8522
+ compressed = false;
8523
+ }
8524
+ } catch (sharpErr) {
8525
+ fallback = true;
8526
+ compressed = false;
8504
8527
  finalBuffer = rawBuffer;
8505
8528
  }
8506
8529
  const compressedSizeBytes = finalBuffer.length;
@@ -8528,7 +8551,8 @@ async function takeScreenshot(page, opts) {
8528
8551
  compressed_size_bytes: compressedSizeBytes,
8529
8552
  compression_ratio: compressionRatio,
8530
8553
  thumbnail_path: thumbnailPath,
8531
- thumbnail_base64: thumbnailBase64
8554
+ thumbnail_base64: thumbnailBase64,
8555
+ ...fallback ? { fallback: true, compressed: false } : {}
8532
8556
  };
8533
8557
  if (opts?.track !== false) {
8534
8558
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"annotate.d.ts","sourceRoot":"","sources":["../../src/lib/annotate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIvC,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,yBAAyB,CAAC,CAqEpC"}
1
+ {"version":3,"file":"annotate.d.ts","sourceRoot":"","sources":["../../src/lib/annotate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIvC,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAID,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,yBAAyB,CAAC,CAwEpC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=screenshot-v4.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot-v4.test.d.ts","sourceRoot":"","sources":["../../src/lib/screenshot-v4.test.ts"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/lib/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAKvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAgDpG,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,iBAAiB,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACrF,OAAO,CAAC,gBAAgB,CAAC,CA8G3B;AAID,wBAAsB,WAAW,CAC/B,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,UAAU,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACzC,OAAO,CAAC,SAAS,CAAC,CA6BpB"}
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/lib/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAKvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAgDpG,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,iBAAiB,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACrF,OAAO,CAAC,gBAAgB,CAAC,CAyH3B;AAID,wBAAsB,WAAW,CAC/B,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,UAAU,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACzC,OAAO,CAAC,SAAS,CAAC,CA6BpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/lib/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAW,MAAM,mBAAmB,CAAC;AAyB3D,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,wBAAsB,aAAa,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA+D3F;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAWtD;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAI5D;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAIjE;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAIlE;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgBtE;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,aAAa,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,EAAE,CAE/F;AAED,wBAAgB,iBAAiB,IAAI,OAAO,EAAE,CAE7C;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,kBAE5C;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,WAErD;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGxF"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/lib/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAW,MAAM,mBAAmB,CAAC;AAyB3D,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,wBAAsB,aAAa,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAiE3F;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAWtD;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAI5D;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAIjE;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAIlE;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgBtE;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,aAAa,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,EAAE,CAE/F;AAED,wBAAgB,iBAAiB,IAAI,OAAO,EAAE,CAE7C;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,kBAE5C;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,WAErD;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGxF"}
package/dist/mcp/index.js CHANGED
@@ -30,6 +30,12 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
30
30
  var __require = import.meta.require;
31
31
 
32
32
  // src/db/schema.ts
33
+ var exports_schema = {};
34
+ __export(exports_schema, {
35
+ resetDatabase: () => resetDatabase,
36
+ getDatabase: () => getDatabase,
37
+ getDataDir: () => getDataDir
38
+ });
33
39
  import { Database } from "bun:sqlite";
34
40
  import { join } from "path";
35
41
  import { mkdirSync } from "fs";
@@ -55,6 +61,15 @@ function getDatabase(path) {
55
61
  runMigrations(_db);
56
62
  return _db;
57
63
  }
64
+ function resetDatabase() {
65
+ if (_db) {
66
+ try {
67
+ _db.close();
68
+ } catch {}
69
+ }
70
+ _db = null;
71
+ _dbPath = null;
72
+ }
58
73
  function runMigrations(db) {
59
74
  db.exec(`
60
75
  CREATE TABLE IF NOT EXISTS schema_migrations (
@@ -252,12 +267,28 @@ var init_console_log = __esm(() => {
252
267
  });
253
268
 
254
269
  // src/lib/snapshot.ts
270
+ var exports_snapshot = {};
271
+ __export(exports_snapshot, {
272
+ takeSnapshot: () => takeSnapshot,
273
+ setLastSnapshot: () => setLastSnapshot,
274
+ hasRefs: () => hasRefs,
275
+ getSessionRefs: () => getSessionRefs,
276
+ getRefLocator: () => getRefLocator,
277
+ getRefInfo: () => getRefInfo,
278
+ getLastSnapshot: () => getLastSnapshot,
279
+ diffSnapshots: () => diffSnapshots,
280
+ clearSessionRefs: () => clearSessionRefs,
281
+ clearLastSnapshot: () => clearLastSnapshot
282
+ });
255
283
  function getLastSnapshot(sessionId) {
256
284
  return lastSnapshots.get(sessionId) ?? null;
257
285
  }
258
286
  function setLastSnapshot(sessionId, snapshot) {
259
287
  lastSnapshots.set(sessionId, snapshot);
260
288
  }
289
+ function clearLastSnapshot(sessionId) {
290
+ lastSnapshots.delete(sessionId);
291
+ }
261
292
  async function takeSnapshot(page, sessionId) {
262
293
  let ariaTree;
263
294
  try {
@@ -360,6 +391,21 @@ function getRefLocator(page, sessionId, ref) {
360
391
  throw new Error(`Ref ${ref} not found. Available refs: ${[...refMap.keys()].slice(0, 20).join(", ")}`);
361
392
  return page.getByRole(entry.role, { name: entry.name }).first();
362
393
  }
394
+ function getRefInfo(sessionId, ref) {
395
+ const refMap = sessionRefMaps.get(sessionId);
396
+ if (!refMap)
397
+ return null;
398
+ return refMap.get(ref) ?? null;
399
+ }
400
+ function getSessionRefs(sessionId) {
401
+ return sessionRefMaps.get(sessionId) ?? null;
402
+ }
403
+ function clearSessionRefs(sessionId) {
404
+ sessionRefMaps.delete(sessionId);
405
+ }
406
+ function hasRefs(sessionId) {
407
+ return sessionRefMaps.has(sessionId) && (sessionRefMaps.get(sessionId)?.size ?? 0) > 0;
408
+ }
363
409
  function refKey(info) {
364
410
  return `${info.role}::${info.name}`;
365
411
  }
@@ -6833,7 +6879,8 @@ async function annotateScreenshot(page, sessionId) {
6833
6879
  const annotations = [];
6834
6880
  const labelToRef = {};
6835
6881
  let labelCounter = 1;
6836
- for (const [ref, info] of Object.entries(snapshot.refs)) {
6882
+ const refsToAnnotate = Object.entries(snapshot.refs).slice(0, MAX_ANNOTATIONS);
6883
+ for (const [ref, info] of refsToAnnotate) {
6837
6884
  try {
6838
6885
  const locator = page.getByRole(info.role, { name: info.name }).first();
6839
6886
  const box = await locator.boundingBox();
@@ -6872,7 +6919,7 @@ async function annotateScreenshot(page, sessionId) {
6872
6919
  const annotatedBuffer = await import_sharp3.default(rawBuffer).composite([{ input: Buffer.from(svg), top: 0, left: 0 }]).webp({ quality: 85 }).toBuffer();
6873
6920
  return { buffer: annotatedBuffer, annotations, labelToRef };
6874
6921
  }
6875
- var import_sharp3;
6922
+ var import_sharp3, MAX_ANNOTATIONS = 40;
6876
6923
  var init_annotate = __esm(() => {
6877
6924
  init_snapshot();
6878
6925
  import_sharp3 = __toESM(require_lib(), 1);
@@ -10855,6 +10902,10 @@ var coerce = {
10855
10902
  date: (arg) => ZodDate.create({ ...arg, coerce: true })
10856
10903
  };
10857
10904
  var NEVER = INVALID;
10905
+ // src/mcp/index.ts
10906
+ import { readFileSync as readFileSync3 } from "fs";
10907
+ import { join as join7 } from "path";
10908
+
10858
10909
  // src/types/index.ts
10859
10910
  class BrowserError extends Error {
10860
10911
  code;
@@ -10922,7 +10973,14 @@ import { randomUUID } from "crypto";
10922
10973
  function createSession(data) {
10923
10974
  const db = getDatabase();
10924
10975
  const id = randomUUID();
10925
- db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, data.name ?? null);
10976
+ let name = data.name ?? null;
10977
+ if (name) {
10978
+ const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
10979
+ if (existing) {
10980
+ name = `${name}-${id.slice(0, 6)}`;
10981
+ }
10982
+ }
10983
+ db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
10926
10984
  return getSession(id);
10927
10985
  }
10928
10986
  function getSessionByName(name) {
@@ -11424,12 +11482,19 @@ async function createSession2(opts = {}) {
11424
11482
  userAgent: opts.userAgent
11425
11483
  });
11426
11484
  }
11485
+ let sessionName = opts.name ?? (opts.startUrl ? (() => {
11486
+ try {
11487
+ return new URL(opts.startUrl).hostname;
11488
+ } catch {
11489
+ return;
11490
+ }
11491
+ })() : undefined);
11427
11492
  const session = createSession({
11428
11493
  engine: resolvedEngine,
11429
11494
  projectId: opts.projectId,
11430
11495
  agentId: opts.agentId,
11431
11496
  startUrl: opts.startUrl,
11432
- name: opts.name ?? (opts.startUrl ? new URL(opts.startUrl).hostname : undefined)
11497
+ name: sessionName
11433
11498
  });
11434
11499
  if (opts.stealth) {
11435
11500
  try {
@@ -12116,11 +12181,20 @@ async function takeScreenshot(page, opts) {
12116
12181
  }
12117
12182
  const originalSizeBytes = rawBuffer.length;
12118
12183
  let finalBuffer;
12119
- if (compress && format !== "png") {
12120
- finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
12121
- } else if (compress && format === "png") {
12122
- finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
12123
- } else {
12184
+ let compressed = true;
12185
+ let fallback = false;
12186
+ try {
12187
+ if (compress && format !== "png") {
12188
+ finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
12189
+ } else if (compress && format === "png") {
12190
+ finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
12191
+ } else {
12192
+ finalBuffer = rawBuffer;
12193
+ compressed = false;
12194
+ }
12195
+ } catch (sharpErr) {
12196
+ fallback = true;
12197
+ compressed = false;
12124
12198
  finalBuffer = rawBuffer;
12125
12199
  }
12126
12200
  const compressedSizeBytes = finalBuffer.length;
@@ -12148,7 +12222,8 @@ async function takeScreenshot(page, opts) {
12148
12222
  compressed_size_bytes: compressedSizeBytes,
12149
12223
  compression_ratio: compressionRatio,
12150
12224
  thumbnail_path: thumbnailPath,
12151
- thumbnail_base64: thumbnailBase64
12225
+ thumbnail_base64: thumbnailBase64,
12226
+ ...fallback ? { fallback: true, compressed: false } : {}
12152
12227
  };
12153
12228
  if (opts?.track !== false) {
12154
12229
  try {
@@ -13149,6 +13224,7 @@ function deleteProfile(name) {
13149
13224
  }
13150
13225
 
13151
13226
  // src/mcp/index.ts
13227
+ var _pkg = JSON.parse(readFileSync3(join7(import.meta.dir, "../../package.json"), "utf8"));
13152
13228
  var networkLogCleanup = new Map;
13153
13229
  var consoleCaptureCleanup = new Map;
13154
13230
  var harCaptures = new Map;
@@ -13214,13 +13290,50 @@ server.tool("browser_session_close", "Close a browser session", { session_id: ex
13214
13290
  return err(e);
13215
13291
  }
13216
13292
  });
13217
- server.tool("browser_navigate", "Navigate to a URL. Returns title + thumbnail + accessibility snapshot preview with refs.", { session_id: exports_external.string(), url: exports_external.string(), timeout: exports_external.number().optional().default(30000), auto_snapshot: exports_external.boolean().optional().default(true), auto_thumbnail: exports_external.boolean().optional().default(true) }, async ({ session_id, url, timeout, auto_snapshot, auto_thumbnail }) => {
13293
+ server.tool("browser_navigate", "Navigate to a URL. Auto-detects redirects, auto-names session, returns compact refs + thumbnail.", {
13294
+ session_id: exports_external.string(),
13295
+ url: exports_external.string(),
13296
+ timeout: exports_external.number().optional().default(30000),
13297
+ auto_snapshot: exports_external.boolean().optional().default(true),
13298
+ auto_thumbnail: exports_external.boolean().optional().default(true)
13299
+ }, async ({ session_id, url, timeout, auto_snapshot, auto_thumbnail }) => {
13218
13300
  try {
13219
13301
  const page = getSessionPage(session_id);
13220
13302
  await navigate(page, url, timeout);
13221
13303
  const title = await getTitle(page);
13222
13304
  const current_url = await getUrl(page);
13223
- const result = { url, title, current_url };
13305
+ const redirected = current_url !== url && current_url !== url + "/" && url !== current_url.replace(/\/$/, "");
13306
+ let redirect_type;
13307
+ if (redirected) {
13308
+ try {
13309
+ const reqHost = new URL(url).hostname;
13310
+ const resHost = new URL(current_url).hostname;
13311
+ const reqPath = new URL(url).pathname;
13312
+ const resPath = new URL(current_url).pathname;
13313
+ if (reqHost !== resHost)
13314
+ redirect_type = "canonical";
13315
+ else if (resPath.match(/\/[a-z]{2}-[a-z]{2}\//))
13316
+ redirect_type = "geo";
13317
+ else if (current_url.includes("login") || current_url.includes("signin"))
13318
+ redirect_type = "auth";
13319
+ else
13320
+ redirect_type = "unknown";
13321
+ } catch {}
13322
+ }
13323
+ try {
13324
+ const session = getSession2(session_id);
13325
+ if (!session.name) {
13326
+ const hostname = new URL(current_url).hostname;
13327
+ renameSession2(session_id, hostname);
13328
+ }
13329
+ } catch {}
13330
+ const result = {
13331
+ url,
13332
+ title,
13333
+ current_url,
13334
+ redirected,
13335
+ ...redirect_type ? { redirect_type } : {}
13336
+ };
13224
13337
  if (auto_thumbnail) {
13225
13338
  try {
13226
13339
  const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
@@ -13230,7 +13343,9 @@ server.tool("browser_navigate", "Navigate to a URL. Returns title + thumbnail +
13230
13343
  if (auto_snapshot) {
13231
13344
  try {
13232
13345
  const snap = await takeSnapshot(page, session_id);
13233
- result.snapshot_preview = snap.tree.slice(0, 3000);
13346
+ setLastSnapshot(session_id, snap);
13347
+ const refEntries = Object.entries(snap.refs).slice(0, 30);
13348
+ result.snapshot_refs = refEntries.map(([ref, info]) => `${info.role}:${info.name.slice(0, 50)} [${ref}]`).join(", ");
13234
13349
  result.interactive_count = snap.interactive_count;
13235
13350
  result.has_errors = getConsoleLog(session_id, "error").length > 0;
13236
13351
  } catch {}
@@ -13336,7 +13451,7 @@ server.tool("browser_select", "Select a dropdown option by ref or selector", { s
13336
13451
  return err(e);
13337
13452
  }
13338
13453
  });
13339
- server.tool("browser_check", "Check or uncheck a checkbox by ref or selector", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), checked: exports_external.boolean() }, async ({ session_id, selector, ref, checked }) => {
13454
+ server.tool("browser_toggle", "Check or uncheck a checkbox by ref or selector", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), checked: exports_external.boolean() }, async ({ session_id, selector, ref, checked }) => {
13340
13455
  try {
13341
13456
  const page = getSessionPage(session_id);
13342
13457
  if (ref) {
@@ -13427,12 +13542,33 @@ server.tool("browser_find", "Find elements matching a selector and return their
13427
13542
  return err(e);
13428
13543
  }
13429
13544
  });
13430
- server.tool("browser_snapshot", "Get a structured accessibility snapshot with element refs (@e0, @e1...). Use refs in browser_click, browser_type, etc.", { session_id: exports_external.string() }, async ({ session_id }) => {
13545
+ server.tool("browser_snapshot", "Get accessibility snapshot with element refs (@e0, @e1...). Use compact=true (default) for token-efficient output. Use refs in browser_click, browser_type, etc.", {
13546
+ session_id: exports_external.string(),
13547
+ compact: exports_external.boolean().optional().default(true),
13548
+ max_refs: exports_external.number().optional().default(50),
13549
+ full_tree: exports_external.boolean().optional().default(false)
13550
+ }, async ({ session_id, compact, max_refs, full_tree }) => {
13431
13551
  try {
13432
13552
  const page = getSessionPage(session_id);
13433
13553
  const result = await takeSnapshot(page, session_id);
13434
13554
  setLastSnapshot(session_id, result);
13435
- return json({ snapshot: result.tree, refs: result.refs, interactive_count: result.interactive_count });
13555
+ const refEntries = Object.entries(result.refs).slice(0, max_refs);
13556
+ const limitedRefs = Object.fromEntries(refEntries);
13557
+ const truncated = Object.keys(result.refs).length > max_refs;
13558
+ if (compact && !full_tree) {
13559
+ const compactRefs = refEntries.map(([ref, info]) => `${info.role}:${info.name.slice(0, 60)} [${ref}]${info.checked !== undefined ? ` checked=${info.checked}` : ""}${!info.enabled ? " disabled" : ""}`).join(`
13560
+ `);
13561
+ return json({
13562
+ snapshot_compact: compactRefs,
13563
+ interactive_count: result.interactive_count,
13564
+ shown_count: refEntries.length,
13565
+ truncated,
13566
+ refs: limitedRefs
13567
+ });
13568
+ }
13569
+ const tree = full_tree ? result.tree : result.tree.slice(0, 5000) + (result.tree.length > 5000 ? `
13570
+ ... (truncated \u2014 use full_tree=true for complete)` : "");
13571
+ return json({ snapshot: tree, refs: limitedRefs, interactive_count: result.interactive_count, truncated });
13436
13572
  } catch (e) {
13437
13573
  return err(e);
13438
13574
  }
@@ -13914,40 +14050,6 @@ server.tool("browser_watch_stop", "Stop a DOM change watcher", { watch_id: expor
13914
14050
  return err(e);
13915
14051
  }
13916
14052
  });
13917
- server.tool("browser_page_check", "One-call page summary: page info + console errors + performance metrics + thumbnail + accessibility snapshot preview. Replaces 4-5 separate tool calls.", { session_id: exports_external.string() }, async ({ session_id }) => {
13918
- try {
13919
- const page = getSessionPage(session_id);
13920
- const info = await getPageInfo(page);
13921
- const errors2 = getConsoleLog(session_id, "error");
13922
- info.has_console_errors = errors2.length > 0;
13923
- let perf = {};
13924
- try {
13925
- perf = await getPerformanceMetrics(page);
13926
- } catch {}
13927
- let thumbnail_base64 = "";
13928
- try {
13929
- const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
13930
- thumbnail_base64 = ss.base64;
13931
- } catch {}
13932
- let snapshot_preview = "";
13933
- let interactive_count = 0;
13934
- try {
13935
- const snap = await takeSnapshot(page, session_id);
13936
- snapshot_preview = snap.tree.slice(0, 2000);
13937
- interactive_count = snap.interactive_count;
13938
- } catch {}
13939
- return json({
13940
- ...info,
13941
- error_count: errors2.length,
13942
- performance: perf,
13943
- thumbnail_base64: thumbnail_base64.length > 50000 ? "" : thumbnail_base64,
13944
- snapshot_preview,
13945
- interactive_count
13946
- });
13947
- } catch (e) {
13948
- return err(e);
13949
- }
13950
- });
13951
14053
  server.tool("browser_gallery_list", "List screenshot gallery entries with optional filters", {
13952
14054
  project_id: exports_external.string().optional(),
13953
14055
  session_id: exports_external.string().optional(),
@@ -14272,7 +14374,7 @@ server.tool("browser_help", "Show all available browser tools grouped by categor
14272
14374
  { tool: "browser_hover", description: "Hover over an element" },
14273
14375
  { tool: "browser_scroll", description: "Scroll the page" },
14274
14376
  { tool: "browser_select", description: "Select a dropdown option" },
14275
- { tool: "browser_check", description: "Check/uncheck a checkbox" },
14377
+ { tool: "browser_toggle", description: "Check/uncheck a checkbox" },
14276
14378
  { tool: "browser_upload", description: "Upload a file to an input" },
14277
14379
  { tool: "browser_press_key", description: "Press a keyboard key" },
14278
14380
  { tool: "browser_wait", description: "Wait for a selector to appear" },
@@ -14292,9 +14394,10 @@ server.tool("browser_help", "Show all available browser tools grouped by categor
14292
14394
  { tool: "browser_evaluate", description: "Execute JavaScript in page context" }
14293
14395
  ],
14294
14396
  Capture: [
14295
- { tool: "browser_screenshot", description: "Take a screenshot (PNG/JPEG/WebP)" },
14397
+ { tool: "browser_screenshot", description: "Take a screenshot (PNG/JPEG/WebP, annotate=true for labels)" },
14296
14398
  { tool: "browser_pdf", description: "Generate a PDF of the page" },
14297
- { tool: "browser_scroll_and_screenshot", description: "Scroll then screenshot in one call" }
14399
+ { tool: "browser_scroll_and_screenshot", description: "Scroll then screenshot in one call" },
14400
+ { tool: "browser_scroll_to_element", description: "Scroll element into view + screenshot" }
14298
14401
  ],
14299
14402
  Storage: [
14300
14403
  { tool: "browser_cookies_get", description: "Get cookies" },
@@ -14373,7 +14476,8 @@ server.tool("browser_help", "Show all available browser tools grouped by categor
14373
14476
  { tool: "browser_tab_close", description: "Close a tab by index" }
14374
14477
  ],
14375
14478
  Meta: [
14376
- { tool: "browser_page_check", description: "One-call page summary with diagnostics" },
14479
+ { tool: "browser_check", description: "RECOMMENDED: One-call page summary with diagnostics" },
14480
+ { tool: "browser_version", description: "Show running binary version and tool count" },
14377
14481
  { tool: "browser_help", description: "Show this help (all tools)" },
14378
14482
  { tool: "browser_snapshot_diff", description: "Diff current snapshot vs previous" },
14379
14483
  { tool: "browser_watch_start", description: "Watch page for DOM changes" },
@@ -14387,5 +14491,87 @@ server.tool("browser_help", "Show all available browser tools grouped by categor
14387
14491
  return err(e);
14388
14492
  }
14389
14493
  });
14494
+ server.tool("browser_version", "Get the running browser MCP version, tool count, and environment info. Use this to verify which binary is active.", {}, async () => {
14495
+ try {
14496
+ const { getDataDir: getDataDir4 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
14497
+ const toolCount = Object.keys(server._registeredTools ?? {}).length;
14498
+ return json({
14499
+ version: _pkg.version,
14500
+ mcp_tools_count: toolCount,
14501
+ bun_version: Bun.version,
14502
+ data_dir: getDataDir4(),
14503
+ node_env: process.env["NODE_ENV"] ?? "production"
14504
+ });
14505
+ } catch (e) {
14506
+ return err(e);
14507
+ }
14508
+ });
14509
+ server.tool("browser_scroll_to_element", "Scroll an element into view (by ref or selector) then optionally take a screenshot of it. Replaces scroll + wait + screenshot pattern.", {
14510
+ session_id: exports_external.string(),
14511
+ selector: exports_external.string().optional(),
14512
+ ref: exports_external.string().optional(),
14513
+ screenshot: exports_external.boolean().optional().default(true),
14514
+ wait_ms: exports_external.number().optional().default(200)
14515
+ }, async ({ session_id, selector, ref, screenshot: doScreenshot, wait_ms }) => {
14516
+ try {
14517
+ const page = getSessionPage(session_id);
14518
+ let locator;
14519
+ if (ref) {
14520
+ const { getRefLocator: getRefLocator2 } = await Promise.resolve().then(() => (init_snapshot(), exports_snapshot));
14521
+ locator = getRefLocator2(page, session_id, ref);
14522
+ } else if (selector) {
14523
+ locator = page.locator(selector).first();
14524
+ } else {
14525
+ return err(new Error("Either ref or selector is required"));
14526
+ }
14527
+ await locator.scrollIntoViewIfNeeded();
14528
+ await new Promise((r) => setTimeout(r, wait_ms));
14529
+ const result = { scrolled: ref ?? selector };
14530
+ if (doScreenshot) {
14531
+ try {
14532
+ const ss = await takeScreenshot(page, { selector, track: false });
14533
+ ss.url = page.url();
14534
+ if (ss.base64.length > 50000) {
14535
+ ss.base64_truncated = true;
14536
+ ss.base64 = ss.thumbnail_base64 ?? "";
14537
+ }
14538
+ result.screenshot = ss;
14539
+ } catch {}
14540
+ }
14541
+ return json(result);
14542
+ } catch (e) {
14543
+ return err(e);
14544
+ }
14545
+ });
14546
+ server.tool("browser_check", "RECOMMENDED FIRST CALL: one-shot page summary \u2014 url, title, errors, performance, thumbnail, refs. Replaces 4+ separate tool calls.", { session_id: exports_external.string() }, async ({ session_id }) => {
14547
+ try {
14548
+ const page = getSessionPage(session_id);
14549
+ const info = await getPageInfo(page);
14550
+ const errors2 = getConsoleLog(session_id, "error");
14551
+ info.has_console_errors = errors2.length > 0;
14552
+ let perf = {};
14553
+ try {
14554
+ perf = await getPerformanceMetrics(page);
14555
+ } catch {}
14556
+ let thumbnail_base64 = "";
14557
+ try {
14558
+ const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
14559
+ thumbnail_base64 = ss.base64.length > 50000 ? "" : ss.base64;
14560
+ } catch {}
14561
+ let snapshot_refs = "";
14562
+ let interactive_count = 0;
14563
+ try {
14564
+ const snap = await takeSnapshot(page, session_id);
14565
+ setLastSnapshot(session_id, snap);
14566
+ interactive_count = snap.interactive_count;
14567
+ snapshot_refs = Object.entries(snap.refs).slice(0, 30).map(([ref, i]) => `${i.role}:${i.name.slice(0, 50)} [${ref}]`).join(", ");
14568
+ } catch {}
14569
+ return json({ ...info, error_count: errors2.length, performance: perf, thumbnail_base64, snapshot_refs, interactive_count });
14570
+ } catch (e) {
14571
+ return err(e);
14572
+ }
14573
+ });
14574
+ var _startupToolCount = Object.keys(server._registeredTools ?? {}).length;
14575
+ console.error(`@hasna/browser v${_pkg.version} \u2014 ${_startupToolCount} tools | data: ${(await Promise.resolve().then(() => (init_schema(), exports_schema))).getDataDir()}`);
14390
14576
  var transport = new StdioServerTransport;
14391
14577
  await server.connect(transport);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=v4.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v4.test.d.ts","sourceRoot":"","sources":["../../src/mcp/v4.test.ts"],"names":[],"mappings":""}
@@ -6881,7 +6881,14 @@ import { randomUUID } from "crypto";
6881
6881
  function createSession(data) {
6882
6882
  const db = getDatabase();
6883
6883
  const id = randomUUID();
6884
- db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, data.name ?? null);
6884
+ let name = data.name ?? null;
6885
+ if (name) {
6886
+ const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
6887
+ if (existing) {
6888
+ name = `${name}-${id.slice(0, 6)}`;
6889
+ }
6890
+ }
6891
+ db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
6885
6892
  return getSession(id);
6886
6893
  }
6887
6894
  function getSession(id) {
@@ -7361,12 +7368,19 @@ async function createSession2(opts = {}) {
7361
7368
  userAgent: opts.userAgent
7362
7369
  });
7363
7370
  }
7371
+ let sessionName = opts.name ?? (opts.startUrl ? (() => {
7372
+ try {
7373
+ return new URL(opts.startUrl).hostname;
7374
+ } catch {
7375
+ return;
7376
+ }
7377
+ })() : undefined);
7364
7378
  const session = createSession({
7365
7379
  engine: resolvedEngine,
7366
7380
  projectId: opts.projectId,
7367
7381
  agentId: opts.agentId,
7368
7382
  startUrl: opts.startUrl,
7369
- name: opts.name ?? (opts.startUrl ? new URL(opts.startUrl).hostname : undefined)
7383
+ name: sessionName
7370
7384
  });
7371
7385
  if (opts.stealth) {
7372
7386
  try {
@@ -7744,11 +7758,20 @@ async function takeScreenshot(page, opts) {
7744
7758
  }
7745
7759
  const originalSizeBytes = rawBuffer.length;
7746
7760
  let finalBuffer;
7747
- if (compress && format !== "png") {
7748
- finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
7749
- } else if (compress && format === "png") {
7750
- finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
7751
- } else {
7761
+ let compressed = true;
7762
+ let fallback = false;
7763
+ try {
7764
+ if (compress && format !== "png") {
7765
+ finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
7766
+ } else if (compress && format === "png") {
7767
+ finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
7768
+ } else {
7769
+ finalBuffer = rawBuffer;
7770
+ compressed = false;
7771
+ }
7772
+ } catch (sharpErr) {
7773
+ fallback = true;
7774
+ compressed = false;
7752
7775
  finalBuffer = rawBuffer;
7753
7776
  }
7754
7777
  const compressedSizeBytes = finalBuffer.length;
@@ -7776,7 +7799,8 @@ async function takeScreenshot(page, opts) {
7776
7799
  compressed_size_bytes: compressedSizeBytes,
7777
7800
  compression_ratio: compressionRatio,
7778
7801
  thumbnail_path: thumbnailPath,
7779
- thumbnail_base64: thumbnailBase64
7802
+ thumbnail_base64: thumbnailBase64,
7803
+ ...fallback ? { fallback: true, compressed: false } : {}
7780
7804
  };
7781
7805
  if (opts?.track !== false) {
7782
7806
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/browser",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "General-purpose browser agent toolkit — Playwright, Chrome DevTools Protocol, Lightpanda with auto engine selection. CLI + MCP + REST + SDK.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",