import { createRequire } from "node:module"; // ── Windows Node.js compatibility (auto-generated) ── import { fileURLToPath as _ftp } from "node:url"; import { dirname as _dn } from "node:path"; const __browseNodeSrcDir = _dn(_dn(_ftp(import.meta.url))) + "/src"; { const _r = createRequire(import.meta.url); _r("./bun-polyfill.cjs"); } // ── end compatibility ── var __defProp = Object.defineProperty; var __returnValue = (v) => v; function __exportSetter(name, newValue) { this[name] = __returnValue.bind(null, newValue); } var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true, configurable: true, set: __exportSetter.bind(all, name) }); }; var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res); var __require = /* @__PURE__ */ createRequire(import.meta.url); // browse/src/buffers.ts class CircularBuffer { buffer; head = 0; _size = 0; _totalAdded = 0; capacity; constructor(capacity) { this.capacity = capacity; this.buffer = new Array(capacity); } push(entry) { const index = (this.head + this._size) % this.capacity; this.buffer[index] = entry; if (this._size < this.capacity) { this._size++; } else { this.head = (this.head + 1) % this.capacity; } this._totalAdded++; } toArray() { const result = []; for (let i = 0;i < this._size; i++) { result.push(this.buffer[(this.head + i) % this.capacity]); } return result; } last(n) { const count = Math.min(n, this._size); const result = []; const start = (this.head + this._size - count) % this.capacity; for (let i = 0;i < count; i++) { result.push(this.buffer[(start + i) % this.capacity]); } return result; } get length() { return this._size; } get totalAdded() { return this._totalAdded; } clear() { this.head = 0; this._size = 0; } get(index) { if (index < 0 || index >= this._size) return; return this.buffer[(this.head + index) % this.capacity]; } set(index, entry) { if (index < 0 || index >= this._size) return; this.buffer[(this.head + index) % this.capacity] = entry; } } function addConsoleEntry(entry) { consoleBuffer.push(entry); } function addNetworkEntry(entry) { networkBuffer.push(entry); } function addDialogEntry(entry) { dialogBuffer.push(entry); } var HIGH_WATER_MARK = 50000, consoleBuffer, networkBuffer, dialogBuffer; var init_buffers = __esm(() => { consoleBuffer = new CircularBuffer(HIGH_WATER_MARK); networkBuffer = new CircularBuffer(HIGH_WATER_MARK); dialogBuffer = new CircularBuffer(HIGH_WATER_MARK); }); // browse/src/url-validation.ts function normalizeHostname(hostname) { let h = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname; if (h.endsWith(".")) h = h.slice(0, -1); return h; } function isMetadataIp(hostname) { try { const probe = new URL(`http://${hostname}`); const normalized = probe.hostname; if (BLOCKED_METADATA_HOSTS.has(normalized)) return true; if (normalized.endsWith(".") && BLOCKED_METADATA_HOSTS.has(normalized.slice(0, -1))) return true; } catch {} return false; } function validateNavigationUrl(url) { let parsed; try { parsed = new URL(url); } catch { throw new Error(`Invalid URL: ${url}`); } if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { throw new Error(`Blocked: scheme "${parsed.protocol}" is not allowed. Only http: and https: URLs are permitted.`); } const hostname = normalizeHostname(parsed.hostname.toLowerCase()); if (BLOCKED_METADATA_HOSTS.has(hostname) || isMetadataIp(hostname)) { throw new Error(`Blocked: ${parsed.hostname} is a cloud metadata endpoint. Access is denied for security.`); } } var BLOCKED_METADATA_HOSTS; var init_url_validation = __esm(() => { BLOCKED_METADATA_HOSTS = new Set([ "169.254.169.254", "fd00::", "metadata.google.internal" ]); }); // browse/src/platform.ts import * as os from "os"; import * as path from "path"; function isPathWithin(resolvedPath, dir) { return resolvedPath === dir || resolvedPath.startsWith(dir + path.sep); } var IS_WINDOWS, TEMP_DIR; var init_platform = __esm(() => { IS_WINDOWS = process.platform === "win32"; TEMP_DIR = IS_WINDOWS ? os.tmpdir() : "/tmp"; }); // browse/src/read-commands.ts var exports_read_commands = {}; __export(exports_read_commands, { validateReadPath: () => validateReadPath, handleReadCommand: () => handleReadCommand, getCleanText: () => getCleanText }); import * as fs from "fs"; import * as path2 from "path"; function hasAwait(code) { const stripped = code.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, ""); return /\bawait\b/.test(stripped); } function needsBlockWrapper(code) { const trimmed = code.trim(); if (trimmed.split(` `).length > 1) return true; if (/\b(const|let|var|function|class|return|throw|if|for|while|switch|try)\b/.test(trimmed)) return true; if (trimmed.includes(";")) return true; return false; } function wrapForEvaluate(code) { if (!hasAwait(code)) return code; const trimmed = code.trim(); return needsBlockWrapper(trimmed) ? `(async()=>{ ${code} })()` : `(async()=>(${trimmed}))()`; } function validateReadPath(filePath) { if (path2.isAbsolute(filePath)) { const resolved = path2.resolve(filePath); const isSafe = SAFE_DIRECTORIES.some((dir) => isPathWithin(resolved, dir)); if (!isSafe) { throw new Error(`Absolute path must be within: ${SAFE_DIRECTORIES.join(", ")}`); } } const normalized = path2.normalize(filePath); if (normalized.includes("..")) { throw new Error("Path traversal sequences (..) are not allowed"); } } async function getCleanText(page) { return await page.evaluate(() => { const body = document.body; if (!body) return ""; const clone = body.cloneNode(true); clone.querySelectorAll("script, style, noscript, svg").forEach((el) => el.remove()); return clone.innerText.split(` `).map((line) => line.trim()).filter((line) => line.length > 0).join(` `); }); } async function handleReadCommand(command, args, bm) { const page = bm.getPage(); switch (command) { case "text": { return await getCleanText(page); } case "html": { const selector = args[0]; if (selector) { const resolved = await bm.resolveRef(selector); if ("locator" in resolved) { return await resolved.locator.innerHTML({ timeout: 5000 }); } return await page.innerHTML(resolved.selector); } return await page.content(); } case "links": { const links = await page.evaluate(() => [...document.querySelectorAll("a[href]")].map((a) => ({ text: a.textContent?.trim().slice(0, 120) || "", href: a.href })).filter((l) => l.text && l.href)); return links.map((l) => `${l.text} → ${l.href}`).join(` `); } case "forms": { const forms = await page.evaluate(() => { return [...document.querySelectorAll("form")].map((form, i) => { const fields = [...form.querySelectorAll("input, select, textarea")].map((el) => { const input = el; return { tag: el.tagName.toLowerCase(), type: input.type || undefined, name: input.name || undefined, id: input.id || undefined, placeholder: input.placeholder || undefined, required: input.required || undefined, value: input.type === "password" ? "[redacted]" : input.value || undefined, options: el.tagName === "SELECT" ? [...el.options].map((o) => ({ value: o.value, text: o.text })) : undefined }; }); return { index: i, action: form.action || undefined, method: form.method || "get", id: form.id || undefined, fields }; }); }); return JSON.stringify(forms, null, 2); } case "accessibility": { const snapshot = await page.locator("body").ariaSnapshot(); return snapshot; } case "js": { const expr = args[0]; if (!expr) throw new Error("Usage: browse js "); const wrapped = wrapForEvaluate(expr); const result = await page.evaluate(wrapped); return typeof result === "object" ? JSON.stringify(result, null, 2) : String(result ?? ""); } case "eval": { const filePath = args[0]; if (!filePath) throw new Error("Usage: browse eval "); validateReadPath(filePath); if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`); const code = fs.readFileSync(filePath, "utf-8"); const wrapped = wrapForEvaluate(code); const result = await page.evaluate(wrapped); return typeof result === "object" ? JSON.stringify(result, null, 2) : String(result ?? ""); } case "css": { const [selector, property] = args; if (!selector || !property) throw new Error("Usage: browse css "); const resolved = await bm.resolveRef(selector); if ("locator" in resolved) { const value2 = await resolved.locator.evaluate((el, prop) => getComputedStyle(el).getPropertyValue(prop), property); return value2; } const value = await page.evaluate(([sel, prop]) => { const el = document.querySelector(sel); if (!el) return `Element not found: ${sel}`; return getComputedStyle(el).getPropertyValue(prop); }, [resolved.selector, property]); return value; } case "attrs": { const selector = args[0]; if (!selector) throw new Error("Usage: browse attrs "); const resolved = await bm.resolveRef(selector); if ("locator" in resolved) { const attrs2 = await resolved.locator.evaluate((el) => { const result = {}; for (const attr of el.attributes) { result[attr.name] = attr.value; } return result; }); return JSON.stringify(attrs2, null, 2); } const attrs = await page.evaluate((sel) => { const el = document.querySelector(sel); if (!el) return `Element not found: ${sel}`; const result = {}; for (const attr of el.attributes) { result[attr.name] = attr.value; } return result; }, resolved.selector); return typeof attrs === "string" ? attrs : JSON.stringify(attrs, null, 2); } case "console": { if (args[0] === "--clear") { consoleBuffer.clear(); return "Console buffer cleared."; } const entries = args[0] === "--errors" ? consoleBuffer.toArray().filter((e) => e.level === "error" || e.level === "warning") : consoleBuffer.toArray(); if (entries.length === 0) return args[0] === "--errors" ? "(no console errors)" : "(no console messages)"; return entries.map((e) => `[${new Date(e.timestamp).toISOString()}] [${e.level}] ${e.text}`).join(` `); } case "network": { if (args[0] === "--clear") { networkBuffer.clear(); return "Network buffer cleared."; } if (networkBuffer.length === 0) return "(no network requests)"; return networkBuffer.toArray().map((e) => `${e.method} ${e.url} → ${e.status || "pending"} (${e.duration || "?"}ms, ${e.size || "?"}B)`).join(` `); } case "dialog": { if (args[0] === "--clear") { dialogBuffer.clear(); return "Dialog buffer cleared."; } if (dialogBuffer.length === 0) return "(no dialogs captured)"; return dialogBuffer.toArray().map((e) => `[${new Date(e.timestamp).toISOString()}] [${e.type}] "${e.message}" → ${e.action}${e.response ? ` "${e.response}"` : ""}`).join(` `); } case "is": { const property = args[0]; const selector = args[1]; if (!property || !selector) throw new Error(`Usage: browse is Properties: visible, hidden, enabled, disabled, checked, editable, focused`); const resolved = await bm.resolveRef(selector); let locator; if ("locator" in resolved) { locator = resolved.locator; } else { locator = page.locator(resolved.selector); } switch (property) { case "visible": return String(await locator.isVisible()); case "hidden": return String(await locator.isHidden()); case "enabled": return String(await locator.isEnabled()); case "disabled": return String(await locator.isDisabled()); case "checked": return String(await locator.isChecked()); case "editable": return String(await locator.isEditable()); case "focused": { const isFocused = await locator.evaluate((el) => el === document.activeElement); return String(isFocused); } default: throw new Error(`Unknown property: ${property}. Use: visible, hidden, enabled, disabled, checked, editable, focused`); } } case "cookies": { const cookies = await page.context().cookies(); return JSON.stringify(cookies, null, 2); } case "storage": { if (args[0] === "set" && args[1]) { const key = args[1]; const value = args[2] || ""; await page.evaluate(([k, v]) => localStorage.setItem(k, v), [key, value]); return `Set localStorage["${key}"]`; } const storage = await page.evaluate(() => ({ localStorage: { ...localStorage }, sessionStorage: { ...sessionStorage } })); return JSON.stringify(storage, null, 2); } case "perf": { const timings = await page.evaluate(() => { const nav = performance.getEntriesByType("navigation")[0]; if (!nav) return "No navigation timing data available."; return { dns: Math.round(nav.domainLookupEnd - nav.domainLookupStart), tcp: Math.round(nav.connectEnd - nav.connectStart), ssl: Math.round(nav.secureConnectionStart > 0 ? nav.connectEnd - nav.secureConnectionStart : 0), ttfb: Math.round(nav.responseStart - nav.requestStart), download: Math.round(nav.responseEnd - nav.responseStart), domParse: Math.round(nav.domInteractive - nav.responseEnd), domReady: Math.round(nav.domContentLoadedEventEnd - nav.startTime), load: Math.round(nav.loadEventEnd - nav.startTime), total: Math.round(nav.loadEventEnd - nav.startTime) }; }); if (typeof timings === "string") return timings; return Object.entries(timings).map(([k, v]) => `${k.padEnd(12)} ${v}ms`).join(` `); } default: throw new Error(`Unknown read command: ${command}`); } } var SAFE_DIRECTORIES; var init_read_commands = __esm(() => { init_buffers(); init_platform(); SAFE_DIRECTORIES = [TEMP_DIR, process.cwd()]; }); // browse/src/cookie-import-browser.ts const Database = null; // bun:sqlite stubbed on Node import * as crypto from "crypto"; import * as fs2 from "fs"; import * as path3 from "path"; import * as os2 from "os"; function findInstalledBrowsers() { const appSupport = path3.join(os2.homedir(), "Library", "Application Support"); return BROWSER_REGISTRY.filter((b) => { const dbPath = path3.join(appSupport, b.dataDir, "Default", "Cookies"); try { return fs2.existsSync(dbPath); } catch { return false; } }); } function listDomains(browserName, profile = "Default") { const browser = resolveBrowser(browserName); const dbPath = getCookieDbPath(browser, profile); const db = openDb(dbPath, browser.name); try { const now = chromiumNow(); const rows = db.query(`SELECT host_key AS domain, COUNT(*) AS count FROM cookies WHERE has_expires = 0 OR expires_utc > ? GROUP BY host_key ORDER BY count DESC`).all(now); return { domains: rows, browser: browser.name }; } finally { db.close(); } } async function importCookies(browserName, domains, profile = "Default") { if (domains.length === 0) return { cookies: [], count: 0, failed: 0, domainCounts: {} }; const browser = resolveBrowser(browserName); const derivedKey = await getDerivedKey(browser); const dbPath = getCookieDbPath(browser, profile); const db = openDb(dbPath, browser.name); try { const now = chromiumNow(); const placeholders = domains.map(() => "?").join(","); const rows = db.query(`SELECT host_key, name, value, encrypted_value, path, expires_utc, is_secure, is_httponly, has_expires, samesite FROM cookies WHERE host_key IN (${placeholders}) AND (has_expires = 0 OR expires_utc > ?) ORDER BY host_key, name`).all(...domains, now); const cookies = []; let failed = 0; const domainCounts = {}; for (const row of rows) { try { const value = decryptCookieValue(row, derivedKey); const cookie = toPlaywrightCookie(row, value); cookies.push(cookie); domainCounts[row.host_key] = (domainCounts[row.host_key] || 0) + 1; } catch { failed++; } } return { cookies, count: cookies.length, failed, domainCounts }; } finally { db.close(); } } function resolveBrowser(nameOrAlias) { const needle = nameOrAlias.toLowerCase().trim(); const found = BROWSER_REGISTRY.find((b) => b.aliases.includes(needle) || b.name.toLowerCase() === needle); if (!found) { const supported = BROWSER_REGISTRY.flatMap((b) => b.aliases).join(", "); throw new CookieImportError(`Unknown browser '${nameOrAlias}'. Supported: ${supported}`, "unknown_browser"); } return found; } function validateProfile(profile) { if (/[/\\]|\.\./.test(profile) || /[\x00-\x1f]/.test(profile)) { throw new CookieImportError(`Invalid profile name: '${profile}'`, "bad_request"); } } function getCookieDbPath(browser, profile) { validateProfile(profile); const appSupport = path3.join(os2.homedir(), "Library", "Application Support"); const dbPath = path3.join(appSupport, browser.dataDir, profile, "Cookies"); if (!fs2.existsSync(dbPath)) { throw new CookieImportError(`${browser.name} is not installed (no cookie database at ${dbPath})`, "not_installed"); } return dbPath; } function openDb(dbPath, browserName) { try { return new Database(dbPath, { readonly: true }); } catch (err) { if (err.message?.includes("SQLITE_BUSY") || err.message?.includes("database is locked")) { return openDbFromCopy(dbPath, browserName); } if (err.message?.includes("SQLITE_CORRUPT") || err.message?.includes("malformed")) { throw new CookieImportError(`Cookie database for ${browserName} is corrupt`, "db_corrupt"); } throw err; } } function openDbFromCopy(dbPath, browserName) { const tmpPath = `/tmp/browse-cookies-${browserName.toLowerCase()}-${crypto.randomUUID()}.db`; try { fs2.copyFileSync(dbPath, tmpPath); const walPath = dbPath + "-wal"; const shmPath = dbPath + "-shm"; if (fs2.existsSync(walPath)) fs2.copyFileSync(walPath, tmpPath + "-wal"); if (fs2.existsSync(shmPath)) fs2.copyFileSync(shmPath, tmpPath + "-shm"); const db = new Database(tmpPath, { readonly: true }); const origClose = db.close.bind(db); db.close = () => { origClose(); try { fs2.unlinkSync(tmpPath); } catch {} try { fs2.unlinkSync(tmpPath + "-wal"); } catch {} try { fs2.unlinkSync(tmpPath + "-shm"); } catch {} }; return db; } catch { try { fs2.unlinkSync(tmpPath); } catch {} throw new CookieImportError(`Cookie database is locked (${browserName} may be running). Try closing ${browserName} first.`, "db_locked", "retry"); } } async function getDerivedKey(browser) { const cached = keyCache.get(browser.keychainService); if (cached) return cached; const password = await getKeychainPassword(browser.keychainService); const derived = crypto.pbkdf2Sync(password, "saltysalt", 1003, 16, "sha1"); keyCache.set(browser.keychainService, derived); return derived; } async function getKeychainPassword(service) { const proc = Bun.spawn(["security", "find-generic-password", "-s", service, "-w"], { stdout: "pipe", stderr: "pipe" }); const timeout = new Promise((_, reject) => setTimeout(() => { proc.kill(); reject(new CookieImportError(`macOS is waiting for Keychain permission. Look for a dialog asking to allow access to "${service}".`, "keychain_timeout", "retry")); }, 1e4)); try { const exitCode = await Promise.race([proc.exited, timeout]); const stdout = await new Response(proc.stdout).text(); const stderr = await new Response(proc.stderr).text(); if (exitCode !== 0) { const errText = stderr.trim().toLowerCase(); if (errText.includes("user canceled") || errText.includes("denied") || errText.includes("interaction not allowed")) { throw new CookieImportError(`Keychain access denied. Click "Allow" in the macOS dialog for "${service}".`, "keychain_denied", "retry"); } if (errText.includes("could not be found") || errText.includes("not found")) { throw new CookieImportError(`No Keychain entry for "${service}". Is this a Chromium-based browser?`, "keychain_not_found"); } throw new CookieImportError(`Could not read Keychain: ${stderr.trim()}`, "keychain_error", "retry"); } return stdout.trim(); } catch (err) { if (err instanceof CookieImportError) throw err; throw new CookieImportError(`Could not read Keychain: ${err.message}`, "keychain_error", "retry"); } } function decryptCookieValue(row, key) { if (row.value && row.value.length > 0) return row.value; const ev = Buffer.from(row.encrypted_value); if (ev.length === 0) return ""; const prefix = ev.slice(0, 3).toString("utf-8"); if (prefix !== "v10") { throw new Error(`Unknown encryption prefix: ${prefix}`); } const ciphertext = ev.slice(3); const iv = Buffer.alloc(16, 32); const decipher = crypto.createDecipheriv("aes-128-cbc", key, iv); const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]); if (plaintext.length <= 32) return ""; return plaintext.slice(32).toString("utf-8"); } function toPlaywrightCookie(row, value) { return { name: row.name, value, domain: row.host_key, path: row.path || "/", expires: chromiumEpochToUnix(row.expires_utc, row.has_expires), secure: row.is_secure === 1, httpOnly: row.is_httponly === 1, sameSite: mapSameSite(row.samesite) }; } function chromiumNow() { return BigInt(Date.now()) * 1000n + CHROMIUM_EPOCH_OFFSET; } function chromiumEpochToUnix(epoch, hasExpires) { if (hasExpires === 0 || epoch === 0 || epoch === 0n) return -1; const epochBig = BigInt(epoch); const unixMicro = epochBig - CHROMIUM_EPOCH_OFFSET; return Number(unixMicro / 1000000n); } function mapSameSite(value) { switch (value) { case 0: return "None"; case 1: return "Lax"; case 2: return "Strict"; default: return "Lax"; } } var CookieImportError, BROWSER_REGISTRY, keyCache, CHROMIUM_EPOCH_OFFSET = 11644473600000000n; var init_cookie_import_browser = __esm(() => { CookieImportError = class CookieImportError extends Error { code; action; constructor(message, code, action) { super(message); this.code = code; this.action = action; this.name = "CookieImportError"; } }; BROWSER_REGISTRY = [ { name: "Comet", dataDir: "Comet/", keychainService: "Comet Safe Storage", aliases: ["comet", "perplexity"] }, { name: "Chrome", dataDir: "Google/Chrome/", keychainService: "Chrome Safe Storage", aliases: ["chrome", "google-chrome"] }, { name: "Arc", dataDir: "Arc/User Data/", keychainService: "Arc Safe Storage", aliases: ["arc"] }, { name: "Brave", dataDir: "BraveSoftware/Brave-Browser/", keychainService: "Brave Safe Storage", aliases: ["brave"] }, { name: "Edge", dataDir: "Microsoft Edge/", keychainService: "Microsoft Edge Safe Storage", aliases: ["edge"] } ]; keyCache = new Map; }); // browse/src/write-commands.ts var exports_write_commands = {}; __export(exports_write_commands, { handleWriteCommand: () => handleWriteCommand }); import * as fs3 from "fs"; import * as path4 from "path"; async function handleWriteCommand(command, args, bm) { const page = bm.getPage(); switch (command) { case "goto": { const url = args[0]; if (!url) throw new Error("Usage: browse goto "); validateNavigationUrl(url); const response = await page.goto(url, { waitUntil: "domcontentloaded", timeout: 15000 }); const status = response?.status() || "unknown"; return `Navigated to ${url} (${status})`; } case "back": { await page.goBack({ waitUntil: "domcontentloaded", timeout: 15000 }); return `Back → ${page.url()}`; } case "forward": { await page.goForward({ waitUntil: "domcontentloaded", timeout: 15000 }); return `Forward → ${page.url()}`; } case "reload": { await page.reload({ waitUntil: "domcontentloaded", timeout: 15000 }); return `Reloaded ${page.url()}`; } case "click": { const selector = args[0]; if (!selector) throw new Error("Usage: browse click "); const role = bm.getRefRole(selector); if (role === "option") { const resolved2 = await bm.resolveRef(selector); if ("locator" in resolved2) { const optionInfo = await resolved2.locator.evaluate((el) => { if (el.tagName !== "OPTION") return null; const option = el; const select = option.closest("select"); if (!select) return null; return { value: option.value, text: option.text }; }); if (optionInfo) { await resolved2.locator.locator("xpath=ancestor::select").selectOption(optionInfo.value, { timeout: 5000 }); return `Selected "${optionInfo.text}" (auto-routed from click on