🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@feedthrough/playwright

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@feedthrough/playwright - npm Package Compare versions

Comparing version
0.1.0
to
0.3.0
+1
-1
dist/generated/bundle.d.ts

@@ -1,2 +0,2 @@

export declare const bridgeBundle = "\"use strict\";\n(() => {\n // src/transport.ts\n var MAX_QUEUE = 1e3;\n var Transport = class {\n constructor(url, onMessage, onStatus, reconnectDelay) {\n this.url = url;\n this.onMessage = onMessage;\n this.onStatus = onStatus;\n this.reconnectDelay = reconnectDelay;\n this.ws = null;\n this.queue = [];\n this.reconnectTimer = null;\n this.destroyed = false;\n }\n connect() {\n if (this.destroyed) return;\n this.ws = new WebSocket(this.url);\n this.ws.onopen = () => {\n this.onStatus(true);\n for (const msg of this.queue) this.ws.send(msg);\n this.queue = [];\n };\n this.ws.onmessage = (event) => {\n try {\n this.onMessage(JSON.parse(event.data));\n } catch {\n }\n };\n this.ws.onclose = () => {\n this.onStatus(false);\n if (!this.destroyed) {\n this.reconnectTimer = setTimeout(() => this.connect(), this.reconnectDelay);\n }\n };\n this.ws.onerror = () => {\n };\n }\n send(msg) {\n const serialized = JSON.stringify(msg);\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(serialized);\n } else {\n this.queue.push(serialized);\n if (this.queue.length > MAX_QUEUE) this.queue.shift();\n }\n }\n destroy() {\n this.destroyed = true;\n if (this.reconnectTimer !== null) clearTimeout(this.reconnectTimer);\n this.ws?.close();\n }\n };\n\n // src/interceptors/console.ts\n var STD_LEVELS = [\"log\", \"warn\", \"error\", \"info\", \"debug\"];\n var MAX_LOGS = 1e3;\n var MAX_ARG_CHARS = 1e4;\n var ConsoleInterceptor = class {\n constructor() {\n this.originals = /* @__PURE__ */ new Map();\n this.logs = [];\n this.counts = /* @__PURE__ */ new Map();\n this.timers = /* @__PURE__ */ new Map();\n }\n install() {\n const c = console;\n const record = (msg) => {\n this.logs.push(msg);\n if (this.logs.length > MAX_LOGS) this.logs.shift();\n };\n const rich = (method, level, args, extras = {}) => {\n record({ type: \"console\", ts: Date.now(), level, method, args: args.map(serialize), ...extras });\n };\n const wrap = (name, fn) => {\n const orig = c[name];\n if (typeof orig !== \"function\") return;\n this.originals.set(name, orig.bind(console));\n c[name] = fn;\n };\n for (const level of STD_LEVELS) {\n const orig = c[level].bind(console);\n this.originals.set(level, orig);\n c[level] = (...args) => {\n orig(...args);\n record({ type: \"console\", ts: Date.now(), level, args: args.map(serialize) });\n };\n }\n wrap(\"dir\", (obj, options) => {\n this.originals.get(\"dir\")(obj, options);\n rich(\"dir\", \"log\", options === void 0 ? [obj] : [obj, options]);\n });\n wrap(\"table\", (data, columns) => {\n this.originals.get(\"table\")(data, columns);\n rich(\"table\", \"log\", columns === void 0 ? [data] : [data, columns]);\n });\n wrap(\"trace\", (...args) => {\n this.originals.get(\"trace\")(...args);\n rich(\"trace\", \"log\", args, { stack: captureStack() });\n });\n wrap(\"assert\", (condition, ...args) => {\n this.originals.get(\"assert\")(condition, ...args);\n if (condition) return;\n rich(\"assert\", \"error\", args.length ? args : [\"Assertion failed\"], { stack: captureStack() });\n });\n wrap(\"count\", (label) => {\n this.originals.get(\"count\")(label);\n const key = label == null ? \"default\" : String(label);\n const n = (this.counts.get(key) ?? 0) + 1;\n this.counts.set(key, n);\n rich(\"count\", \"log\", [`${key}: ${n}`]);\n });\n wrap(\"countReset\", (label) => {\n this.originals.get(\"countReset\")(label);\n const key = label == null ? \"default\" : String(label);\n this.counts.set(key, 0);\n rich(\"countReset\", \"log\", [`${key}: 0`]);\n });\n wrap(\"time\", (label) => {\n this.originals.get(\"time\")(label);\n const key = label == null ? \"default\" : String(label);\n this.timers.set(key, performance.now());\n });\n wrap(\"timeEnd\", (label) => {\n this.originals.get(\"timeEnd\")(label);\n const key = label == null ? \"default\" : String(label);\n const start = this.timers.get(key);\n if (start === void 0) {\n rich(\"timeEnd\", \"warn\", [`Timer \"${key}\" does not exist`]);\n return;\n }\n this.timers.delete(key);\n rich(\"timeEnd\", \"log\", [`${key}: ${(performance.now() - start).toFixed(3)}ms`]);\n });\n wrap(\"timeLog\", (label, ...args) => {\n this.originals.get(\"timeLog\")(label, ...args);\n const key = label == null ? \"default\" : String(label);\n const start = this.timers.get(key);\n if (start === void 0) {\n rich(\"timeLog\", \"warn\", [`Timer \"${key}\" does not exist`, ...args]);\n return;\n }\n rich(\"timeLog\", \"log\", [`${key}: ${(performance.now() - start).toFixed(3)}ms`, ...args]);\n });\n wrap(\"group\", (...args) => {\n this.originals.get(\"group\")(...args);\n rich(\"group\", \"log\", args);\n });\n wrap(\"groupCollapsed\", (...args) => {\n this.originals.get(\"groupCollapsed\")(...args);\n rich(\"groupCollapsed\", \"log\", args);\n });\n wrap(\"groupEnd\", () => {\n this.originals.get(\"groupEnd\")();\n rich(\"groupEnd\", \"log\", []);\n });\n wrap(\"clear\", () => {\n this.originals.get(\"clear\")();\n rich(\"clear\", \"log\", []);\n });\n this.onError = (e) => {\n record({\n type: \"console\",\n ts: Date.now(),\n level: \"error\",\n method: \"uncaught\",\n args: [serialize(e.message || \"Uncaught error\")],\n stack: e.error instanceof Error ? e.error.stack : void 0\n });\n };\n this.onRejection = (e) => {\n const reason = e.reason;\n record({\n type: \"console\",\n ts: Date.now(),\n level: \"error\",\n method: \"unhandledrejection\",\n args: [serialize(reason instanceof Error ? reason.message : reason)],\n stack: reason instanceof Error ? reason.stack : void 0\n });\n };\n window.addEventListener(\"error\", this.onError);\n window.addEventListener(\"unhandledrejection\", this.onRejection);\n }\n uninstall() {\n const c = console;\n for (const [name, original] of this.originals) {\n c[name] = original;\n }\n this.originals.clear();\n if (this.onError) window.removeEventListener(\"error\", this.onError);\n if (this.onRejection) window.removeEventListener(\"unhandledrejection\", this.onRejection);\n }\n getLogs(opts = {}) {\n let result = this.logs;\n if (opts.levels && opts.levels.length > 0) {\n const wanted = new Set(opts.levels);\n result = result.filter((m) => wanted.has(m.level));\n }\n if (opts.match) {\n const needle = opts.match.toLowerCase();\n result = result.filter((m) => JSON.stringify(m.args).toLowerCase().includes(needle));\n }\n if (opts.since !== void 0) {\n result = result.filter((m) => m.ts >= opts.since);\n }\n if (opts.limit !== void 0) {\n result = result.slice(-opts.limit);\n } else if (result === this.logs) {\n result = [...this.logs];\n }\n return result;\n }\n };\n function captureStack() {\n const raw = new Error().stack ?? \"\";\n const lines = raw.split(\"\\n\");\n return lines.length > 2 ? lines.slice(2).join(\"\\n\") : raw;\n }\n function serialize(v) {\n if (v === null || v === void 0) return String(v);\n if (typeof v === \"string\") return cap(v);\n if (typeof v !== \"object\") return v;\n if (v instanceof Error) return { name: v.name, message: v.message, stack: v.stack };\n try {\n const json = JSON.stringify(v);\n return json.length > MAX_ARG_CHARS ? cap(json) : JSON.parse(json);\n } catch {\n return String(v);\n }\n }\n function cap(text) {\n return text.length > MAX_ARG_CHARS ? text.slice(0, MAX_ARG_CHARS) + \"\\u2026[truncated]\" : text;\n }\n\n // src/interceptors/network.ts\n var MAX_REQUESTS = 1e3;\n var MAX_BODY_CHARS = 1e4;\n var MAX_BODY_BYTES = 64 * 1024;\n var NetworkInterceptor = class {\n constructor() {\n this.requests = [];\n this.origFetch = null;\n this.OrigXHR = null;\n }\n // Captured into a local buffer only \u2014 never streamed. An agent pulls requests\n // on demand via get_network_requests; pushing every request/response over the\n // WebSocket would be wasted traffic since nothing subscribes to it.\n install() {\n this.interceptFetch();\n this.interceptXHR();\n }\n interceptFetch() {\n this.origFetch = window.fetch.bind(window);\n const orig = this.origFetch;\n const requests = this.requests;\n window.fetch = async (input, init) => {\n const url = input instanceof Request ? input.url : String(input);\n const method = resolveMethod(input, init);\n const requestId = uid();\n const startTs = Date.now();\n const requestHeaders = mergeRequestHeaders(input, init);\n const requestBody = serializeRequestBody(init?.body);\n const pending = {\n type: \"network\",\n ts: startTs,\n requestId,\n method,\n url,\n requestHeaders,\n requestBody\n };\n pushRequest(requests, pending);\n try {\n const res = await orig(input, init);\n Object.assign(pending, {\n ts: Date.now(),\n status: res.status,\n duration: Date.now() - startTs,\n responseHeaders: headersToObject(res.headers)\n });\n captureResponseBody(res.clone(), pending);\n return res;\n } catch (e) {\n Object.assign(pending, {\n ts: Date.now(),\n duration: Date.now() - startTs,\n error: e instanceof Error ? e.message : String(e)\n });\n throw e;\n }\n };\n }\n interceptXHR() {\n this.OrigXHR = window.XMLHttpRequest;\n const OrigXHR = this.OrigXHR;\n const requests = this.requests;\n function FeedthroughXHR() {\n const xhr = new OrigXHR();\n let method = \"GET\";\n let url = \"\";\n const requestHeaders = {};\n const origOpen = xhr.open.bind(xhr);\n xhr.open = (m, u, async, user, password) => {\n method = m.toUpperCase();\n url = String(u);\n origOpen(m, String(u), async ?? true, user, password);\n };\n const origSetHeader = xhr.setRequestHeader.bind(xhr);\n xhr.setRequestHeader = (name, value) => {\n requestHeaders[name] = String(value);\n origSetHeader(name, value);\n };\n const origSend = xhr.send.bind(xhr);\n xhr.send = (body) => {\n const requestId = uid();\n const startTs = Date.now();\n const requestBody = serializeRequestBody(body ?? void 0);\n const pending = {\n type: \"network\",\n ts: startTs,\n requestId,\n method,\n url,\n requestHeaders: Object.keys(requestHeaders).length ? { ...requestHeaders } : void 0,\n requestBody\n };\n pushRequest(requests, pending);\n xhr.addEventListener(\"loadend\", () => {\n let responseBody;\n try {\n if (xhr.responseType === \"\" || xhr.responseType === \"text\") {\n responseBody = capText(xhr.responseText);\n } else {\n responseBody = `[XHR responseType=${xhr.responseType}]`;\n }\n } catch {\n }\n Object.assign(pending, {\n ts: Date.now(),\n status: xhr.status,\n duration: Date.now() - startTs,\n responseBody,\n responseHeaders: parseRawHeaders(xhr.getAllResponseHeaders())\n });\n });\n origSend(body);\n };\n return xhr;\n }\n FeedthroughXHR.prototype = OrigXHR.prototype;\n window.XMLHttpRequest = FeedthroughXHR;\n }\n uninstall() {\n if (this.origFetch) window.fetch = this.origFetch;\n if (this.OrigXHR) window.XMLHttpRequest = this.OrigXHR;\n }\n getRequests(filter, since) {\n let all = [...this.requests];\n if (since !== void 0) all = all.filter((r) => r.ts >= since);\n if (!filter) return all;\n const lower = filter.toLowerCase();\n return all.filter((r) => r.url.toLowerCase().includes(lower) || r.method.toLowerCase() === lower);\n }\n };\n function resolveMethod(input, init) {\n return (init?.method ?? (input instanceof Request ? input.method : null) ?? \"GET\").toUpperCase();\n }\n function pushRequest(requests, msg) {\n requests.push(msg);\n if (requests.length > MAX_REQUESTS) requests.shift();\n }\n function mergeRequestHeaders(input, init) {\n const source = init?.headers ?? (input instanceof Request ? input.headers : void 0);\n if (!source) return void 0;\n const out = {};\n if (source instanceof Headers) {\n source.forEach((v, k) => {\n out[k] = v;\n });\n } else if (Array.isArray(source)) {\n for (const [k, v] of source) out[k] = String(v);\n } else {\n for (const [k, v] of Object.entries(source)) out[k] = String(v);\n }\n return Object.keys(out).length ? out : void 0;\n }\n function headersToObject(headers) {\n const out = {};\n headers.forEach((v, k) => {\n out[k] = v;\n });\n return out;\n }\n function parseRawHeaders(raw) {\n if (!raw) return void 0;\n const out = {};\n for (const line of raw.split(\"\\r\\n\")) {\n const idx = line.indexOf(\":\");\n if (idx <= 0) continue;\n const k = line.slice(0, idx).trim();\n const v = line.slice(idx + 1).trim();\n if (k) out[k] = v;\n }\n return Object.keys(out).length ? out : void 0;\n }\n function serializeRequestBody(body) {\n if (body == null) return void 0;\n if (typeof body === \"string\") return capText(body);\n if (body instanceof URLSearchParams) return capText(body.toString());\n if (body instanceof FormData) {\n const obj = {};\n body.forEach((v, k) => {\n obj[k] = v instanceof File ? `[File: ${v.name}, ${v.size} bytes]` : v;\n });\n try {\n return capText(JSON.stringify(obj));\n } catch {\n return \"[FormData]\";\n }\n }\n if (body instanceof Blob) return `[Blob: ${body.size} bytes, ${body.type || \"unknown\"}]`;\n if (body instanceof ArrayBuffer) return `[ArrayBuffer: ${body.byteLength} bytes]`;\n if (ArrayBuffer.isView(body)) return `[${body.constructor.name}: ${body.byteLength} bytes]`;\n if (body instanceof ReadableStream) return \"[ReadableStream]\";\n try {\n return capText(JSON.stringify(body));\n } catch {\n return String(body);\n }\n }\n function capText(text) {\n if (text.length <= MAX_BODY_CHARS) return text;\n return text.slice(0, MAX_BODY_CHARS) + `\\u2026[truncated, ${text.length - MAX_BODY_CHARS} more chars]`;\n }\n function captureResponseBody(res, pending) {\n const ct = (res.headers.get(\"content-type\") ?? \"\").toLowerCase();\n if (isSkippable(ct)) {\n const len = res.headers.get(\"content-length\");\n pending.responseBody = `[${describe(ct)}${len ? `, ${len} bytes` : \"\"}, ${ct || \"no content-type\"}]`;\n return;\n }\n readBounded(res).then((body) => {\n pending.responseBody = body;\n }).catch(() => {\n });\n }\n async function readBounded(res) {\n if (!res.body) return capText(await res.text());\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let text = \"\";\n let bytes = 0;\n let truncated = false;\n try {\n for (; ; ) {\n const { done, value } = await reader.read();\n if (done) break;\n bytes += value.byteLength;\n text += decoder.decode(value, { stream: true });\n if (bytes >= MAX_BODY_BYTES || text.length >= MAX_BODY_CHARS) {\n truncated = true;\n break;\n }\n }\n } finally {\n reader.cancel().catch(() => {\n });\n }\n if (!truncated) return capText(text);\n return text.slice(0, MAX_BODY_CHARS) + \"\\u2026[truncated]\";\n }\n function isSkippable(ct) {\n return /^(image|video|audio|font)\\//.test(ct) || ct.startsWith(\"application/octet-stream\") || ct.startsWith(\"application/pdf\") || ct.startsWith(\"application/zip\") || ct.startsWith(\"text/event-stream\") || // SSE \u2014 never-ending stream\n ct.startsWith(\"application/x-ndjson\");\n }\n function describe(ct) {\n if (ct.startsWith(\"text/event-stream\")) return \"event stream\";\n if (ct.startsWith(\"application/x-ndjson\")) return \"ndjson stream\";\n return \"binary\";\n }\n var counter = 0;\n function uid() {\n return `${Date.now()}-${++counter}`;\n }\n\n // src/commands.ts\n var CommandHandler = class {\n constructor(transport, console2, network) {\n this.transport = transport;\n this.console = console2;\n this.network = network;\n }\n handle(msg) {\n if (!isCommand(msg)) return;\n let value;\n let error;\n try {\n value = this.dispatch(msg);\n } catch (e) {\n error = e instanceof Error ? e.message : String(e);\n }\n const result = {\n type: \"result\",\n ts: Date.now(),\n commandId: msg.id,\n ok: error === void 0,\n value,\n error\n };\n this.transport.send(result);\n }\n dispatch(cmd) {\n switch (cmd.action) {\n case \"click\":\n return clickEl(cmd.selector);\n case \"fill\":\n return fillEl(cmd.selector, cmd.value);\n case \"hover\":\n return hoverEl(cmd.selector);\n case \"inspect\":\n return inspectEl(cmd.selector, cmd.properties);\n case \"query_dom\":\n return queryDom(cmd.selector);\n case \"get_console_logs\":\n return this.console.getLogs({ limit: cmd.limit, levels: cmd.levels, match: cmd.match, since: cmd.since });\n case \"get_network_requests\":\n return this.network.getRequests(cmd.filter, cmd.since);\n case \"press_key\":\n return pressKey(cmd.selector, cmd.key);\n case \"get_html\":\n return getHtml(cmd.selector);\n case \"get_page_info\":\n return getPageInfo();\n case \"set_style\":\n return setStyle(cmd.selector, cmd.properties);\n case \"set_attribute\":\n return setAttribute(cmd.selector, cmd.name, cmd.value);\n case \"set_text\":\n return setText(cmd.selector, cmd.text);\n case \"reset_overrides\":\n return resetOverrides();\n }\n }\n };\n function getEl(selector) {\n const el = document.querySelector(selector);\n if (!el) throw new Error(`no element matches \"${selector}\"`);\n return el;\n }\n function clickEl(selector) {\n const el = getEl(selector);\n el.click();\n return { tag: el.tagName.toLowerCase(), id: el.id || null };\n }\n function fillEl(selector, value) {\n const el = getEl(selector);\n el.focus();\n const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : el instanceof HTMLSelectElement ? HTMLSelectElement.prototype : HTMLInputElement.prototype;\n const nativeSetter = Object.getOwnPropertyDescriptor(proto, \"value\")?.set;\n if (nativeSetter) {\n nativeSetter.call(el, value);\n } else {\n el.value = value;\n }\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n el.dispatchEvent(new Event(\"change\", { bubbles: true }));\n return { tag: el.tagName.toLowerCase(), value };\n }\n function hoverEl(selector) {\n const el = getEl(selector);\n el.dispatchEvent(new MouseEvent(\"mouseover\", { bubbles: true }));\n el.dispatchEvent(new MouseEvent(\"mouseenter\", { bubbles: true }));\n return { tag: el.tagName.toLowerCase() };\n }\n var DEFAULT_STYLE_PROPS = [\n \"display\",\n \"position\",\n \"visibility\",\n \"opacity\",\n \"z-index\",\n \"box-sizing\",\n \"top\",\n \"right\",\n \"bottom\",\n \"left\",\n \"width\",\n \"height\",\n \"margin\",\n \"padding\",\n \"border\",\n \"color\",\n \"background-color\",\n \"font-family\",\n \"font-size\",\n \"font-weight\",\n \"line-height\",\n \"text-align\",\n \"overflow\",\n \"cursor\",\n \"pointer-events\",\n \"flex\",\n \"flex-direction\",\n \"justify-content\",\n \"align-items\",\n \"gap\",\n \"grid-template-columns\",\n \"transform\"\n ];\n function inspectEl(selector, properties) {\n const el = getEl(selector);\n const rect = el.getBoundingClientRect();\n const cs = window.getComputedStyle(el);\n const result = {\n tag: el.tagName.toLowerCase(),\n id: el.id || null,\n classes: Array.from(el.classList),\n attributes: Object.fromEntries(Array.from(el.attributes).map((a) => [a.name, a.value])),\n textContent: el.textContent?.trim().slice(0, 200),\n rect: {\n top: rect.top,\n right: rect.right,\n bottom: rect.bottom,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n x: rect.x,\n y: rect.y\n },\n scroll: { x: window.scrollX, y: window.scrollY },\n inViewport: rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth,\n styles: pickStyles(cs, DEFAULT_STYLE_PROPS)\n };\n const state = elementState(el);\n if (state) result.state = state;\n if (properties && properties.length > 0) {\n const requested = {};\n for (const p of properties) requested[p] = cs.getPropertyValue(p);\n result.requested = requested;\n }\n return result;\n }\n function pickStyles(cs, props) {\n const out = {};\n for (const p of props) {\n const v = cs.getPropertyValue(p);\n if (v) out[p] = v;\n }\n return out;\n }\n function elementState(el) {\n const s = {};\n if (el instanceof HTMLInputElement) {\n s.value = capValue(el.value);\n s.type = el.type;\n s.checked = el.checked;\n s.disabled = el.disabled;\n s.readOnly = el.readOnly;\n s.required = el.required;\n if (el.placeholder) s.placeholder = el.placeholder;\n if (el.validationMessage) s.validationMessage = el.validationMessage;\n } else if (el instanceof HTMLTextAreaElement) {\n s.value = capValue(el.value);\n s.disabled = el.disabled;\n s.readOnly = el.readOnly;\n } else if (el instanceof HTMLSelectElement) {\n s.value = el.value;\n s.selectedIndex = el.selectedIndex;\n s.disabled = el.disabled;\n } else if (el instanceof HTMLButtonElement) {\n s.type = el.type;\n s.disabled = el.disabled;\n } else if (el instanceof HTMLAnchorElement) {\n s.href = el.href;\n }\n if (el instanceof HTMLElement && Object.keys(el.dataset).length > 0) {\n s.dataset = { ...el.dataset };\n }\n return Object.keys(s).length > 0 ? s : void 0;\n }\n function capValue(v) {\n return v.length > 1e3 ? v.slice(0, 1e3) + \"\\u2026[truncated]\" : v;\n }\n function queryDom(selector) {\n return Array.from(document.querySelectorAll(selector)).map((el) => ({\n tag: el.tagName.toLowerCase(),\n id: el.id || null,\n classes: Array.from(el.classList),\n textContent: el.textContent?.trim().slice(0, 100)\n }));\n }\n var NAMED_KEYS = {\n Enter: { code: \"Enter\", keyCode: 13 },\n Tab: { code: \"Tab\", keyCode: 9 },\n Escape: { code: \"Escape\", keyCode: 27 },\n Backspace: { code: \"Backspace\", keyCode: 8 },\n Delete: { code: \"Delete\", keyCode: 46 },\n ArrowUp: { code: \"ArrowUp\", keyCode: 38 },\n ArrowDown: { code: \"ArrowDown\", keyCode: 40 },\n ArrowLeft: { code: \"ArrowLeft\", keyCode: 37 },\n ArrowRight: { code: \"ArrowRight\", keyCode: 39 },\n \" \": { code: \"Space\", keyCode: 32 }\n };\n function pressKey(selector, key) {\n const el = getEl(selector);\n el.focus?.();\n const named = NAMED_KEYS[key];\n const keyCode = named ? named.keyCode : key.length === 1 ? key.toUpperCase().charCodeAt(0) : 0;\n const code = named ? named.code : key.length === 1 ? `Key${key.toUpperCase()}` : key;\n const init = {\n key,\n code,\n keyCode,\n which: keyCode,\n bubbles: true,\n cancelable: true\n };\n el.dispatchEvent(new KeyboardEvent(\"keydown\", init));\n if (key.length === 1) el.dispatchEvent(new KeyboardEvent(\"keypress\", init));\n el.dispatchEvent(new KeyboardEvent(\"keyup\", init));\n return { tag: el.tagName.toLowerCase(), key };\n }\n var MAX_HTML_CHARS = 5e4;\n function getHtml(selector) {\n const el = getEl(selector);\n const html = el.outerHTML;\n const truncated = html.length > MAX_HTML_CHARS;\n return {\n tag: el.tagName.toLowerCase(),\n html: truncated ? html.slice(0, MAX_HTML_CHARS) + \"\\u2026[truncated]\" : html,\n truncated\n };\n }\n function getPageInfo() {\n return {\n url: window.location.href,\n title: document.title,\n readyState: document.readyState,\n viewport: { width: window.innerWidth, height: window.innerHeight },\n scroll: { x: window.scrollX, y: window.scrollY },\n userAgent: navigator.userAgent\n };\n }\n var PREVIEW_NOTE = \"Live preview only \\u2014 applied to the running DOM, not saved to source, and reset on reload/HMR. Once the user is happy, edit the actual source to make it permanent.\";\n var CLOBBER_WARNING = \"This may be reverted on the next framework render (React/Vue/etc. re-render this element from component state). If it snaps back, change it in the source instead of here.\";\n var FRAMEWORK_OWNED_ATTRS = /* @__PURE__ */ new Set([\"class\", \"style\", \"value\", \"checked\", \"disabled\", \"selected\"]);\n var overrides = [];\n function setStyle(selector, properties) {\n const el = getEl(selector);\n if (!el.style) throw new Error(`element \"${selector}\" has no style (not an HTMLElement)`);\n const applied = {};\n for (const [prop, value] of Object.entries(properties)) {\n const prevValue = el.style.getPropertyValue(prop);\n const prevPriority = el.style.getPropertyPriority(prop);\n overrides.push(() => {\n if (prevValue) el.style.setProperty(prop, prevValue, prevPriority);\n else el.style.removeProperty(prop);\n });\n el.style.setProperty(prop, value);\n applied[prop] = el.style.getPropertyValue(prop);\n }\n return { tag: el.tagName.toLowerCase(), applied, note: PREVIEW_NOTE };\n }\n function setAttribute(selector, name, value) {\n const el = getEl(selector);\n const had = el.hasAttribute(name);\n const prev = had ? el.getAttribute(name) : null;\n overrides.push(() => {\n if (had) el.setAttribute(name, prev ?? \"\");\n else el.removeAttribute(name);\n });\n if (value === null) el.removeAttribute(name);\n else el.setAttribute(name, value);\n const result = {\n tag: el.tagName.toLowerCase(),\n name,\n value: value === null ? null : el.getAttribute(name),\n removed: value === null,\n note: PREVIEW_NOTE\n };\n if (FRAMEWORK_OWNED_ATTRS.has(name.toLowerCase())) result.frameworkWarning = CLOBBER_WARNING;\n return result;\n }\n function setText(selector, text) {\n const el = getEl(selector);\n const prev = el.textContent;\n overrides.push(() => {\n el.textContent = prev;\n });\n el.textContent = text;\n return {\n tag: el.tagName.toLowerCase(),\n text,\n note: PREVIEW_NOTE,\n frameworkWarning: CLOBBER_WARNING\n };\n }\n function resetOverrides() {\n const count = overrides.length;\n while (overrides.length) overrides.pop()();\n return { reverted: count, note: \"All bridge-applied DOM changes since connect have been undone (best effort \\u2014 elements re-created by the framework since may not roll back).\" };\n }\n function isCommand(v) {\n return typeof v === \"object\" && v !== null && v.type === \"command\" && typeof v.id === \"string\";\n }\n\n // src/bridge.ts\n var DEFAULT_SERVER_URL = \"ws://localhost:8765\";\n var FeedthroughBridge = class {\n constructor(options = {}) {\n const url = options.serverUrl ?? DEFAULT_SERVER_URL;\n const reconnectDelay = options.reconnectDelay ?? 2e3;\n this.consoleInterceptor = new ConsoleInterceptor();\n this.networkInterceptor = new NetworkInterceptor();\n this.transport = new Transport(\n url,\n (msg) => this.commandHandler.handle(msg),\n (connected) => {\n if (connected) this.transport.send({ type: \"hello\", url: window.location.href });\n },\n reconnectDelay\n );\n this.commandHandler = new CommandHandler(this.transport, this.consoleInterceptor, this.networkInterceptor);\n }\n connect() {\n this.consoleInterceptor.install();\n this.networkInterceptor.install();\n this.transport.connect();\n }\n destroy() {\n this.transport.destroy();\n this.consoleInterceptor.uninstall();\n this.networkInterceptor.uninstall();\n }\n };\n\n // src/browser.ts\n window.__feedthrough = new FeedthroughBridge(window.__feedthroughOptions ?? {});\n window.__feedthrough.connect();\n})();\n";
export declare const bridgeBundle = "\"use strict\";\n(() => {\n // src/commands.ts\n var CommandHandler = class {\n constructor(transport, console2, network) {\n this.transport = transport;\n this.console = console2;\n this.network = network;\n }\n handle(msg) {\n if (!isCommand(msg)) return;\n let value;\n let error;\n try {\n value = this.dispatch(msg);\n } catch (e) {\n error = e instanceof Error ? e.message : String(e);\n }\n const result = {\n type: \"result\",\n ts: Date.now(),\n commandId: msg.id,\n ok: error === void 0,\n value,\n error\n };\n this.transport.send(result);\n }\n dispatch(cmd) {\n switch (cmd.action) {\n case \"click\":\n return clickEl(cmd.selector);\n case \"fill\":\n return fillEl(cmd.selector, cmd.value);\n case \"hover\":\n return hoverEl(cmd.selector);\n case \"inspect\":\n return inspectEl(cmd.selector, cmd.properties);\n case \"query_dom\":\n return queryDom(cmd.selector);\n case \"get_console_logs\":\n return this.console.getLogs({\n limit: cmd.limit,\n levels: cmd.levels,\n match: cmd.match,\n since: cmd.since\n });\n case \"get_network_requests\":\n return this.network.getRequests(cmd.filter, cmd.since);\n case \"press_key\":\n return pressKey(cmd.selector, cmd.key);\n case \"get_html\":\n return getHtml(cmd.selector);\n case \"get_page_info\":\n return getPageInfo();\n case \"set_style\":\n return setStyle(cmd.selector, cmd.properties);\n case \"set_attribute\":\n return setAttribute(cmd.selector, cmd.name, cmd.value);\n case \"set_text\":\n return setText(cmd.selector, cmd.text);\n case \"reset_overrides\":\n return resetOverrides();\n }\n }\n };\n function getEl(selector) {\n const el = document.querySelector(selector);\n if (!el) throw new Error(`no element matches \"${selector}\"`);\n return el;\n }\n function clickEl(selector) {\n const el = getEl(selector);\n el.click();\n return { tag: el.tagName.toLowerCase(), id: el.id || null };\n }\n function fillEl(selector, value) {\n const el = getEl(selector);\n el.focus();\n const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : el instanceof HTMLSelectElement ? HTMLSelectElement.prototype : HTMLInputElement.prototype;\n const nativeSetter = Object.getOwnPropertyDescriptor(proto, \"value\")?.set;\n if (nativeSetter) {\n nativeSetter.call(el, value);\n } else {\n el.value = value;\n }\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n el.dispatchEvent(new Event(\"change\", { bubbles: true }));\n return { tag: el.tagName.toLowerCase(), value };\n }\n function hoverEl(selector) {\n const el = getEl(selector);\n el.dispatchEvent(new MouseEvent(\"mouseover\", { bubbles: true }));\n el.dispatchEvent(new MouseEvent(\"mouseenter\", { bubbles: true }));\n return { tag: el.tagName.toLowerCase() };\n }\n var DEFAULT_STYLE_PROPS = [\n \"display\",\n \"position\",\n \"visibility\",\n \"opacity\",\n \"z-index\",\n \"box-sizing\",\n \"top\",\n \"right\",\n \"bottom\",\n \"left\",\n \"width\",\n \"height\",\n \"margin\",\n \"padding\",\n \"border\",\n \"color\",\n \"background-color\",\n \"font-family\",\n \"font-size\",\n \"font-weight\",\n \"line-height\",\n \"text-align\",\n \"overflow\",\n \"cursor\",\n \"pointer-events\",\n \"flex\",\n \"flex-direction\",\n \"justify-content\",\n \"align-items\",\n \"gap\",\n \"grid-template-columns\",\n \"transform\"\n ];\n function inspectEl(selector, properties) {\n const el = getEl(selector);\n const rect = el.getBoundingClientRect();\n const cs = window.getComputedStyle(el);\n const result = {\n tag: el.tagName.toLowerCase(),\n id: el.id || null,\n classes: Array.from(el.classList),\n attributes: Object.fromEntries(Array.from(el.attributes).map((a) => [a.name, a.value])),\n textContent: el.textContent?.trim().slice(0, 200),\n rect: {\n top: rect.top,\n right: rect.right,\n bottom: rect.bottom,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n x: rect.x,\n y: rect.y\n },\n scroll: { x: window.scrollX, y: window.scrollY },\n inViewport: rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth,\n styles: pickStyles(cs, DEFAULT_STYLE_PROPS)\n };\n result.path = ancestorChain(el);\n const overflow = overflowInfo(el);\n if (overflow) result.overflow = overflow;\n const clipped = clippedByAncestor(el, rect, cs);\n if (clipped) result.clipped = clipped;\n const vis = effectiveVisibility(el, rect, cs);\n result.visible = vis.visible;\n if (!vis.visible) result.hiddenReason = vis.reason;\n const occ = occlusionInfo(el, rect);\n if (occ) {\n result.hittable = occ.hittable;\n if (occ.occludedBy) result.occludedBy = occ.occludedBy;\n }\n const a11y = accessibilityInfo(el);\n if (a11y) result.a11y = a11y;\n const pseudo = pseudoContent(el);\n if (pseudo) result.pseudo = pseudo;\n const state = elementState(el);\n if (state) result.state = state;\n if (properties && properties.length > 0) {\n const requested = {};\n for (const p of properties) requested[p] = cs.getPropertyValue(p);\n result.requested = requested;\n }\n return result;\n }\n function overflowInfo(el) {\n const { scrollWidth, clientWidth, scrollHeight, clientHeight } = el;\n const x = scrollWidth > clientWidth;\n const y = scrollHeight > clientHeight;\n if (!x && !y) return void 0;\n return { x, y, scrollWidth, clientWidth, scrollHeight, clientHeight };\n }\n function refString(el) {\n const tag = el.tagName.toLowerCase();\n const id = el.id ? `#${el.id}` : \"\";\n const all = Array.from(el.classList);\n const classes = all.slice(0, 3).map((c) => `.${c}`).join(\"\");\n const more = all.length > 3 ? \"\\u2026\" : \"\";\n return tag + id + classes + more;\n }\n function chainRef(el) {\n const tag = el.tagName.toLowerCase();\n if (el.id) return `${tag}#${el.id}`;\n const cls = el.classList[0];\n return cls ? `${tag}.${cls}` : tag;\n }\n function ancestorChain(el) {\n const maxDepth = 6;\n const parts = [];\n let node = el;\n while (node) {\n parts.unshift(chainRef(node));\n node = node.parentElement;\n if (parts.length >= maxDepth && node) {\n parts.unshift(\"\\u2026\");\n break;\n }\n }\n return parts.join(\" > \");\n }\n var MAX_ANCESTOR_WALK = 50;\n function establishesFixedContainingBlock(cs) {\n if (cs.transform !== \"none\") return true;\n if (cs.perspective !== \"none\") return true;\n if (cs.filter !== \"none\") return true;\n const backdrop = cs.getPropertyValue(\"backdrop-filter\");\n if (backdrop && backdrop !== \"none\") return true;\n if (/transform|perspective|filter/.test(cs.willChange)) return true;\n if (/paint|layout|strict|content/.test(cs.contain)) return true;\n return false;\n }\n function clippedByAncestor(el, rect, cs) {\n const tol = 1;\n let clipping = cs.position !== \"fixed\";\n let node = el.parentElement;\n for (let depth = 0; node && depth < MAX_ANCESTOR_WALK; node = node.parentElement, depth++) {\n const acs = window.getComputedStyle(node);\n if (!clipping) {\n if (establishesFixedContainingBlock(acs)) clipping = true;\n else continue;\n }\n const clipsX = acs.overflowX !== \"visible\";\n const clipsY = acs.overflowY !== \"visible\";\n if (!clipsX && !clipsY) continue;\n const ar = node.getBoundingClientRect();\n const edges = [];\n if (clipsY && rect.top < ar.top - tol) edges.push(\"top\");\n if (clipsX && rect.right > ar.right + tol) edges.push(\"right\");\n if (clipsY && rect.bottom > ar.bottom + tol) edges.push(\"bottom\");\n if (clipsX && rect.left < ar.left - tol) edges.push(\"left\");\n if (edges.length > 0) return { by: refString(node), edges };\n }\n return void 0;\n }\n function pseudoContent(el) {\n const out = {};\n for (const pseudo of [\"::before\", \"::after\"]) {\n const content = window.getComputedStyle(el, pseudo).content;\n if (content && content !== \"none\" && content !== \"normal\") {\n out[pseudo] = content.length > 200 ? `${content.slice(0, 200)}\\u2026` : content;\n }\n }\n return Object.keys(out).length > 0 ? out : void 0;\n }\n function occlusionInfo(el, rect) {\n if (rect.width === 0 || rect.height === 0) return void 0;\n const left = Math.max(rect.left, 0);\n const top0 = Math.max(rect.top, 0);\n const right = Math.min(rect.right, window.innerWidth - 1);\n const bottom = Math.min(rect.bottom, window.innerHeight - 1);\n if (right < left || bottom < top0) return void 0;\n const cx = (left + right) / 2;\n const cy = (top0 + bottom) / 2;\n const top = document.elementFromPoint(cx, cy);\n if (!top) return void 0;\n if (top === el || el.contains(top)) return { hittable: true };\n return {\n hittable: false,\n occludedBy: {\n tag: top.tagName.toLowerCase(),\n id: top.id || null,\n classes: Array.from(top.classList).slice(0, 3)\n }\n };\n }\n function effectiveVisibility(el, rect, cs) {\n if (cs.display === \"none\") return { visible: false, reason: \"display:none\" };\n let node = el.parentElement;\n for (let depth = 0; node && depth < MAX_ANCESTOR_WALK; node = node.parentElement, depth++) {\n const acs = window.getComputedStyle(node);\n if (acs.display === \"none\")\n return { visible: false, reason: `ancestor ${refString(node)} display:none` };\n if (parseFloat(acs.opacity) === 0)\n return { visible: false, reason: `ancestor ${refString(node)} opacity:0` };\n if (node.getAttribute(\"aria-hidden\") === \"true\")\n return { visible: false, reason: `ancestor ${refString(node)} aria-hidden` };\n }\n if (cs.visibility === \"hidden\" || cs.visibility === \"collapse\")\n return { visible: false, reason: `visibility:${cs.visibility}` };\n if (parseFloat(cs.opacity) === 0) return { visible: false, reason: \"opacity:0\" };\n if (el.getAttribute(\"aria-hidden\") === \"true\") return { visible: false, reason: \"aria-hidden\" };\n if (rect.width === 0 || rect.height === 0) return { visible: false, reason: \"zero-size\" };\n return { visible: true };\n }\n function pickStyles(cs, props) {\n const out = {};\n for (const p of props) {\n const v = cs.getPropertyValue(p);\n if (v) out[p] = v;\n }\n return out;\n }\n function hasNamingAttribute(el) {\n return !!el.getAttribute(\"aria-label\")?.trim() || !!el.getAttribute(\"aria-labelledby\")?.trim() || !!el.getAttribute(\"title\")?.trim();\n }\n function implicitRole(el) {\n const tag = el.tagName.toLowerCase();\n switch (tag) {\n case \"a\":\n case \"area\":\n return el.hasAttribute(\"href\") ? \"link\" : null;\n case \"button\":\n return \"button\";\n case \"input\": {\n const t = (el.getAttribute(\"type\") || \"text\").toLowerCase();\n if (el.hasAttribute(\"list\") && [\"text\", \"search\", \"email\", \"tel\", \"url\"].includes(t)) {\n return \"combobox\";\n }\n const map = {\n checkbox: \"checkbox\",\n radio: \"radio\",\n range: \"slider\",\n number: \"spinbutton\",\n button: \"button\",\n submit: \"button\",\n reset: \"button\",\n image: \"button\",\n search: \"searchbox\",\n email: \"textbox\",\n tel: \"textbox\",\n url: \"textbox\",\n text: \"textbox\"\n };\n return map[t] ?? null;\n }\n case \"select\": {\n const size = Number(el.getAttribute(\"size\") || \"0\");\n return el.hasAttribute(\"multiple\") || size > 1 ? \"listbox\" : \"combobox\";\n }\n case \"textarea\":\n return \"textbox\";\n case \"img\":\n return el.getAttribute(\"alt\") === \"\" ? \"presentation\" : \"img\";\n case \"nav\":\n return \"navigation\";\n case \"main\":\n return \"main\";\n case \"header\":\n return el.closest(\"article, aside, main, nav, section\") ? null : \"banner\";\n case \"footer\":\n return el.closest(\"article, aside, main, nav, section\") ? null : \"contentinfo\";\n case \"aside\": {\n const scoped = el.parentElement?.closest(\"article, aside, main, nav, section\");\n return !scoped || hasNamingAttribute(el) ? \"complementary\" : null;\n }\n case \"section\":\n return hasNamingAttribute(el) ? \"region\" : null;\n case \"article\":\n return \"article\";\n case \"dialog\":\n return \"dialog\";\n case \"form\":\n return hasNamingAttribute(el) ? \"form\" : null;\n case \"table\":\n return \"table\";\n case \"ul\":\n case \"ol\":\n return \"list\";\n case \"li\":\n return \"listitem\";\n case \"h1\":\n case \"h2\":\n case \"h3\":\n case \"h4\":\n case \"h5\":\n case \"h6\":\n return \"heading\";\n default:\n return null;\n }\n }\n var NAME_FROM_CONTENT = /* @__PURE__ */ new Set([\n \"button\",\n \"checkbox\",\n \"radio\",\n \"switch\",\n \"link\",\n \"menuitem\",\n \"menuitemcheckbox\",\n \"menuitemradio\",\n \"option\",\n \"tab\",\n \"treeitem\",\n \"heading\",\n \"cell\",\n \"gridcell\",\n \"columnheader\",\n \"rowheader\",\n \"row\",\n \"tooltip\"\n ]);\n function accessibleName(el, role) {\n const labelledby = el.getAttribute(\"aria-labelledby\");\n if (labelledby) {\n const txt = labelledby.split(/\\s+/).map((id) => document.getElementById(id)?.textContent?.trim()).filter(Boolean).join(\" \");\n if (txt) return txt.slice(0, 200);\n }\n const label = el.getAttribute(\"aria-label\")?.trim();\n if (label) return label.slice(0, 200);\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {\n if (el.id) {\n const forLabel = document.querySelector(`label[for=\"${CSS.escape(el.id)}\"]`);\n const txt = forLabel?.textContent?.trim();\n if (txt) return txt.slice(0, 200);\n }\n const wrapping = el.closest(\"label\")?.textContent?.trim();\n if (wrapping) return wrapping.slice(0, 200);\n if (el instanceof HTMLInputElement && el.placeholder) return el.placeholder.slice(0, 200);\n }\n if (el instanceof HTMLImageElement && el.alt) return el.alt.slice(0, 200);\n const title = el.getAttribute(\"title\")?.trim();\n if (title) return title.slice(0, 200);\n if (role && NAME_FROM_CONTENT.has(role.trim().split(/\\s+/)[0])) {\n const text = el.textContent?.trim();\n if (text) return text.slice(0, 200);\n }\n return void 0;\n }\n function accessibilityInfo(el) {\n const a11y = {};\n const role = el.getAttribute(\"role\") || implicitRole(el);\n if (role) a11y.role = role;\n const name = accessibleName(el, role);\n if (name) a11y.name = name;\n const states = {};\n for (const attr of [\n \"aria-expanded\",\n \"aria-checked\",\n \"aria-selected\",\n \"aria-pressed\",\n \"aria-current\",\n \"aria-disabled\"\n ]) {\n const v = el.getAttribute(attr);\n if (v !== null) states[attr.slice(5)] = v === \"true\" ? true : v === \"false\" ? false : v;\n }\n if (el.getAttribute(\"aria-hidden\") === \"true\") states.hidden = true;\n if (\"disabled\" in el && el.disabled) states.disabled = true;\n const tabindex = el.getAttribute(\"tabindex\");\n if (tabindex !== null) {\n const n = Number(tabindex);\n states.tabindex = Number.isNaN(n) ? tabindex : n;\n }\n if (Object.keys(states).length > 0) a11y.states = states;\n return Object.keys(a11y).length > 0 ? a11y : void 0;\n }\n function elementState(el) {\n const s = {};\n if (el instanceof HTMLInputElement) {\n s.value = capValue(el.value);\n s.type = el.type;\n s.checked = el.checked;\n s.disabled = el.disabled;\n s.readOnly = el.readOnly;\n s.required = el.required;\n if (el.placeholder) s.placeholder = el.placeholder;\n if (el.validationMessage) s.validationMessage = el.validationMessage;\n } else if (el instanceof HTMLTextAreaElement) {\n s.value = capValue(el.value);\n s.disabled = el.disabled;\n s.readOnly = el.readOnly;\n } else if (el instanceof HTMLSelectElement) {\n s.value = el.value;\n s.selectedIndex = el.selectedIndex;\n s.disabled = el.disabled;\n } else if (el instanceof HTMLButtonElement) {\n s.type = el.type;\n s.disabled = el.disabled;\n } else if (el instanceof HTMLAnchorElement) {\n s.href = el.href;\n }\n if (el instanceof HTMLElement && Object.keys(el.dataset).length > 0) {\n s.dataset = { ...el.dataset };\n }\n return Object.keys(s).length > 0 ? s : void 0;\n }\n function capValue(v) {\n return v.length > 1e3 ? `${v.slice(0, 1e3)}\\u2026[truncated]` : v;\n }\n function queryDom(selector) {\n return Array.from(document.querySelectorAll(selector)).map((el) => ({\n tag: el.tagName.toLowerCase(),\n id: el.id || null,\n classes: Array.from(el.classList),\n textContent: el.textContent?.trim().slice(0, 100)\n }));\n }\n var NAMED_KEYS = {\n Enter: { code: \"Enter\", keyCode: 13 },\n Tab: { code: \"Tab\", keyCode: 9 },\n Escape: { code: \"Escape\", keyCode: 27 },\n Backspace: { code: \"Backspace\", keyCode: 8 },\n Delete: { code: \"Delete\", keyCode: 46 },\n ArrowUp: { code: \"ArrowUp\", keyCode: 38 },\n ArrowDown: { code: \"ArrowDown\", keyCode: 40 },\n ArrowLeft: { code: \"ArrowLeft\", keyCode: 37 },\n ArrowRight: { code: \"ArrowRight\", keyCode: 39 },\n \" \": { code: \"Space\", keyCode: 32 }\n };\n function pressKey(selector, key) {\n const el = getEl(selector);\n el.focus?.();\n const named = NAMED_KEYS[key];\n const keyCode = named ? named.keyCode : key.length === 1 ? key.toUpperCase().charCodeAt(0) : 0;\n const code = named ? named.code : key.length === 1 ? `Key${key.toUpperCase()}` : key;\n const init = {\n key,\n code,\n keyCode,\n which: keyCode,\n bubbles: true,\n cancelable: true\n };\n el.dispatchEvent(new KeyboardEvent(\"keydown\", init));\n if (key.length === 1) el.dispatchEvent(new KeyboardEvent(\"keypress\", init));\n el.dispatchEvent(new KeyboardEvent(\"keyup\", init));\n return { tag: el.tagName.toLowerCase(), key };\n }\n var MAX_HTML_CHARS = 5e4;\n function getHtml(selector) {\n const el = getEl(selector);\n const html = el.outerHTML;\n const truncated = html.length > MAX_HTML_CHARS;\n return {\n tag: el.tagName.toLowerCase(),\n html: truncated ? `${html.slice(0, MAX_HTML_CHARS)}\\u2026[truncated]` : html,\n truncated\n };\n }\n function getPageInfo() {\n return {\n url: window.location.href,\n title: document.title,\n readyState: document.readyState,\n viewport: { width: window.innerWidth, height: window.innerHeight },\n scroll: { x: window.scrollX, y: window.scrollY },\n userAgent: navigator.userAgent\n };\n }\n var PREVIEW_NOTE = \"Live preview only \\u2014 applied to the running DOM, not saved to source, and reset on reload/HMR. Once the user is happy, edit the actual source to make it permanent.\";\n var CLOBBER_WARNING = \"This may be reverted on the next framework render (React/Vue/etc. re-render this element from component state). If it snaps back, change it in the source instead of here.\";\n var FRAMEWORK_OWNED_ATTRS = /* @__PURE__ */ new Set([\n \"class\",\n \"style\",\n \"value\",\n \"checked\",\n \"disabled\",\n \"selected\"\n ]);\n var overrides = [];\n function setStyle(selector, properties) {\n const el = getEl(selector);\n if (!el.style) throw new Error(`element \"${selector}\" has no style (not an HTMLElement)`);\n const applied = {};\n for (const [prop, value] of Object.entries(properties)) {\n const prevValue = el.style.getPropertyValue(prop);\n const prevPriority = el.style.getPropertyPriority(prop);\n overrides.push(() => {\n if (prevValue) el.style.setProperty(prop, prevValue, prevPriority);\n else el.style.removeProperty(prop);\n });\n el.style.setProperty(prop, value);\n applied[prop] = el.style.getPropertyValue(prop);\n }\n return { tag: el.tagName.toLowerCase(), applied, note: PREVIEW_NOTE };\n }\n function setAttribute(selector, name, value) {\n const el = getEl(selector);\n const had = el.hasAttribute(name);\n const prev = had ? el.getAttribute(name) : null;\n overrides.push(() => {\n if (had) el.setAttribute(name, prev ?? \"\");\n else el.removeAttribute(name);\n });\n if (value === null) el.removeAttribute(name);\n else el.setAttribute(name, value);\n const result = {\n tag: el.tagName.toLowerCase(),\n name,\n value: value === null ? null : el.getAttribute(name),\n removed: value === null,\n note: PREVIEW_NOTE\n };\n if (FRAMEWORK_OWNED_ATTRS.has(name.toLowerCase())) result.frameworkWarning = CLOBBER_WARNING;\n return result;\n }\n function setText(selector, text) {\n const el = getEl(selector);\n const prev = el.textContent;\n overrides.push(() => {\n el.textContent = prev;\n });\n el.textContent = text;\n return {\n tag: el.tagName.toLowerCase(),\n text,\n note: PREVIEW_NOTE,\n frameworkWarning: CLOBBER_WARNING\n };\n }\n function resetOverrides() {\n const count = overrides.length;\n while (overrides.length) overrides.pop()?.();\n return {\n reverted: count,\n note: \"All bridge-applied DOM changes since connect have been undone (best effort \\u2014 elements re-created by the framework since may not roll back).\"\n };\n }\n function isCommand(v) {\n return typeof v === \"object\" && v !== null && v.type === \"command\" && typeof v.id === \"string\";\n }\n\n // src/interceptors/console.ts\n var STD_LEVELS = [\"log\", \"warn\", \"error\", \"info\", \"debug\"];\n var MAX_LOGS = 1e3;\n var MAX_ARG_CHARS = 1e4;\n var ConsoleInterceptor = class {\n constructor() {\n this.originals = /* @__PURE__ */ new Map();\n this.logs = [];\n this.counts = /* @__PURE__ */ new Map();\n this.timers = /* @__PURE__ */ new Map();\n }\n install() {\n const c = console;\n const record = (msg) => {\n this.logs.push(msg);\n if (this.logs.length > MAX_LOGS) this.logs.shift();\n };\n const rich = (method, level, args, extras = {}) => {\n record({\n type: \"console\",\n ts: Date.now(),\n level,\n method,\n args: args.map(serialize),\n ...extras\n });\n };\n const wrap = (name, fn) => {\n const orig = c[name];\n if (typeof orig !== \"function\") return;\n this.originals.set(name, orig.bind(console));\n c[name] = fn;\n };\n for (const level of STD_LEVELS) {\n const orig = c[level].bind(console);\n this.originals.set(level, orig);\n c[level] = (...args) => {\n orig(...args);\n record({ type: \"console\", ts: Date.now(), level, args: args.map(serialize) });\n };\n }\n wrap(\"dir\", (obj, options) => {\n this.originals.get(\"dir\")?.(obj, options);\n rich(\"dir\", \"log\", options === void 0 ? [obj] : [obj, options]);\n });\n wrap(\"table\", (data, columns) => {\n this.originals.get(\"table\")?.(data, columns);\n rich(\"table\", \"log\", columns === void 0 ? [data] : [data, columns]);\n });\n wrap(\"trace\", (...args) => {\n this.originals.get(\"trace\")?.(...args);\n rich(\"trace\", \"log\", args, { stack: captureStack() });\n });\n wrap(\"assert\", (condition, ...args) => {\n this.originals.get(\"assert\")?.(condition, ...args);\n if (condition) return;\n rich(\"assert\", \"error\", args.length ? args : [\"Assertion failed\"], { stack: captureStack() });\n });\n wrap(\"count\", (label) => {\n this.originals.get(\"count\")?.(label);\n const key = label == null ? \"default\" : String(label);\n const n = (this.counts.get(key) ?? 0) + 1;\n this.counts.set(key, n);\n rich(\"count\", \"log\", [`${key}: ${n}`]);\n });\n wrap(\"countReset\", (label) => {\n this.originals.get(\"countReset\")?.(label);\n const key = label == null ? \"default\" : String(label);\n this.counts.set(key, 0);\n rich(\"countReset\", \"log\", [`${key}: 0`]);\n });\n wrap(\"time\", (label) => {\n this.originals.get(\"time\")?.(label);\n const key = label == null ? \"default\" : String(label);\n this.timers.set(key, performance.now());\n });\n wrap(\"timeEnd\", (label) => {\n this.originals.get(\"timeEnd\")?.(label);\n const key = label == null ? \"default\" : String(label);\n const start = this.timers.get(key);\n if (start === void 0) {\n rich(\"timeEnd\", \"warn\", [`Timer \"${key}\" does not exist`]);\n return;\n }\n this.timers.delete(key);\n rich(\"timeEnd\", \"log\", [`${key}: ${(performance.now() - start).toFixed(3)}ms`]);\n });\n wrap(\"timeLog\", (label, ...args) => {\n this.originals.get(\"timeLog\")?.(label, ...args);\n const key = label == null ? \"default\" : String(label);\n const start = this.timers.get(key);\n if (start === void 0) {\n rich(\"timeLog\", \"warn\", [`Timer \"${key}\" does not exist`, ...args]);\n return;\n }\n rich(\"timeLog\", \"log\", [`${key}: ${(performance.now() - start).toFixed(3)}ms`, ...args]);\n });\n wrap(\"group\", (...args) => {\n this.originals.get(\"group\")?.(...args);\n rich(\"group\", \"log\", args);\n });\n wrap(\"groupCollapsed\", (...args) => {\n this.originals.get(\"groupCollapsed\")?.(...args);\n rich(\"groupCollapsed\", \"log\", args);\n });\n wrap(\"groupEnd\", () => {\n this.originals.get(\"groupEnd\")?.();\n rich(\"groupEnd\", \"log\", []);\n });\n wrap(\"clear\", () => {\n this.originals.get(\"clear\")?.();\n rich(\"clear\", \"log\", []);\n });\n this.onError = (e) => {\n record({\n type: \"console\",\n ts: Date.now(),\n level: \"error\",\n method: \"uncaught\",\n args: [serialize(e.message || \"Uncaught error\")],\n stack: e.error instanceof Error ? e.error.stack : void 0\n });\n };\n this.onRejection = (e) => {\n const reason = e.reason;\n record({\n type: \"console\",\n ts: Date.now(),\n level: \"error\",\n method: \"unhandledrejection\",\n args: [serialize(reason instanceof Error ? reason.message : reason)],\n stack: reason instanceof Error ? reason.stack : void 0\n });\n };\n window.addEventListener(\"error\", this.onError);\n window.addEventListener(\"unhandledrejection\", this.onRejection);\n }\n uninstall() {\n const c = console;\n for (const [name, original] of this.originals) {\n c[name] = original;\n }\n this.originals.clear();\n if (this.onError) window.removeEventListener(\"error\", this.onError);\n if (this.onRejection) window.removeEventListener(\"unhandledrejection\", this.onRejection);\n }\n getLogs(opts = {}) {\n let result = this.logs;\n if (opts.levels && opts.levels.length > 0) {\n const wanted = new Set(opts.levels);\n result = result.filter((m) => wanted.has(m.level));\n }\n if (opts.match) {\n const needle = opts.match.toLowerCase();\n result = result.filter((m) => JSON.stringify(m.args).toLowerCase().includes(needle));\n }\n if (opts.since !== void 0) {\n result = result.filter((m) => m.ts >= opts.since);\n }\n if (opts.limit !== void 0) {\n result = result.slice(-opts.limit);\n } else if (result === this.logs) {\n result = [...this.logs];\n }\n return result;\n }\n };\n function captureStack() {\n const raw = new Error().stack ?? \"\";\n const lines = raw.split(\"\\n\");\n return lines.length > 2 ? lines.slice(2).join(\"\\n\") : raw;\n }\n function serialize(v) {\n if (v === null || v === void 0) return String(v);\n if (typeof v === \"string\") return cap(v);\n if (typeof v !== \"object\") return v;\n if (v instanceof Error) return { name: v.name, message: v.message, stack: v.stack };\n try {\n const json = JSON.stringify(v);\n return json.length > MAX_ARG_CHARS ? cap(json) : JSON.parse(json);\n } catch {\n return String(v);\n }\n }\n function cap(text) {\n return text.length > MAX_ARG_CHARS ? `${text.slice(0, MAX_ARG_CHARS)}\\u2026[truncated]` : text;\n }\n\n // src/interceptors/network.ts\n var MAX_REQUESTS = 1e3;\n var MAX_BODY_CHARS = 1e4;\n var MAX_BODY_BYTES = 64 * 1024;\n var NetworkInterceptor = class {\n constructor() {\n this.requests = [];\n this.origFetch = null;\n this.OrigXHR = null;\n }\n // Captured into a local buffer only \u2014 never streamed. An agent pulls requests\n // on demand via get_network_requests; pushing every request/response over the\n // WebSocket would be wasted traffic since nothing subscribes to it.\n install() {\n this.interceptFetch();\n this.interceptXHR();\n }\n interceptFetch() {\n this.origFetch = window.fetch.bind(window);\n const orig = this.origFetch;\n const requests = this.requests;\n window.fetch = async (input, init) => {\n const url = input instanceof Request ? input.url : String(input);\n const method = resolveMethod(input, init);\n const requestId = uid();\n const startTs = Date.now();\n const requestHeaders = mergeRequestHeaders(input, init);\n const requestBody = serializeRequestBody(init?.body);\n const pending = {\n type: \"network\",\n ts: startTs,\n requestId,\n method,\n url,\n requestHeaders,\n requestBody\n };\n pushRequest(requests, pending);\n try {\n const res = await orig(input, init);\n Object.assign(pending, {\n ts: Date.now(),\n status: res.status,\n duration: Date.now() - startTs,\n responseHeaders: headersToObject(res.headers)\n });\n captureResponseBody(res.clone(), pending);\n return res;\n } catch (e) {\n Object.assign(pending, {\n ts: Date.now(),\n duration: Date.now() - startTs,\n error: e instanceof Error ? e.message : String(e)\n });\n throw e;\n }\n };\n }\n interceptXHR() {\n this.OrigXHR = window.XMLHttpRequest;\n const OrigXHR = this.OrigXHR;\n const requests = this.requests;\n function FeedthroughXHR() {\n const xhr = new OrigXHR();\n let method = \"GET\";\n let url = \"\";\n const requestHeaders = {};\n const origOpen = xhr.open.bind(xhr);\n xhr.open = (m, u, async, user, password) => {\n method = m.toUpperCase();\n url = String(u);\n origOpen(m, String(u), async ?? true, user, password);\n };\n const origSetHeader = xhr.setRequestHeader.bind(xhr);\n xhr.setRequestHeader = (name, value) => {\n requestHeaders[name] = String(value);\n origSetHeader(name, value);\n };\n const origSend = xhr.send.bind(xhr);\n xhr.send = (body) => {\n const requestId = uid();\n const startTs = Date.now();\n const requestBody = serializeRequestBody(body ?? void 0);\n const pending = {\n type: \"network\",\n ts: startTs,\n requestId,\n method,\n url,\n requestHeaders: Object.keys(requestHeaders).length ? { ...requestHeaders } : void 0,\n requestBody\n };\n pushRequest(requests, pending);\n xhr.addEventListener(\"loadend\", () => {\n let responseBody;\n try {\n if (xhr.responseType === \"\" || xhr.responseType === \"text\") {\n responseBody = capText(xhr.responseText);\n } else {\n responseBody = `[XHR responseType=${xhr.responseType}]`;\n }\n } catch {\n }\n Object.assign(pending, {\n ts: Date.now(),\n status: xhr.status,\n duration: Date.now() - startTs,\n responseBody,\n responseHeaders: parseRawHeaders(xhr.getAllResponseHeaders())\n });\n });\n origSend(body);\n };\n return xhr;\n }\n FeedthroughXHR.prototype = OrigXHR.prototype;\n window.XMLHttpRequest = FeedthroughXHR;\n }\n uninstall() {\n if (this.origFetch) window.fetch = this.origFetch;\n if (this.OrigXHR) window.XMLHttpRequest = this.OrigXHR;\n }\n getRequests(filter, since) {\n let all = [...this.requests];\n if (since !== void 0) all = all.filter((r) => r.ts >= since);\n if (!filter) return all;\n const lower = filter.toLowerCase();\n return all.filter((r) => r.url.toLowerCase().includes(lower) || r.method.toLowerCase() === lower);\n }\n };\n function resolveMethod(input, init) {\n return (init?.method ?? (input instanceof Request ? input.method : null) ?? \"GET\").toUpperCase();\n }\n function pushRequest(requests, msg) {\n requests.push(msg);\n if (requests.length > MAX_REQUESTS) requests.shift();\n }\n function mergeRequestHeaders(input, init) {\n const source = init?.headers ?? (input instanceof Request ? input.headers : void 0);\n if (!source) return void 0;\n const out = {};\n if (source instanceof Headers) {\n source.forEach((v, k) => {\n out[k] = v;\n });\n } else if (Array.isArray(source)) {\n for (const [k, v] of source) out[k] = String(v);\n } else {\n for (const [k, v] of Object.entries(source)) out[k] = String(v);\n }\n return Object.keys(out).length ? out : void 0;\n }\n function headersToObject(headers) {\n const out = {};\n headers.forEach((v, k) => {\n out[k] = v;\n });\n return out;\n }\n function parseRawHeaders(raw) {\n if (!raw) return void 0;\n const out = {};\n for (const line of raw.split(\"\\r\\n\")) {\n const idx = line.indexOf(\":\");\n if (idx <= 0) continue;\n const k = line.slice(0, idx).trim();\n const v = line.slice(idx + 1).trim();\n if (k) out[k] = v;\n }\n return Object.keys(out).length ? out : void 0;\n }\n function serializeRequestBody(body) {\n if (body == null) return void 0;\n if (typeof body === \"string\") return capText(body);\n if (body instanceof URLSearchParams) return capText(body.toString());\n if (body instanceof FormData) {\n const obj = {};\n body.forEach((v, k) => {\n obj[k] = v instanceof File ? `[File: ${v.name}, ${v.size} bytes]` : v;\n });\n try {\n return capText(JSON.stringify(obj));\n } catch {\n return \"[FormData]\";\n }\n }\n if (body instanceof Blob) return `[Blob: ${body.size} bytes, ${body.type || \"unknown\"}]`;\n if (body instanceof ArrayBuffer) return `[ArrayBuffer: ${body.byteLength} bytes]`;\n if (ArrayBuffer.isView(body)) return `[${body.constructor.name}: ${body.byteLength} bytes]`;\n if (body instanceof ReadableStream) return \"[ReadableStream]\";\n try {\n return capText(JSON.stringify(body));\n } catch {\n return String(body);\n }\n }\n function capText(text) {\n if (text.length <= MAX_BODY_CHARS) return text;\n return `${text.slice(0, MAX_BODY_CHARS)}\\u2026[truncated, ${text.length - MAX_BODY_CHARS} more chars]`;\n }\n function captureResponseBody(res, pending) {\n const ct = (res.headers.get(\"content-type\") ?? \"\").toLowerCase();\n if (isSkippable(ct)) {\n const len = res.headers.get(\"content-length\");\n pending.responseBody = `[${describe(ct)}${len ? `, ${len} bytes` : \"\"}, ${ct || \"no content-type\"}]`;\n return;\n }\n readBounded(res).then((body) => {\n pending.responseBody = body;\n }).catch(() => {\n });\n }\n async function readBounded(res) {\n if (!res.body) return capText(await res.text());\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let text = \"\";\n let bytes = 0;\n let truncated = false;\n try {\n for (; ; ) {\n const { done, value } = await reader.read();\n if (done) break;\n bytes += value.byteLength;\n text += decoder.decode(value, { stream: true });\n if (bytes >= MAX_BODY_BYTES || text.length >= MAX_BODY_CHARS) {\n truncated = true;\n break;\n }\n }\n } finally {\n reader.cancel().catch(() => {\n });\n }\n if (!truncated) return capText(text);\n return `${text.slice(0, MAX_BODY_CHARS)}\\u2026[truncated]`;\n }\n function isSkippable(ct) {\n return /^(image|video|audio|font)\\//.test(ct) || ct.startsWith(\"application/octet-stream\") || ct.startsWith(\"application/pdf\") || ct.startsWith(\"application/zip\") || ct.startsWith(\"text/event-stream\") || // SSE \u2014 never-ending stream\n ct.startsWith(\"application/x-ndjson\");\n }\n function describe(ct) {\n if (ct.startsWith(\"text/event-stream\")) return \"event stream\";\n if (ct.startsWith(\"application/x-ndjson\")) return \"ndjson stream\";\n return \"binary\";\n }\n var counter = 0;\n function uid() {\n return `${Date.now()}-${++counter}`;\n }\n\n // src/transport.ts\n var MAX_QUEUE = 1e3;\n var Transport = class {\n constructor(url, onMessage, onStatus, reconnectDelay) {\n this.url = url;\n this.onMessage = onMessage;\n this.onStatus = onStatus;\n this.reconnectDelay = reconnectDelay;\n this.ws = null;\n this.queue = [];\n this.reconnectTimer = null;\n this.destroyed = false;\n }\n connect() {\n if (this.destroyed) return;\n const ws = new WebSocket(this.url);\n this.ws = ws;\n const isCurrent = () => this.ws === ws;\n ws.onopen = () => {\n if (!isCurrent()) return;\n this.onStatus(true);\n for (const msg of this.queue) ws.send(msg);\n this.queue = [];\n };\n ws.onmessage = (event) => {\n if (!isCurrent()) return;\n try {\n this.onMessage(JSON.parse(event.data));\n } catch {\n }\n };\n ws.onclose = () => {\n if (!isCurrent()) return;\n this.onStatus(false);\n if (!this.destroyed) {\n this.reconnectTimer = setTimeout(() => this.connect(), this.reconnectDelay);\n }\n };\n ws.onerror = () => {\n };\n }\n send(msg) {\n const serialized = JSON.stringify(msg);\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(serialized);\n } else {\n this.queue.push(serialized);\n if (this.queue.length > MAX_QUEUE) this.queue.shift();\n }\n }\n destroy() {\n this.destroyed = true;\n if (this.reconnectTimer !== null) clearTimeout(this.reconnectTimer);\n this.ws?.close();\n }\n };\n\n // src/bridge.ts\n var DEFAULT_SERVER_URL = \"ws://localhost:8765\";\n var FeedthroughBridge = class {\n constructor(options = {}) {\n const url = options.serverUrl ?? DEFAULT_SERVER_URL;\n const reconnectDelay = options.reconnectDelay ?? 2e3;\n this.consoleInterceptor = new ConsoleInterceptor();\n this.networkInterceptor = new NetworkInterceptor();\n this.transport = new Transport(\n url,\n (msg) => this.commandHandler.handle(msg),\n (connected) => {\n if (connected) this.transport.send({ type: \"hello\", url: window.location.href });\n },\n reconnectDelay\n );\n this.commandHandler = new CommandHandler(\n this.transport,\n this.consoleInterceptor,\n this.networkInterceptor\n );\n }\n connect() {\n this.consoleInterceptor.install();\n this.networkInterceptor.install();\n this.transport.connect();\n }\n destroy() {\n this.transport.destroy();\n this.consoleInterceptor.uninstall();\n this.networkInterceptor.uninstall();\n }\n };\n\n // src/browser.ts\n window.__feedthrough = new FeedthroughBridge(window.__feedthroughOptions ?? {});\n window.__feedthrough.connect();\n})();\n";
//# sourceMappingURL=bundle.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../src/generated/bundle.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,YAAY,k/6BAAw+6B,CAAC"}
{"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../src/generated/bundle.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,YAAY,u4xCAA63xC,CAAC"}
// AUTO-GENERATED by scripts/embed-bundle.mjs — do not edit
export const bridgeBundle = "\"use strict\";\n(() => {\n // src/transport.ts\n var MAX_QUEUE = 1e3;\n var Transport = class {\n constructor(url, onMessage, onStatus, reconnectDelay) {\n this.url = url;\n this.onMessage = onMessage;\n this.onStatus = onStatus;\n this.reconnectDelay = reconnectDelay;\n this.ws = null;\n this.queue = [];\n this.reconnectTimer = null;\n this.destroyed = false;\n }\n connect() {\n if (this.destroyed) return;\n this.ws = new WebSocket(this.url);\n this.ws.onopen = () => {\n this.onStatus(true);\n for (const msg of this.queue) this.ws.send(msg);\n this.queue = [];\n };\n this.ws.onmessage = (event) => {\n try {\n this.onMessage(JSON.parse(event.data));\n } catch {\n }\n };\n this.ws.onclose = () => {\n this.onStatus(false);\n if (!this.destroyed) {\n this.reconnectTimer = setTimeout(() => this.connect(), this.reconnectDelay);\n }\n };\n this.ws.onerror = () => {\n };\n }\n send(msg) {\n const serialized = JSON.stringify(msg);\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(serialized);\n } else {\n this.queue.push(serialized);\n if (this.queue.length > MAX_QUEUE) this.queue.shift();\n }\n }\n destroy() {\n this.destroyed = true;\n if (this.reconnectTimer !== null) clearTimeout(this.reconnectTimer);\n this.ws?.close();\n }\n };\n\n // src/interceptors/console.ts\n var STD_LEVELS = [\"log\", \"warn\", \"error\", \"info\", \"debug\"];\n var MAX_LOGS = 1e3;\n var MAX_ARG_CHARS = 1e4;\n var ConsoleInterceptor = class {\n constructor() {\n this.originals = /* @__PURE__ */ new Map();\n this.logs = [];\n this.counts = /* @__PURE__ */ new Map();\n this.timers = /* @__PURE__ */ new Map();\n }\n install() {\n const c = console;\n const record = (msg) => {\n this.logs.push(msg);\n if (this.logs.length > MAX_LOGS) this.logs.shift();\n };\n const rich = (method, level, args, extras = {}) => {\n record({ type: \"console\", ts: Date.now(), level, method, args: args.map(serialize), ...extras });\n };\n const wrap = (name, fn) => {\n const orig = c[name];\n if (typeof orig !== \"function\") return;\n this.originals.set(name, orig.bind(console));\n c[name] = fn;\n };\n for (const level of STD_LEVELS) {\n const orig = c[level].bind(console);\n this.originals.set(level, orig);\n c[level] = (...args) => {\n orig(...args);\n record({ type: \"console\", ts: Date.now(), level, args: args.map(serialize) });\n };\n }\n wrap(\"dir\", (obj, options) => {\n this.originals.get(\"dir\")(obj, options);\n rich(\"dir\", \"log\", options === void 0 ? [obj] : [obj, options]);\n });\n wrap(\"table\", (data, columns) => {\n this.originals.get(\"table\")(data, columns);\n rich(\"table\", \"log\", columns === void 0 ? [data] : [data, columns]);\n });\n wrap(\"trace\", (...args) => {\n this.originals.get(\"trace\")(...args);\n rich(\"trace\", \"log\", args, { stack: captureStack() });\n });\n wrap(\"assert\", (condition, ...args) => {\n this.originals.get(\"assert\")(condition, ...args);\n if (condition) return;\n rich(\"assert\", \"error\", args.length ? args : [\"Assertion failed\"], { stack: captureStack() });\n });\n wrap(\"count\", (label) => {\n this.originals.get(\"count\")(label);\n const key = label == null ? \"default\" : String(label);\n const n = (this.counts.get(key) ?? 0) + 1;\n this.counts.set(key, n);\n rich(\"count\", \"log\", [`${key}: ${n}`]);\n });\n wrap(\"countReset\", (label) => {\n this.originals.get(\"countReset\")(label);\n const key = label == null ? \"default\" : String(label);\n this.counts.set(key, 0);\n rich(\"countReset\", \"log\", [`${key}: 0`]);\n });\n wrap(\"time\", (label) => {\n this.originals.get(\"time\")(label);\n const key = label == null ? \"default\" : String(label);\n this.timers.set(key, performance.now());\n });\n wrap(\"timeEnd\", (label) => {\n this.originals.get(\"timeEnd\")(label);\n const key = label == null ? \"default\" : String(label);\n const start = this.timers.get(key);\n if (start === void 0) {\n rich(\"timeEnd\", \"warn\", [`Timer \"${key}\" does not exist`]);\n return;\n }\n this.timers.delete(key);\n rich(\"timeEnd\", \"log\", [`${key}: ${(performance.now() - start).toFixed(3)}ms`]);\n });\n wrap(\"timeLog\", (label, ...args) => {\n this.originals.get(\"timeLog\")(label, ...args);\n const key = label == null ? \"default\" : String(label);\n const start = this.timers.get(key);\n if (start === void 0) {\n rich(\"timeLog\", \"warn\", [`Timer \"${key}\" does not exist`, ...args]);\n return;\n }\n rich(\"timeLog\", \"log\", [`${key}: ${(performance.now() - start).toFixed(3)}ms`, ...args]);\n });\n wrap(\"group\", (...args) => {\n this.originals.get(\"group\")(...args);\n rich(\"group\", \"log\", args);\n });\n wrap(\"groupCollapsed\", (...args) => {\n this.originals.get(\"groupCollapsed\")(...args);\n rich(\"groupCollapsed\", \"log\", args);\n });\n wrap(\"groupEnd\", () => {\n this.originals.get(\"groupEnd\")();\n rich(\"groupEnd\", \"log\", []);\n });\n wrap(\"clear\", () => {\n this.originals.get(\"clear\")();\n rich(\"clear\", \"log\", []);\n });\n this.onError = (e) => {\n record({\n type: \"console\",\n ts: Date.now(),\n level: \"error\",\n method: \"uncaught\",\n args: [serialize(e.message || \"Uncaught error\")],\n stack: e.error instanceof Error ? e.error.stack : void 0\n });\n };\n this.onRejection = (e) => {\n const reason = e.reason;\n record({\n type: \"console\",\n ts: Date.now(),\n level: \"error\",\n method: \"unhandledrejection\",\n args: [serialize(reason instanceof Error ? reason.message : reason)],\n stack: reason instanceof Error ? reason.stack : void 0\n });\n };\n window.addEventListener(\"error\", this.onError);\n window.addEventListener(\"unhandledrejection\", this.onRejection);\n }\n uninstall() {\n const c = console;\n for (const [name, original] of this.originals) {\n c[name] = original;\n }\n this.originals.clear();\n if (this.onError) window.removeEventListener(\"error\", this.onError);\n if (this.onRejection) window.removeEventListener(\"unhandledrejection\", this.onRejection);\n }\n getLogs(opts = {}) {\n let result = this.logs;\n if (opts.levels && opts.levels.length > 0) {\n const wanted = new Set(opts.levels);\n result = result.filter((m) => wanted.has(m.level));\n }\n if (opts.match) {\n const needle = opts.match.toLowerCase();\n result = result.filter((m) => JSON.stringify(m.args).toLowerCase().includes(needle));\n }\n if (opts.since !== void 0) {\n result = result.filter((m) => m.ts >= opts.since);\n }\n if (opts.limit !== void 0) {\n result = result.slice(-opts.limit);\n } else if (result === this.logs) {\n result = [...this.logs];\n }\n return result;\n }\n };\n function captureStack() {\n const raw = new Error().stack ?? \"\";\n const lines = raw.split(\"\\n\");\n return lines.length > 2 ? lines.slice(2).join(\"\\n\") : raw;\n }\n function serialize(v) {\n if (v === null || v === void 0) return String(v);\n if (typeof v === \"string\") return cap(v);\n if (typeof v !== \"object\") return v;\n if (v instanceof Error) return { name: v.name, message: v.message, stack: v.stack };\n try {\n const json = JSON.stringify(v);\n return json.length > MAX_ARG_CHARS ? cap(json) : JSON.parse(json);\n } catch {\n return String(v);\n }\n }\n function cap(text) {\n return text.length > MAX_ARG_CHARS ? text.slice(0, MAX_ARG_CHARS) + \"\\u2026[truncated]\" : text;\n }\n\n // src/interceptors/network.ts\n var MAX_REQUESTS = 1e3;\n var MAX_BODY_CHARS = 1e4;\n var MAX_BODY_BYTES = 64 * 1024;\n var NetworkInterceptor = class {\n constructor() {\n this.requests = [];\n this.origFetch = null;\n this.OrigXHR = null;\n }\n // Captured into a local buffer only — never streamed. An agent pulls requests\n // on demand via get_network_requests; pushing every request/response over the\n // WebSocket would be wasted traffic since nothing subscribes to it.\n install() {\n this.interceptFetch();\n this.interceptXHR();\n }\n interceptFetch() {\n this.origFetch = window.fetch.bind(window);\n const orig = this.origFetch;\n const requests = this.requests;\n window.fetch = async (input, init) => {\n const url = input instanceof Request ? input.url : String(input);\n const method = resolveMethod(input, init);\n const requestId = uid();\n const startTs = Date.now();\n const requestHeaders = mergeRequestHeaders(input, init);\n const requestBody = serializeRequestBody(init?.body);\n const pending = {\n type: \"network\",\n ts: startTs,\n requestId,\n method,\n url,\n requestHeaders,\n requestBody\n };\n pushRequest(requests, pending);\n try {\n const res = await orig(input, init);\n Object.assign(pending, {\n ts: Date.now(),\n status: res.status,\n duration: Date.now() - startTs,\n responseHeaders: headersToObject(res.headers)\n });\n captureResponseBody(res.clone(), pending);\n return res;\n } catch (e) {\n Object.assign(pending, {\n ts: Date.now(),\n duration: Date.now() - startTs,\n error: e instanceof Error ? e.message : String(e)\n });\n throw e;\n }\n };\n }\n interceptXHR() {\n this.OrigXHR = window.XMLHttpRequest;\n const OrigXHR = this.OrigXHR;\n const requests = this.requests;\n function FeedthroughXHR() {\n const xhr = new OrigXHR();\n let method = \"GET\";\n let url = \"\";\n const requestHeaders = {};\n const origOpen = xhr.open.bind(xhr);\n xhr.open = (m, u, async, user, password) => {\n method = m.toUpperCase();\n url = String(u);\n origOpen(m, String(u), async ?? true, user, password);\n };\n const origSetHeader = xhr.setRequestHeader.bind(xhr);\n xhr.setRequestHeader = (name, value) => {\n requestHeaders[name] = String(value);\n origSetHeader(name, value);\n };\n const origSend = xhr.send.bind(xhr);\n xhr.send = (body) => {\n const requestId = uid();\n const startTs = Date.now();\n const requestBody = serializeRequestBody(body ?? void 0);\n const pending = {\n type: \"network\",\n ts: startTs,\n requestId,\n method,\n url,\n requestHeaders: Object.keys(requestHeaders).length ? { ...requestHeaders } : void 0,\n requestBody\n };\n pushRequest(requests, pending);\n xhr.addEventListener(\"loadend\", () => {\n let responseBody;\n try {\n if (xhr.responseType === \"\" || xhr.responseType === \"text\") {\n responseBody = capText(xhr.responseText);\n } else {\n responseBody = `[XHR responseType=${xhr.responseType}]`;\n }\n } catch {\n }\n Object.assign(pending, {\n ts: Date.now(),\n status: xhr.status,\n duration: Date.now() - startTs,\n responseBody,\n responseHeaders: parseRawHeaders(xhr.getAllResponseHeaders())\n });\n });\n origSend(body);\n };\n return xhr;\n }\n FeedthroughXHR.prototype = OrigXHR.prototype;\n window.XMLHttpRequest = FeedthroughXHR;\n }\n uninstall() {\n if (this.origFetch) window.fetch = this.origFetch;\n if (this.OrigXHR) window.XMLHttpRequest = this.OrigXHR;\n }\n getRequests(filter, since) {\n let all = [...this.requests];\n if (since !== void 0) all = all.filter((r) => r.ts >= since);\n if (!filter) return all;\n const lower = filter.toLowerCase();\n return all.filter((r) => r.url.toLowerCase().includes(lower) || r.method.toLowerCase() === lower);\n }\n };\n function resolveMethod(input, init) {\n return (init?.method ?? (input instanceof Request ? input.method : null) ?? \"GET\").toUpperCase();\n }\n function pushRequest(requests, msg) {\n requests.push(msg);\n if (requests.length > MAX_REQUESTS) requests.shift();\n }\n function mergeRequestHeaders(input, init) {\n const source = init?.headers ?? (input instanceof Request ? input.headers : void 0);\n if (!source) return void 0;\n const out = {};\n if (source instanceof Headers) {\n source.forEach((v, k) => {\n out[k] = v;\n });\n } else if (Array.isArray(source)) {\n for (const [k, v] of source) out[k] = String(v);\n } else {\n for (const [k, v] of Object.entries(source)) out[k] = String(v);\n }\n return Object.keys(out).length ? out : void 0;\n }\n function headersToObject(headers) {\n const out = {};\n headers.forEach((v, k) => {\n out[k] = v;\n });\n return out;\n }\n function parseRawHeaders(raw) {\n if (!raw) return void 0;\n const out = {};\n for (const line of raw.split(\"\\r\\n\")) {\n const idx = line.indexOf(\":\");\n if (idx <= 0) continue;\n const k = line.slice(0, idx).trim();\n const v = line.slice(idx + 1).trim();\n if (k) out[k] = v;\n }\n return Object.keys(out).length ? out : void 0;\n }\n function serializeRequestBody(body) {\n if (body == null) return void 0;\n if (typeof body === \"string\") return capText(body);\n if (body instanceof URLSearchParams) return capText(body.toString());\n if (body instanceof FormData) {\n const obj = {};\n body.forEach((v, k) => {\n obj[k] = v instanceof File ? `[File: ${v.name}, ${v.size} bytes]` : v;\n });\n try {\n return capText(JSON.stringify(obj));\n } catch {\n return \"[FormData]\";\n }\n }\n if (body instanceof Blob) return `[Blob: ${body.size} bytes, ${body.type || \"unknown\"}]`;\n if (body instanceof ArrayBuffer) return `[ArrayBuffer: ${body.byteLength} bytes]`;\n if (ArrayBuffer.isView(body)) return `[${body.constructor.name}: ${body.byteLength} bytes]`;\n if (body instanceof ReadableStream) return \"[ReadableStream]\";\n try {\n return capText(JSON.stringify(body));\n } catch {\n return String(body);\n }\n }\n function capText(text) {\n if (text.length <= MAX_BODY_CHARS) return text;\n return text.slice(0, MAX_BODY_CHARS) + `\\u2026[truncated, ${text.length - MAX_BODY_CHARS} more chars]`;\n }\n function captureResponseBody(res, pending) {\n const ct = (res.headers.get(\"content-type\") ?? \"\").toLowerCase();\n if (isSkippable(ct)) {\n const len = res.headers.get(\"content-length\");\n pending.responseBody = `[${describe(ct)}${len ? `, ${len} bytes` : \"\"}, ${ct || \"no content-type\"}]`;\n return;\n }\n readBounded(res).then((body) => {\n pending.responseBody = body;\n }).catch(() => {\n });\n }\n async function readBounded(res) {\n if (!res.body) return capText(await res.text());\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let text = \"\";\n let bytes = 0;\n let truncated = false;\n try {\n for (; ; ) {\n const { done, value } = await reader.read();\n if (done) break;\n bytes += value.byteLength;\n text += decoder.decode(value, { stream: true });\n if (bytes >= MAX_BODY_BYTES || text.length >= MAX_BODY_CHARS) {\n truncated = true;\n break;\n }\n }\n } finally {\n reader.cancel().catch(() => {\n });\n }\n if (!truncated) return capText(text);\n return text.slice(0, MAX_BODY_CHARS) + \"\\u2026[truncated]\";\n }\n function isSkippable(ct) {\n return /^(image|video|audio|font)\\//.test(ct) || ct.startsWith(\"application/octet-stream\") || ct.startsWith(\"application/pdf\") || ct.startsWith(\"application/zip\") || ct.startsWith(\"text/event-stream\") || // SSE — never-ending stream\n ct.startsWith(\"application/x-ndjson\");\n }\n function describe(ct) {\n if (ct.startsWith(\"text/event-stream\")) return \"event stream\";\n if (ct.startsWith(\"application/x-ndjson\")) return \"ndjson stream\";\n return \"binary\";\n }\n var counter = 0;\n function uid() {\n return `${Date.now()}-${++counter}`;\n }\n\n // src/commands.ts\n var CommandHandler = class {\n constructor(transport, console2, network) {\n this.transport = transport;\n this.console = console2;\n this.network = network;\n }\n handle(msg) {\n if (!isCommand(msg)) return;\n let value;\n let error;\n try {\n value = this.dispatch(msg);\n } catch (e) {\n error = e instanceof Error ? e.message : String(e);\n }\n const result = {\n type: \"result\",\n ts: Date.now(),\n commandId: msg.id,\n ok: error === void 0,\n value,\n error\n };\n this.transport.send(result);\n }\n dispatch(cmd) {\n switch (cmd.action) {\n case \"click\":\n return clickEl(cmd.selector);\n case \"fill\":\n return fillEl(cmd.selector, cmd.value);\n case \"hover\":\n return hoverEl(cmd.selector);\n case \"inspect\":\n return inspectEl(cmd.selector, cmd.properties);\n case \"query_dom\":\n return queryDom(cmd.selector);\n case \"get_console_logs\":\n return this.console.getLogs({ limit: cmd.limit, levels: cmd.levels, match: cmd.match, since: cmd.since });\n case \"get_network_requests\":\n return this.network.getRequests(cmd.filter, cmd.since);\n case \"press_key\":\n return pressKey(cmd.selector, cmd.key);\n case \"get_html\":\n return getHtml(cmd.selector);\n case \"get_page_info\":\n return getPageInfo();\n case \"set_style\":\n return setStyle(cmd.selector, cmd.properties);\n case \"set_attribute\":\n return setAttribute(cmd.selector, cmd.name, cmd.value);\n case \"set_text\":\n return setText(cmd.selector, cmd.text);\n case \"reset_overrides\":\n return resetOverrides();\n }\n }\n };\n function getEl(selector) {\n const el = document.querySelector(selector);\n if (!el) throw new Error(`no element matches \"${selector}\"`);\n return el;\n }\n function clickEl(selector) {\n const el = getEl(selector);\n el.click();\n return { tag: el.tagName.toLowerCase(), id: el.id || null };\n }\n function fillEl(selector, value) {\n const el = getEl(selector);\n el.focus();\n const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : el instanceof HTMLSelectElement ? HTMLSelectElement.prototype : HTMLInputElement.prototype;\n const nativeSetter = Object.getOwnPropertyDescriptor(proto, \"value\")?.set;\n if (nativeSetter) {\n nativeSetter.call(el, value);\n } else {\n el.value = value;\n }\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n el.dispatchEvent(new Event(\"change\", { bubbles: true }));\n return { tag: el.tagName.toLowerCase(), value };\n }\n function hoverEl(selector) {\n const el = getEl(selector);\n el.dispatchEvent(new MouseEvent(\"mouseover\", { bubbles: true }));\n el.dispatchEvent(new MouseEvent(\"mouseenter\", { bubbles: true }));\n return { tag: el.tagName.toLowerCase() };\n }\n var DEFAULT_STYLE_PROPS = [\n \"display\",\n \"position\",\n \"visibility\",\n \"opacity\",\n \"z-index\",\n \"box-sizing\",\n \"top\",\n \"right\",\n \"bottom\",\n \"left\",\n \"width\",\n \"height\",\n \"margin\",\n \"padding\",\n \"border\",\n \"color\",\n \"background-color\",\n \"font-family\",\n \"font-size\",\n \"font-weight\",\n \"line-height\",\n \"text-align\",\n \"overflow\",\n \"cursor\",\n \"pointer-events\",\n \"flex\",\n \"flex-direction\",\n \"justify-content\",\n \"align-items\",\n \"gap\",\n \"grid-template-columns\",\n \"transform\"\n ];\n function inspectEl(selector, properties) {\n const el = getEl(selector);\n const rect = el.getBoundingClientRect();\n const cs = window.getComputedStyle(el);\n const result = {\n tag: el.tagName.toLowerCase(),\n id: el.id || null,\n classes: Array.from(el.classList),\n attributes: Object.fromEntries(Array.from(el.attributes).map((a) => [a.name, a.value])),\n textContent: el.textContent?.trim().slice(0, 200),\n rect: {\n top: rect.top,\n right: rect.right,\n bottom: rect.bottom,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n x: rect.x,\n y: rect.y\n },\n scroll: { x: window.scrollX, y: window.scrollY },\n inViewport: rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth,\n styles: pickStyles(cs, DEFAULT_STYLE_PROPS)\n };\n const state = elementState(el);\n if (state) result.state = state;\n if (properties && properties.length > 0) {\n const requested = {};\n for (const p of properties) requested[p] = cs.getPropertyValue(p);\n result.requested = requested;\n }\n return result;\n }\n function pickStyles(cs, props) {\n const out = {};\n for (const p of props) {\n const v = cs.getPropertyValue(p);\n if (v) out[p] = v;\n }\n return out;\n }\n function elementState(el) {\n const s = {};\n if (el instanceof HTMLInputElement) {\n s.value = capValue(el.value);\n s.type = el.type;\n s.checked = el.checked;\n s.disabled = el.disabled;\n s.readOnly = el.readOnly;\n s.required = el.required;\n if (el.placeholder) s.placeholder = el.placeholder;\n if (el.validationMessage) s.validationMessage = el.validationMessage;\n } else if (el instanceof HTMLTextAreaElement) {\n s.value = capValue(el.value);\n s.disabled = el.disabled;\n s.readOnly = el.readOnly;\n } else if (el instanceof HTMLSelectElement) {\n s.value = el.value;\n s.selectedIndex = el.selectedIndex;\n s.disabled = el.disabled;\n } else if (el instanceof HTMLButtonElement) {\n s.type = el.type;\n s.disabled = el.disabled;\n } else if (el instanceof HTMLAnchorElement) {\n s.href = el.href;\n }\n if (el instanceof HTMLElement && Object.keys(el.dataset).length > 0) {\n s.dataset = { ...el.dataset };\n }\n return Object.keys(s).length > 0 ? s : void 0;\n }\n function capValue(v) {\n return v.length > 1e3 ? v.slice(0, 1e3) + \"\\u2026[truncated]\" : v;\n }\n function queryDom(selector) {\n return Array.from(document.querySelectorAll(selector)).map((el) => ({\n tag: el.tagName.toLowerCase(),\n id: el.id || null,\n classes: Array.from(el.classList),\n textContent: el.textContent?.trim().slice(0, 100)\n }));\n }\n var NAMED_KEYS = {\n Enter: { code: \"Enter\", keyCode: 13 },\n Tab: { code: \"Tab\", keyCode: 9 },\n Escape: { code: \"Escape\", keyCode: 27 },\n Backspace: { code: \"Backspace\", keyCode: 8 },\n Delete: { code: \"Delete\", keyCode: 46 },\n ArrowUp: { code: \"ArrowUp\", keyCode: 38 },\n ArrowDown: { code: \"ArrowDown\", keyCode: 40 },\n ArrowLeft: { code: \"ArrowLeft\", keyCode: 37 },\n ArrowRight: { code: \"ArrowRight\", keyCode: 39 },\n \" \": { code: \"Space\", keyCode: 32 }\n };\n function pressKey(selector, key) {\n const el = getEl(selector);\n el.focus?.();\n const named = NAMED_KEYS[key];\n const keyCode = named ? named.keyCode : key.length === 1 ? key.toUpperCase().charCodeAt(0) : 0;\n const code = named ? named.code : key.length === 1 ? `Key${key.toUpperCase()}` : key;\n const init = {\n key,\n code,\n keyCode,\n which: keyCode,\n bubbles: true,\n cancelable: true\n };\n el.dispatchEvent(new KeyboardEvent(\"keydown\", init));\n if (key.length === 1) el.dispatchEvent(new KeyboardEvent(\"keypress\", init));\n el.dispatchEvent(new KeyboardEvent(\"keyup\", init));\n return { tag: el.tagName.toLowerCase(), key };\n }\n var MAX_HTML_CHARS = 5e4;\n function getHtml(selector) {\n const el = getEl(selector);\n const html = el.outerHTML;\n const truncated = html.length > MAX_HTML_CHARS;\n return {\n tag: el.tagName.toLowerCase(),\n html: truncated ? html.slice(0, MAX_HTML_CHARS) + \"\\u2026[truncated]\" : html,\n truncated\n };\n }\n function getPageInfo() {\n return {\n url: window.location.href,\n title: document.title,\n readyState: document.readyState,\n viewport: { width: window.innerWidth, height: window.innerHeight },\n scroll: { x: window.scrollX, y: window.scrollY },\n userAgent: navigator.userAgent\n };\n }\n var PREVIEW_NOTE = \"Live preview only \\u2014 applied to the running DOM, not saved to source, and reset on reload/HMR. Once the user is happy, edit the actual source to make it permanent.\";\n var CLOBBER_WARNING = \"This may be reverted on the next framework render (React/Vue/etc. re-render this element from component state). If it snaps back, change it in the source instead of here.\";\n var FRAMEWORK_OWNED_ATTRS = /* @__PURE__ */ new Set([\"class\", \"style\", \"value\", \"checked\", \"disabled\", \"selected\"]);\n var overrides = [];\n function setStyle(selector, properties) {\n const el = getEl(selector);\n if (!el.style) throw new Error(`element \"${selector}\" has no style (not an HTMLElement)`);\n const applied = {};\n for (const [prop, value] of Object.entries(properties)) {\n const prevValue = el.style.getPropertyValue(prop);\n const prevPriority = el.style.getPropertyPriority(prop);\n overrides.push(() => {\n if (prevValue) el.style.setProperty(prop, prevValue, prevPriority);\n else el.style.removeProperty(prop);\n });\n el.style.setProperty(prop, value);\n applied[prop] = el.style.getPropertyValue(prop);\n }\n return { tag: el.tagName.toLowerCase(), applied, note: PREVIEW_NOTE };\n }\n function setAttribute(selector, name, value) {\n const el = getEl(selector);\n const had = el.hasAttribute(name);\n const prev = had ? el.getAttribute(name) : null;\n overrides.push(() => {\n if (had) el.setAttribute(name, prev ?? \"\");\n else el.removeAttribute(name);\n });\n if (value === null) el.removeAttribute(name);\n else el.setAttribute(name, value);\n const result = {\n tag: el.tagName.toLowerCase(),\n name,\n value: value === null ? null : el.getAttribute(name),\n removed: value === null,\n note: PREVIEW_NOTE\n };\n if (FRAMEWORK_OWNED_ATTRS.has(name.toLowerCase())) result.frameworkWarning = CLOBBER_WARNING;\n return result;\n }\n function setText(selector, text) {\n const el = getEl(selector);\n const prev = el.textContent;\n overrides.push(() => {\n el.textContent = prev;\n });\n el.textContent = text;\n return {\n tag: el.tagName.toLowerCase(),\n text,\n note: PREVIEW_NOTE,\n frameworkWarning: CLOBBER_WARNING\n };\n }\n function resetOverrides() {\n const count = overrides.length;\n while (overrides.length) overrides.pop()();\n return { reverted: count, note: \"All bridge-applied DOM changes since connect have been undone (best effort \\u2014 elements re-created by the framework since may not roll back).\" };\n }\n function isCommand(v) {\n return typeof v === \"object\" && v !== null && v.type === \"command\" && typeof v.id === \"string\";\n }\n\n // src/bridge.ts\n var DEFAULT_SERVER_URL = \"ws://localhost:8765\";\n var FeedthroughBridge = class {\n constructor(options = {}) {\n const url = options.serverUrl ?? DEFAULT_SERVER_URL;\n const reconnectDelay = options.reconnectDelay ?? 2e3;\n this.consoleInterceptor = new ConsoleInterceptor();\n this.networkInterceptor = new NetworkInterceptor();\n this.transport = new Transport(\n url,\n (msg) => this.commandHandler.handle(msg),\n (connected) => {\n if (connected) this.transport.send({ type: \"hello\", url: window.location.href });\n },\n reconnectDelay\n );\n this.commandHandler = new CommandHandler(this.transport, this.consoleInterceptor, this.networkInterceptor);\n }\n connect() {\n this.consoleInterceptor.install();\n this.networkInterceptor.install();\n this.transport.connect();\n }\n destroy() {\n this.transport.destroy();\n this.consoleInterceptor.uninstall();\n this.networkInterceptor.uninstall();\n }\n };\n\n // src/browser.ts\n window.__feedthrough = new FeedthroughBridge(window.__feedthroughOptions ?? {});\n window.__feedthrough.connect();\n})();\n";
export const bridgeBundle = "\"use strict\";\n(() => {\n // src/commands.ts\n var CommandHandler = class {\n constructor(transport, console2, network) {\n this.transport = transport;\n this.console = console2;\n this.network = network;\n }\n handle(msg) {\n if (!isCommand(msg)) return;\n let value;\n let error;\n try {\n value = this.dispatch(msg);\n } catch (e) {\n error = e instanceof Error ? e.message : String(e);\n }\n const result = {\n type: \"result\",\n ts: Date.now(),\n commandId: msg.id,\n ok: error === void 0,\n value,\n error\n };\n this.transport.send(result);\n }\n dispatch(cmd) {\n switch (cmd.action) {\n case \"click\":\n return clickEl(cmd.selector);\n case \"fill\":\n return fillEl(cmd.selector, cmd.value);\n case \"hover\":\n return hoverEl(cmd.selector);\n case \"inspect\":\n return inspectEl(cmd.selector, cmd.properties);\n case \"query_dom\":\n return queryDom(cmd.selector);\n case \"get_console_logs\":\n return this.console.getLogs({\n limit: cmd.limit,\n levels: cmd.levels,\n match: cmd.match,\n since: cmd.since\n });\n case \"get_network_requests\":\n return this.network.getRequests(cmd.filter, cmd.since);\n case \"press_key\":\n return pressKey(cmd.selector, cmd.key);\n case \"get_html\":\n return getHtml(cmd.selector);\n case \"get_page_info\":\n return getPageInfo();\n case \"set_style\":\n return setStyle(cmd.selector, cmd.properties);\n case \"set_attribute\":\n return setAttribute(cmd.selector, cmd.name, cmd.value);\n case \"set_text\":\n return setText(cmd.selector, cmd.text);\n case \"reset_overrides\":\n return resetOverrides();\n }\n }\n };\n function getEl(selector) {\n const el = document.querySelector(selector);\n if (!el) throw new Error(`no element matches \"${selector}\"`);\n return el;\n }\n function clickEl(selector) {\n const el = getEl(selector);\n el.click();\n return { tag: el.tagName.toLowerCase(), id: el.id || null };\n }\n function fillEl(selector, value) {\n const el = getEl(selector);\n el.focus();\n const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : el instanceof HTMLSelectElement ? HTMLSelectElement.prototype : HTMLInputElement.prototype;\n const nativeSetter = Object.getOwnPropertyDescriptor(proto, \"value\")?.set;\n if (nativeSetter) {\n nativeSetter.call(el, value);\n } else {\n el.value = value;\n }\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n el.dispatchEvent(new Event(\"change\", { bubbles: true }));\n return { tag: el.tagName.toLowerCase(), value };\n }\n function hoverEl(selector) {\n const el = getEl(selector);\n el.dispatchEvent(new MouseEvent(\"mouseover\", { bubbles: true }));\n el.dispatchEvent(new MouseEvent(\"mouseenter\", { bubbles: true }));\n return { tag: el.tagName.toLowerCase() };\n }\n var DEFAULT_STYLE_PROPS = [\n \"display\",\n \"position\",\n \"visibility\",\n \"opacity\",\n \"z-index\",\n \"box-sizing\",\n \"top\",\n \"right\",\n \"bottom\",\n \"left\",\n \"width\",\n \"height\",\n \"margin\",\n \"padding\",\n \"border\",\n \"color\",\n \"background-color\",\n \"font-family\",\n \"font-size\",\n \"font-weight\",\n \"line-height\",\n \"text-align\",\n \"overflow\",\n \"cursor\",\n \"pointer-events\",\n \"flex\",\n \"flex-direction\",\n \"justify-content\",\n \"align-items\",\n \"gap\",\n \"grid-template-columns\",\n \"transform\"\n ];\n function inspectEl(selector, properties) {\n const el = getEl(selector);\n const rect = el.getBoundingClientRect();\n const cs = window.getComputedStyle(el);\n const result = {\n tag: el.tagName.toLowerCase(),\n id: el.id || null,\n classes: Array.from(el.classList),\n attributes: Object.fromEntries(Array.from(el.attributes).map((a) => [a.name, a.value])),\n textContent: el.textContent?.trim().slice(0, 200),\n rect: {\n top: rect.top,\n right: rect.right,\n bottom: rect.bottom,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n x: rect.x,\n y: rect.y\n },\n scroll: { x: window.scrollX, y: window.scrollY },\n inViewport: rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth,\n styles: pickStyles(cs, DEFAULT_STYLE_PROPS)\n };\n result.path = ancestorChain(el);\n const overflow = overflowInfo(el);\n if (overflow) result.overflow = overflow;\n const clipped = clippedByAncestor(el, rect, cs);\n if (clipped) result.clipped = clipped;\n const vis = effectiveVisibility(el, rect, cs);\n result.visible = vis.visible;\n if (!vis.visible) result.hiddenReason = vis.reason;\n const occ = occlusionInfo(el, rect);\n if (occ) {\n result.hittable = occ.hittable;\n if (occ.occludedBy) result.occludedBy = occ.occludedBy;\n }\n const a11y = accessibilityInfo(el);\n if (a11y) result.a11y = a11y;\n const pseudo = pseudoContent(el);\n if (pseudo) result.pseudo = pseudo;\n const state = elementState(el);\n if (state) result.state = state;\n if (properties && properties.length > 0) {\n const requested = {};\n for (const p of properties) requested[p] = cs.getPropertyValue(p);\n result.requested = requested;\n }\n return result;\n }\n function overflowInfo(el) {\n const { scrollWidth, clientWidth, scrollHeight, clientHeight } = el;\n const x = scrollWidth > clientWidth;\n const y = scrollHeight > clientHeight;\n if (!x && !y) return void 0;\n return { x, y, scrollWidth, clientWidth, scrollHeight, clientHeight };\n }\n function refString(el) {\n const tag = el.tagName.toLowerCase();\n const id = el.id ? `#${el.id}` : \"\";\n const all = Array.from(el.classList);\n const classes = all.slice(0, 3).map((c) => `.${c}`).join(\"\");\n const more = all.length > 3 ? \"\\u2026\" : \"\";\n return tag + id + classes + more;\n }\n function chainRef(el) {\n const tag = el.tagName.toLowerCase();\n if (el.id) return `${tag}#${el.id}`;\n const cls = el.classList[0];\n return cls ? `${tag}.${cls}` : tag;\n }\n function ancestorChain(el) {\n const maxDepth = 6;\n const parts = [];\n let node = el;\n while (node) {\n parts.unshift(chainRef(node));\n node = node.parentElement;\n if (parts.length >= maxDepth && node) {\n parts.unshift(\"\\u2026\");\n break;\n }\n }\n return parts.join(\" > \");\n }\n var MAX_ANCESTOR_WALK = 50;\n function establishesFixedContainingBlock(cs) {\n if (cs.transform !== \"none\") return true;\n if (cs.perspective !== \"none\") return true;\n if (cs.filter !== \"none\") return true;\n const backdrop = cs.getPropertyValue(\"backdrop-filter\");\n if (backdrop && backdrop !== \"none\") return true;\n if (/transform|perspective|filter/.test(cs.willChange)) return true;\n if (/paint|layout|strict|content/.test(cs.contain)) return true;\n return false;\n }\n function clippedByAncestor(el, rect, cs) {\n const tol = 1;\n let clipping = cs.position !== \"fixed\";\n let node = el.parentElement;\n for (let depth = 0; node && depth < MAX_ANCESTOR_WALK; node = node.parentElement, depth++) {\n const acs = window.getComputedStyle(node);\n if (!clipping) {\n if (establishesFixedContainingBlock(acs)) clipping = true;\n else continue;\n }\n const clipsX = acs.overflowX !== \"visible\";\n const clipsY = acs.overflowY !== \"visible\";\n if (!clipsX && !clipsY) continue;\n const ar = node.getBoundingClientRect();\n const edges = [];\n if (clipsY && rect.top < ar.top - tol) edges.push(\"top\");\n if (clipsX && rect.right > ar.right + tol) edges.push(\"right\");\n if (clipsY && rect.bottom > ar.bottom + tol) edges.push(\"bottom\");\n if (clipsX && rect.left < ar.left - tol) edges.push(\"left\");\n if (edges.length > 0) return { by: refString(node), edges };\n }\n return void 0;\n }\n function pseudoContent(el) {\n const out = {};\n for (const pseudo of [\"::before\", \"::after\"]) {\n const content = window.getComputedStyle(el, pseudo).content;\n if (content && content !== \"none\" && content !== \"normal\") {\n out[pseudo] = content.length > 200 ? `${content.slice(0, 200)}\\u2026` : content;\n }\n }\n return Object.keys(out).length > 0 ? out : void 0;\n }\n function occlusionInfo(el, rect) {\n if (rect.width === 0 || rect.height === 0) return void 0;\n const left = Math.max(rect.left, 0);\n const top0 = Math.max(rect.top, 0);\n const right = Math.min(rect.right, window.innerWidth - 1);\n const bottom = Math.min(rect.bottom, window.innerHeight - 1);\n if (right < left || bottom < top0) return void 0;\n const cx = (left + right) / 2;\n const cy = (top0 + bottom) / 2;\n const top = document.elementFromPoint(cx, cy);\n if (!top) return void 0;\n if (top === el || el.contains(top)) return { hittable: true };\n return {\n hittable: false,\n occludedBy: {\n tag: top.tagName.toLowerCase(),\n id: top.id || null,\n classes: Array.from(top.classList).slice(0, 3)\n }\n };\n }\n function effectiveVisibility(el, rect, cs) {\n if (cs.display === \"none\") return { visible: false, reason: \"display:none\" };\n let node = el.parentElement;\n for (let depth = 0; node && depth < MAX_ANCESTOR_WALK; node = node.parentElement, depth++) {\n const acs = window.getComputedStyle(node);\n if (acs.display === \"none\")\n return { visible: false, reason: `ancestor ${refString(node)} display:none` };\n if (parseFloat(acs.opacity) === 0)\n return { visible: false, reason: `ancestor ${refString(node)} opacity:0` };\n if (node.getAttribute(\"aria-hidden\") === \"true\")\n return { visible: false, reason: `ancestor ${refString(node)} aria-hidden` };\n }\n if (cs.visibility === \"hidden\" || cs.visibility === \"collapse\")\n return { visible: false, reason: `visibility:${cs.visibility}` };\n if (parseFloat(cs.opacity) === 0) return { visible: false, reason: \"opacity:0\" };\n if (el.getAttribute(\"aria-hidden\") === \"true\") return { visible: false, reason: \"aria-hidden\" };\n if (rect.width === 0 || rect.height === 0) return { visible: false, reason: \"zero-size\" };\n return { visible: true };\n }\n function pickStyles(cs, props) {\n const out = {};\n for (const p of props) {\n const v = cs.getPropertyValue(p);\n if (v) out[p] = v;\n }\n return out;\n }\n function hasNamingAttribute(el) {\n return !!el.getAttribute(\"aria-label\")?.trim() || !!el.getAttribute(\"aria-labelledby\")?.trim() || !!el.getAttribute(\"title\")?.trim();\n }\n function implicitRole(el) {\n const tag = el.tagName.toLowerCase();\n switch (tag) {\n case \"a\":\n case \"area\":\n return el.hasAttribute(\"href\") ? \"link\" : null;\n case \"button\":\n return \"button\";\n case \"input\": {\n const t = (el.getAttribute(\"type\") || \"text\").toLowerCase();\n if (el.hasAttribute(\"list\") && [\"text\", \"search\", \"email\", \"tel\", \"url\"].includes(t)) {\n return \"combobox\";\n }\n const map = {\n checkbox: \"checkbox\",\n radio: \"radio\",\n range: \"slider\",\n number: \"spinbutton\",\n button: \"button\",\n submit: \"button\",\n reset: \"button\",\n image: \"button\",\n search: \"searchbox\",\n email: \"textbox\",\n tel: \"textbox\",\n url: \"textbox\",\n text: \"textbox\"\n };\n return map[t] ?? null;\n }\n case \"select\": {\n const size = Number(el.getAttribute(\"size\") || \"0\");\n return el.hasAttribute(\"multiple\") || size > 1 ? \"listbox\" : \"combobox\";\n }\n case \"textarea\":\n return \"textbox\";\n case \"img\":\n return el.getAttribute(\"alt\") === \"\" ? \"presentation\" : \"img\";\n case \"nav\":\n return \"navigation\";\n case \"main\":\n return \"main\";\n case \"header\":\n return el.closest(\"article, aside, main, nav, section\") ? null : \"banner\";\n case \"footer\":\n return el.closest(\"article, aside, main, nav, section\") ? null : \"contentinfo\";\n case \"aside\": {\n const scoped = el.parentElement?.closest(\"article, aside, main, nav, section\");\n return !scoped || hasNamingAttribute(el) ? \"complementary\" : null;\n }\n case \"section\":\n return hasNamingAttribute(el) ? \"region\" : null;\n case \"article\":\n return \"article\";\n case \"dialog\":\n return \"dialog\";\n case \"form\":\n return hasNamingAttribute(el) ? \"form\" : null;\n case \"table\":\n return \"table\";\n case \"ul\":\n case \"ol\":\n return \"list\";\n case \"li\":\n return \"listitem\";\n case \"h1\":\n case \"h2\":\n case \"h3\":\n case \"h4\":\n case \"h5\":\n case \"h6\":\n return \"heading\";\n default:\n return null;\n }\n }\n var NAME_FROM_CONTENT = /* @__PURE__ */ new Set([\n \"button\",\n \"checkbox\",\n \"radio\",\n \"switch\",\n \"link\",\n \"menuitem\",\n \"menuitemcheckbox\",\n \"menuitemradio\",\n \"option\",\n \"tab\",\n \"treeitem\",\n \"heading\",\n \"cell\",\n \"gridcell\",\n \"columnheader\",\n \"rowheader\",\n \"row\",\n \"tooltip\"\n ]);\n function accessibleName(el, role) {\n const labelledby = el.getAttribute(\"aria-labelledby\");\n if (labelledby) {\n const txt = labelledby.split(/\\s+/).map((id) => document.getElementById(id)?.textContent?.trim()).filter(Boolean).join(\" \");\n if (txt) return txt.slice(0, 200);\n }\n const label = el.getAttribute(\"aria-label\")?.trim();\n if (label) return label.slice(0, 200);\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {\n if (el.id) {\n const forLabel = document.querySelector(`label[for=\"${CSS.escape(el.id)}\"]`);\n const txt = forLabel?.textContent?.trim();\n if (txt) return txt.slice(0, 200);\n }\n const wrapping = el.closest(\"label\")?.textContent?.trim();\n if (wrapping) return wrapping.slice(0, 200);\n if (el instanceof HTMLInputElement && el.placeholder) return el.placeholder.slice(0, 200);\n }\n if (el instanceof HTMLImageElement && el.alt) return el.alt.slice(0, 200);\n const title = el.getAttribute(\"title\")?.trim();\n if (title) return title.slice(0, 200);\n if (role && NAME_FROM_CONTENT.has(role.trim().split(/\\s+/)[0])) {\n const text = el.textContent?.trim();\n if (text) return text.slice(0, 200);\n }\n return void 0;\n }\n function accessibilityInfo(el) {\n const a11y = {};\n const role = el.getAttribute(\"role\") || implicitRole(el);\n if (role) a11y.role = role;\n const name = accessibleName(el, role);\n if (name) a11y.name = name;\n const states = {};\n for (const attr of [\n \"aria-expanded\",\n \"aria-checked\",\n \"aria-selected\",\n \"aria-pressed\",\n \"aria-current\",\n \"aria-disabled\"\n ]) {\n const v = el.getAttribute(attr);\n if (v !== null) states[attr.slice(5)] = v === \"true\" ? true : v === \"false\" ? false : v;\n }\n if (el.getAttribute(\"aria-hidden\") === \"true\") states.hidden = true;\n if (\"disabled\" in el && el.disabled) states.disabled = true;\n const tabindex = el.getAttribute(\"tabindex\");\n if (tabindex !== null) {\n const n = Number(tabindex);\n states.tabindex = Number.isNaN(n) ? tabindex : n;\n }\n if (Object.keys(states).length > 0) a11y.states = states;\n return Object.keys(a11y).length > 0 ? a11y : void 0;\n }\n function elementState(el) {\n const s = {};\n if (el instanceof HTMLInputElement) {\n s.value = capValue(el.value);\n s.type = el.type;\n s.checked = el.checked;\n s.disabled = el.disabled;\n s.readOnly = el.readOnly;\n s.required = el.required;\n if (el.placeholder) s.placeholder = el.placeholder;\n if (el.validationMessage) s.validationMessage = el.validationMessage;\n } else if (el instanceof HTMLTextAreaElement) {\n s.value = capValue(el.value);\n s.disabled = el.disabled;\n s.readOnly = el.readOnly;\n } else if (el instanceof HTMLSelectElement) {\n s.value = el.value;\n s.selectedIndex = el.selectedIndex;\n s.disabled = el.disabled;\n } else if (el instanceof HTMLButtonElement) {\n s.type = el.type;\n s.disabled = el.disabled;\n } else if (el instanceof HTMLAnchorElement) {\n s.href = el.href;\n }\n if (el instanceof HTMLElement && Object.keys(el.dataset).length > 0) {\n s.dataset = { ...el.dataset };\n }\n return Object.keys(s).length > 0 ? s : void 0;\n }\n function capValue(v) {\n return v.length > 1e3 ? `${v.slice(0, 1e3)}\\u2026[truncated]` : v;\n }\n function queryDom(selector) {\n return Array.from(document.querySelectorAll(selector)).map((el) => ({\n tag: el.tagName.toLowerCase(),\n id: el.id || null,\n classes: Array.from(el.classList),\n textContent: el.textContent?.trim().slice(0, 100)\n }));\n }\n var NAMED_KEYS = {\n Enter: { code: \"Enter\", keyCode: 13 },\n Tab: { code: \"Tab\", keyCode: 9 },\n Escape: { code: \"Escape\", keyCode: 27 },\n Backspace: { code: \"Backspace\", keyCode: 8 },\n Delete: { code: \"Delete\", keyCode: 46 },\n ArrowUp: { code: \"ArrowUp\", keyCode: 38 },\n ArrowDown: { code: \"ArrowDown\", keyCode: 40 },\n ArrowLeft: { code: \"ArrowLeft\", keyCode: 37 },\n ArrowRight: { code: \"ArrowRight\", keyCode: 39 },\n \" \": { code: \"Space\", keyCode: 32 }\n };\n function pressKey(selector, key) {\n const el = getEl(selector);\n el.focus?.();\n const named = NAMED_KEYS[key];\n const keyCode = named ? named.keyCode : key.length === 1 ? key.toUpperCase().charCodeAt(0) : 0;\n const code = named ? named.code : key.length === 1 ? `Key${key.toUpperCase()}` : key;\n const init = {\n key,\n code,\n keyCode,\n which: keyCode,\n bubbles: true,\n cancelable: true\n };\n el.dispatchEvent(new KeyboardEvent(\"keydown\", init));\n if (key.length === 1) el.dispatchEvent(new KeyboardEvent(\"keypress\", init));\n el.dispatchEvent(new KeyboardEvent(\"keyup\", init));\n return { tag: el.tagName.toLowerCase(), key };\n }\n var MAX_HTML_CHARS = 5e4;\n function getHtml(selector) {\n const el = getEl(selector);\n const html = el.outerHTML;\n const truncated = html.length > MAX_HTML_CHARS;\n return {\n tag: el.tagName.toLowerCase(),\n html: truncated ? `${html.slice(0, MAX_HTML_CHARS)}\\u2026[truncated]` : html,\n truncated\n };\n }\n function getPageInfo() {\n return {\n url: window.location.href,\n title: document.title,\n readyState: document.readyState,\n viewport: { width: window.innerWidth, height: window.innerHeight },\n scroll: { x: window.scrollX, y: window.scrollY },\n userAgent: navigator.userAgent\n };\n }\n var PREVIEW_NOTE = \"Live preview only \\u2014 applied to the running DOM, not saved to source, and reset on reload/HMR. Once the user is happy, edit the actual source to make it permanent.\";\n var CLOBBER_WARNING = \"This may be reverted on the next framework render (React/Vue/etc. re-render this element from component state). If it snaps back, change it in the source instead of here.\";\n var FRAMEWORK_OWNED_ATTRS = /* @__PURE__ */ new Set([\n \"class\",\n \"style\",\n \"value\",\n \"checked\",\n \"disabled\",\n \"selected\"\n ]);\n var overrides = [];\n function setStyle(selector, properties) {\n const el = getEl(selector);\n if (!el.style) throw new Error(`element \"${selector}\" has no style (not an HTMLElement)`);\n const applied = {};\n for (const [prop, value] of Object.entries(properties)) {\n const prevValue = el.style.getPropertyValue(prop);\n const prevPriority = el.style.getPropertyPriority(prop);\n overrides.push(() => {\n if (prevValue) el.style.setProperty(prop, prevValue, prevPriority);\n else el.style.removeProperty(prop);\n });\n el.style.setProperty(prop, value);\n applied[prop] = el.style.getPropertyValue(prop);\n }\n return { tag: el.tagName.toLowerCase(), applied, note: PREVIEW_NOTE };\n }\n function setAttribute(selector, name, value) {\n const el = getEl(selector);\n const had = el.hasAttribute(name);\n const prev = had ? el.getAttribute(name) : null;\n overrides.push(() => {\n if (had) el.setAttribute(name, prev ?? \"\");\n else el.removeAttribute(name);\n });\n if (value === null) el.removeAttribute(name);\n else el.setAttribute(name, value);\n const result = {\n tag: el.tagName.toLowerCase(),\n name,\n value: value === null ? null : el.getAttribute(name),\n removed: value === null,\n note: PREVIEW_NOTE\n };\n if (FRAMEWORK_OWNED_ATTRS.has(name.toLowerCase())) result.frameworkWarning = CLOBBER_WARNING;\n return result;\n }\n function setText(selector, text) {\n const el = getEl(selector);\n const prev = el.textContent;\n overrides.push(() => {\n el.textContent = prev;\n });\n el.textContent = text;\n return {\n tag: el.tagName.toLowerCase(),\n text,\n note: PREVIEW_NOTE,\n frameworkWarning: CLOBBER_WARNING\n };\n }\n function resetOverrides() {\n const count = overrides.length;\n while (overrides.length) overrides.pop()?.();\n return {\n reverted: count,\n note: \"All bridge-applied DOM changes since connect have been undone (best effort \\u2014 elements re-created by the framework since may not roll back).\"\n };\n }\n function isCommand(v) {\n return typeof v === \"object\" && v !== null && v.type === \"command\" && typeof v.id === \"string\";\n }\n\n // src/interceptors/console.ts\n var STD_LEVELS = [\"log\", \"warn\", \"error\", \"info\", \"debug\"];\n var MAX_LOGS = 1e3;\n var MAX_ARG_CHARS = 1e4;\n var ConsoleInterceptor = class {\n constructor() {\n this.originals = /* @__PURE__ */ new Map();\n this.logs = [];\n this.counts = /* @__PURE__ */ new Map();\n this.timers = /* @__PURE__ */ new Map();\n }\n install() {\n const c = console;\n const record = (msg) => {\n this.logs.push(msg);\n if (this.logs.length > MAX_LOGS) this.logs.shift();\n };\n const rich = (method, level, args, extras = {}) => {\n record({\n type: \"console\",\n ts: Date.now(),\n level,\n method,\n args: args.map(serialize),\n ...extras\n });\n };\n const wrap = (name, fn) => {\n const orig = c[name];\n if (typeof orig !== \"function\") return;\n this.originals.set(name, orig.bind(console));\n c[name] = fn;\n };\n for (const level of STD_LEVELS) {\n const orig = c[level].bind(console);\n this.originals.set(level, orig);\n c[level] = (...args) => {\n orig(...args);\n record({ type: \"console\", ts: Date.now(), level, args: args.map(serialize) });\n };\n }\n wrap(\"dir\", (obj, options) => {\n this.originals.get(\"dir\")?.(obj, options);\n rich(\"dir\", \"log\", options === void 0 ? [obj] : [obj, options]);\n });\n wrap(\"table\", (data, columns) => {\n this.originals.get(\"table\")?.(data, columns);\n rich(\"table\", \"log\", columns === void 0 ? [data] : [data, columns]);\n });\n wrap(\"trace\", (...args) => {\n this.originals.get(\"trace\")?.(...args);\n rich(\"trace\", \"log\", args, { stack: captureStack() });\n });\n wrap(\"assert\", (condition, ...args) => {\n this.originals.get(\"assert\")?.(condition, ...args);\n if (condition) return;\n rich(\"assert\", \"error\", args.length ? args : [\"Assertion failed\"], { stack: captureStack() });\n });\n wrap(\"count\", (label) => {\n this.originals.get(\"count\")?.(label);\n const key = label == null ? \"default\" : String(label);\n const n = (this.counts.get(key) ?? 0) + 1;\n this.counts.set(key, n);\n rich(\"count\", \"log\", [`${key}: ${n}`]);\n });\n wrap(\"countReset\", (label) => {\n this.originals.get(\"countReset\")?.(label);\n const key = label == null ? \"default\" : String(label);\n this.counts.set(key, 0);\n rich(\"countReset\", \"log\", [`${key}: 0`]);\n });\n wrap(\"time\", (label) => {\n this.originals.get(\"time\")?.(label);\n const key = label == null ? \"default\" : String(label);\n this.timers.set(key, performance.now());\n });\n wrap(\"timeEnd\", (label) => {\n this.originals.get(\"timeEnd\")?.(label);\n const key = label == null ? \"default\" : String(label);\n const start = this.timers.get(key);\n if (start === void 0) {\n rich(\"timeEnd\", \"warn\", [`Timer \"${key}\" does not exist`]);\n return;\n }\n this.timers.delete(key);\n rich(\"timeEnd\", \"log\", [`${key}: ${(performance.now() - start).toFixed(3)}ms`]);\n });\n wrap(\"timeLog\", (label, ...args) => {\n this.originals.get(\"timeLog\")?.(label, ...args);\n const key = label == null ? \"default\" : String(label);\n const start = this.timers.get(key);\n if (start === void 0) {\n rich(\"timeLog\", \"warn\", [`Timer \"${key}\" does not exist`, ...args]);\n return;\n }\n rich(\"timeLog\", \"log\", [`${key}: ${(performance.now() - start).toFixed(3)}ms`, ...args]);\n });\n wrap(\"group\", (...args) => {\n this.originals.get(\"group\")?.(...args);\n rich(\"group\", \"log\", args);\n });\n wrap(\"groupCollapsed\", (...args) => {\n this.originals.get(\"groupCollapsed\")?.(...args);\n rich(\"groupCollapsed\", \"log\", args);\n });\n wrap(\"groupEnd\", () => {\n this.originals.get(\"groupEnd\")?.();\n rich(\"groupEnd\", \"log\", []);\n });\n wrap(\"clear\", () => {\n this.originals.get(\"clear\")?.();\n rich(\"clear\", \"log\", []);\n });\n this.onError = (e) => {\n record({\n type: \"console\",\n ts: Date.now(),\n level: \"error\",\n method: \"uncaught\",\n args: [serialize(e.message || \"Uncaught error\")],\n stack: e.error instanceof Error ? e.error.stack : void 0\n });\n };\n this.onRejection = (e) => {\n const reason = e.reason;\n record({\n type: \"console\",\n ts: Date.now(),\n level: \"error\",\n method: \"unhandledrejection\",\n args: [serialize(reason instanceof Error ? reason.message : reason)],\n stack: reason instanceof Error ? reason.stack : void 0\n });\n };\n window.addEventListener(\"error\", this.onError);\n window.addEventListener(\"unhandledrejection\", this.onRejection);\n }\n uninstall() {\n const c = console;\n for (const [name, original] of this.originals) {\n c[name] = original;\n }\n this.originals.clear();\n if (this.onError) window.removeEventListener(\"error\", this.onError);\n if (this.onRejection) window.removeEventListener(\"unhandledrejection\", this.onRejection);\n }\n getLogs(opts = {}) {\n let result = this.logs;\n if (opts.levels && opts.levels.length > 0) {\n const wanted = new Set(opts.levels);\n result = result.filter((m) => wanted.has(m.level));\n }\n if (opts.match) {\n const needle = opts.match.toLowerCase();\n result = result.filter((m) => JSON.stringify(m.args).toLowerCase().includes(needle));\n }\n if (opts.since !== void 0) {\n result = result.filter((m) => m.ts >= opts.since);\n }\n if (opts.limit !== void 0) {\n result = result.slice(-opts.limit);\n } else if (result === this.logs) {\n result = [...this.logs];\n }\n return result;\n }\n };\n function captureStack() {\n const raw = new Error().stack ?? \"\";\n const lines = raw.split(\"\\n\");\n return lines.length > 2 ? lines.slice(2).join(\"\\n\") : raw;\n }\n function serialize(v) {\n if (v === null || v === void 0) return String(v);\n if (typeof v === \"string\") return cap(v);\n if (typeof v !== \"object\") return v;\n if (v instanceof Error) return { name: v.name, message: v.message, stack: v.stack };\n try {\n const json = JSON.stringify(v);\n return json.length > MAX_ARG_CHARS ? cap(json) : JSON.parse(json);\n } catch {\n return String(v);\n }\n }\n function cap(text) {\n return text.length > MAX_ARG_CHARS ? `${text.slice(0, MAX_ARG_CHARS)}\\u2026[truncated]` : text;\n }\n\n // src/interceptors/network.ts\n var MAX_REQUESTS = 1e3;\n var MAX_BODY_CHARS = 1e4;\n var MAX_BODY_BYTES = 64 * 1024;\n var NetworkInterceptor = class {\n constructor() {\n this.requests = [];\n this.origFetch = null;\n this.OrigXHR = null;\n }\n // Captured into a local buffer only — never streamed. An agent pulls requests\n // on demand via get_network_requests; pushing every request/response over the\n // WebSocket would be wasted traffic since nothing subscribes to it.\n install() {\n this.interceptFetch();\n this.interceptXHR();\n }\n interceptFetch() {\n this.origFetch = window.fetch.bind(window);\n const orig = this.origFetch;\n const requests = this.requests;\n window.fetch = async (input, init) => {\n const url = input instanceof Request ? input.url : String(input);\n const method = resolveMethod(input, init);\n const requestId = uid();\n const startTs = Date.now();\n const requestHeaders = mergeRequestHeaders(input, init);\n const requestBody = serializeRequestBody(init?.body);\n const pending = {\n type: \"network\",\n ts: startTs,\n requestId,\n method,\n url,\n requestHeaders,\n requestBody\n };\n pushRequest(requests, pending);\n try {\n const res = await orig(input, init);\n Object.assign(pending, {\n ts: Date.now(),\n status: res.status,\n duration: Date.now() - startTs,\n responseHeaders: headersToObject(res.headers)\n });\n captureResponseBody(res.clone(), pending);\n return res;\n } catch (e) {\n Object.assign(pending, {\n ts: Date.now(),\n duration: Date.now() - startTs,\n error: e instanceof Error ? e.message : String(e)\n });\n throw e;\n }\n };\n }\n interceptXHR() {\n this.OrigXHR = window.XMLHttpRequest;\n const OrigXHR = this.OrigXHR;\n const requests = this.requests;\n function FeedthroughXHR() {\n const xhr = new OrigXHR();\n let method = \"GET\";\n let url = \"\";\n const requestHeaders = {};\n const origOpen = xhr.open.bind(xhr);\n xhr.open = (m, u, async, user, password) => {\n method = m.toUpperCase();\n url = String(u);\n origOpen(m, String(u), async ?? true, user, password);\n };\n const origSetHeader = xhr.setRequestHeader.bind(xhr);\n xhr.setRequestHeader = (name, value) => {\n requestHeaders[name] = String(value);\n origSetHeader(name, value);\n };\n const origSend = xhr.send.bind(xhr);\n xhr.send = (body) => {\n const requestId = uid();\n const startTs = Date.now();\n const requestBody = serializeRequestBody(body ?? void 0);\n const pending = {\n type: \"network\",\n ts: startTs,\n requestId,\n method,\n url,\n requestHeaders: Object.keys(requestHeaders).length ? { ...requestHeaders } : void 0,\n requestBody\n };\n pushRequest(requests, pending);\n xhr.addEventListener(\"loadend\", () => {\n let responseBody;\n try {\n if (xhr.responseType === \"\" || xhr.responseType === \"text\") {\n responseBody = capText(xhr.responseText);\n } else {\n responseBody = `[XHR responseType=${xhr.responseType}]`;\n }\n } catch {\n }\n Object.assign(pending, {\n ts: Date.now(),\n status: xhr.status,\n duration: Date.now() - startTs,\n responseBody,\n responseHeaders: parseRawHeaders(xhr.getAllResponseHeaders())\n });\n });\n origSend(body);\n };\n return xhr;\n }\n FeedthroughXHR.prototype = OrigXHR.prototype;\n window.XMLHttpRequest = FeedthroughXHR;\n }\n uninstall() {\n if (this.origFetch) window.fetch = this.origFetch;\n if (this.OrigXHR) window.XMLHttpRequest = this.OrigXHR;\n }\n getRequests(filter, since) {\n let all = [...this.requests];\n if (since !== void 0) all = all.filter((r) => r.ts >= since);\n if (!filter) return all;\n const lower = filter.toLowerCase();\n return all.filter((r) => r.url.toLowerCase().includes(lower) || r.method.toLowerCase() === lower);\n }\n };\n function resolveMethod(input, init) {\n return (init?.method ?? (input instanceof Request ? input.method : null) ?? \"GET\").toUpperCase();\n }\n function pushRequest(requests, msg) {\n requests.push(msg);\n if (requests.length > MAX_REQUESTS) requests.shift();\n }\n function mergeRequestHeaders(input, init) {\n const source = init?.headers ?? (input instanceof Request ? input.headers : void 0);\n if (!source) return void 0;\n const out = {};\n if (source instanceof Headers) {\n source.forEach((v, k) => {\n out[k] = v;\n });\n } else if (Array.isArray(source)) {\n for (const [k, v] of source) out[k] = String(v);\n } else {\n for (const [k, v] of Object.entries(source)) out[k] = String(v);\n }\n return Object.keys(out).length ? out : void 0;\n }\n function headersToObject(headers) {\n const out = {};\n headers.forEach((v, k) => {\n out[k] = v;\n });\n return out;\n }\n function parseRawHeaders(raw) {\n if (!raw) return void 0;\n const out = {};\n for (const line of raw.split(\"\\r\\n\")) {\n const idx = line.indexOf(\":\");\n if (idx <= 0) continue;\n const k = line.slice(0, idx).trim();\n const v = line.slice(idx + 1).trim();\n if (k) out[k] = v;\n }\n return Object.keys(out).length ? out : void 0;\n }\n function serializeRequestBody(body) {\n if (body == null) return void 0;\n if (typeof body === \"string\") return capText(body);\n if (body instanceof URLSearchParams) return capText(body.toString());\n if (body instanceof FormData) {\n const obj = {};\n body.forEach((v, k) => {\n obj[k] = v instanceof File ? `[File: ${v.name}, ${v.size} bytes]` : v;\n });\n try {\n return capText(JSON.stringify(obj));\n } catch {\n return \"[FormData]\";\n }\n }\n if (body instanceof Blob) return `[Blob: ${body.size} bytes, ${body.type || \"unknown\"}]`;\n if (body instanceof ArrayBuffer) return `[ArrayBuffer: ${body.byteLength} bytes]`;\n if (ArrayBuffer.isView(body)) return `[${body.constructor.name}: ${body.byteLength} bytes]`;\n if (body instanceof ReadableStream) return \"[ReadableStream]\";\n try {\n return capText(JSON.stringify(body));\n } catch {\n return String(body);\n }\n }\n function capText(text) {\n if (text.length <= MAX_BODY_CHARS) return text;\n return `${text.slice(0, MAX_BODY_CHARS)}\\u2026[truncated, ${text.length - MAX_BODY_CHARS} more chars]`;\n }\n function captureResponseBody(res, pending) {\n const ct = (res.headers.get(\"content-type\") ?? \"\").toLowerCase();\n if (isSkippable(ct)) {\n const len = res.headers.get(\"content-length\");\n pending.responseBody = `[${describe(ct)}${len ? `, ${len} bytes` : \"\"}, ${ct || \"no content-type\"}]`;\n return;\n }\n readBounded(res).then((body) => {\n pending.responseBody = body;\n }).catch(() => {\n });\n }\n async function readBounded(res) {\n if (!res.body) return capText(await res.text());\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let text = \"\";\n let bytes = 0;\n let truncated = false;\n try {\n for (; ; ) {\n const { done, value } = await reader.read();\n if (done) break;\n bytes += value.byteLength;\n text += decoder.decode(value, { stream: true });\n if (bytes >= MAX_BODY_BYTES || text.length >= MAX_BODY_CHARS) {\n truncated = true;\n break;\n }\n }\n } finally {\n reader.cancel().catch(() => {\n });\n }\n if (!truncated) return capText(text);\n return `${text.slice(0, MAX_BODY_CHARS)}\\u2026[truncated]`;\n }\n function isSkippable(ct) {\n return /^(image|video|audio|font)\\//.test(ct) || ct.startsWith(\"application/octet-stream\") || ct.startsWith(\"application/pdf\") || ct.startsWith(\"application/zip\") || ct.startsWith(\"text/event-stream\") || // SSE — never-ending stream\n ct.startsWith(\"application/x-ndjson\");\n }\n function describe(ct) {\n if (ct.startsWith(\"text/event-stream\")) return \"event stream\";\n if (ct.startsWith(\"application/x-ndjson\")) return \"ndjson stream\";\n return \"binary\";\n }\n var counter = 0;\n function uid() {\n return `${Date.now()}-${++counter}`;\n }\n\n // src/transport.ts\n var MAX_QUEUE = 1e3;\n var Transport = class {\n constructor(url, onMessage, onStatus, reconnectDelay) {\n this.url = url;\n this.onMessage = onMessage;\n this.onStatus = onStatus;\n this.reconnectDelay = reconnectDelay;\n this.ws = null;\n this.queue = [];\n this.reconnectTimer = null;\n this.destroyed = false;\n }\n connect() {\n if (this.destroyed) return;\n const ws = new WebSocket(this.url);\n this.ws = ws;\n const isCurrent = () => this.ws === ws;\n ws.onopen = () => {\n if (!isCurrent()) return;\n this.onStatus(true);\n for (const msg of this.queue) ws.send(msg);\n this.queue = [];\n };\n ws.onmessage = (event) => {\n if (!isCurrent()) return;\n try {\n this.onMessage(JSON.parse(event.data));\n } catch {\n }\n };\n ws.onclose = () => {\n if (!isCurrent()) return;\n this.onStatus(false);\n if (!this.destroyed) {\n this.reconnectTimer = setTimeout(() => this.connect(), this.reconnectDelay);\n }\n };\n ws.onerror = () => {\n };\n }\n send(msg) {\n const serialized = JSON.stringify(msg);\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(serialized);\n } else {\n this.queue.push(serialized);\n if (this.queue.length > MAX_QUEUE) this.queue.shift();\n }\n }\n destroy() {\n this.destroyed = true;\n if (this.reconnectTimer !== null) clearTimeout(this.reconnectTimer);\n this.ws?.close();\n }\n };\n\n // src/bridge.ts\n var DEFAULT_SERVER_URL = \"ws://localhost:8765\";\n var FeedthroughBridge = class {\n constructor(options = {}) {\n const url = options.serverUrl ?? DEFAULT_SERVER_URL;\n const reconnectDelay = options.reconnectDelay ?? 2e3;\n this.consoleInterceptor = new ConsoleInterceptor();\n this.networkInterceptor = new NetworkInterceptor();\n this.transport = new Transport(\n url,\n (msg) => this.commandHandler.handle(msg),\n (connected) => {\n if (connected) this.transport.send({ type: \"hello\", url: window.location.href });\n },\n reconnectDelay\n );\n this.commandHandler = new CommandHandler(\n this.transport,\n this.consoleInterceptor,\n this.networkInterceptor\n );\n }\n connect() {\n this.consoleInterceptor.install();\n this.networkInterceptor.install();\n this.transport.connect();\n }\n destroy() {\n this.transport.destroy();\n this.consoleInterceptor.uninstall();\n this.networkInterceptor.uninstall();\n }\n };\n\n // src/browser.ts\n window.__feedthrough = new FeedthroughBridge(window.__feedthroughOptions ?? {});\n window.__feedthrough.connect();\n})();\n";
//# sourceMappingURL=bundle.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../src/generated/bundle.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,MAAM,CAAC,MAAM,YAAY,GAAG,q+6BAAq+6B,CAAC"}
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../src/generated/bundle.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,MAAM,CAAC,MAAM,YAAY,GAAG,03xCAA03xC,CAAC"}

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,aAAkB,+OAS3D;AAED,eAAO,MAAM,IAAI,6OAAqB,CAAC"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,aAAkB,+OAS3D;AAED,eAAO,MAAM,IAAI,6OAAqB,CAAC"}

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGrD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAyB,EAAE;IAC1D,OAAO,QAAQ,CAAC,MAAM,CAAC;QACrB,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE;YAC5B,MAAM,IAAI,CAAC,aAAa,CAAC;gBACvB,OAAO,EAAE,iCAAiC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,YAAY,EAAE;aACtF,CAAC,CAAC;YACH,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,gBAAgB,EAAE,CAAC"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAyB,EAAE;IAC1D,OAAO,QAAQ,CAAC,MAAM,CAAC;QACrB,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE;YAC5B,MAAM,IAAI,CAAC,aAAa,CAAC;gBACvB,OAAO,EAAE,iCAAiC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,YAAY,EAAE;aACtF,CAAC,CAAC;YACH,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,gBAAgB,EAAE,CAAC"}
{
"name": "@feedthrough/playwright",
"version": "0.1.0",
"version": "0.3.0",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/feedthrough/feedthrough.git",
"directory": "packages/playwright"
},
"type": "module",

@@ -19,3 +24,3 @@ "main": "./dist/index.js",

"dependencies": {
"@feedthrough/core": "0.1.0"
"@feedthrough/core": "0.3.0"
},

@@ -22,0 +27,0 @@ "devDependencies": {