Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Fetch any URL via Chrome CDP and convert the rendered page to clean markdown with YouTube transcript support.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/lib/browser/page-snapshot.ts
1import type { BrowserSession } from "./session";23export interface CapturedPageSnapshot {4html: string;5finalUrl: string;6}78export const CAPTURE_NORMALIZED_PAGE_SCRIPT = String.raw`9(() => {10const baseUrl = document.baseURI || location.href;11const htmlClone = document.documentElement.cloneNode(true);1213function materializeShadowDom(sourceRoot, cloneRoot) {14const sourceElements = Array.from(sourceRoot.querySelectorAll("*"));15const cloneElements = Array.from(cloneRoot.querySelectorAll("*"));1617for (let index = sourceElements.length - 1; index >= 0; index -= 1) {18const sourceElement = sourceElements[index];19const cloneElement = cloneElements[index];20const shadowRoot = sourceElement && sourceElement.shadowRoot;21if (!shadowRoot || !cloneElement || !shadowRoot.innerHTML) {22continue;23}2425if (cloneElement.tagName && cloneElement.tagName.includes("-")) {26const wrapper = document.createElement("div");27wrapper.setAttribute("data-shadow-host", cloneElement.tagName.toLowerCase());28wrapper.innerHTML = shadowRoot.innerHTML;29cloneElement.replaceWith(wrapper);30} else {31cloneElement.innerHTML = shadowRoot.innerHTML;32}33}34}3536function toAbsolute(url) {37if (!url) return url;38try {39return new URL(url, baseUrl).href;40} catch {41return url;42}43}4445function absolutizeAttribute(root, selector, attribute) {46root.querySelectorAll(selector).forEach((element) => {47const value = element.getAttribute(attribute);48if (!value) return;49const absolute = toAbsolute(value);50if (absolute) {51element.setAttribute(attribute, absolute);52}53});54}5556function absolutizeSrcset(root, selector) {57root.querySelectorAll(selector).forEach((element) => {58const srcset = element.getAttribute("srcset");59if (!srcset) return;60element.setAttribute(61"srcset",62srcset63.split(",")64.map((part) => {65const trimmed = part.trim();66if (!trimmed) return "";67const [url, ...descriptor] = trimmed.split(/\s+/);68const absolute = toAbsolute(url);69return descriptor.length > 0 ? absolute + " " + descriptor.join(" ") : absolute;70})71.filter(Boolean)72.join(", "),73);74});75}7677materializeShadowDom(document.documentElement, htmlClone);7879htmlClone80.querySelectorAll("img[data-src], video[data-src], audio[data-src], source[data-src]")81.forEach((element) => {82const dataSource = element.getAttribute("data-src");83const current = element.getAttribute("src");84if (dataSource && (!current || current === "" || current.startsWith("data:"))) {85element.setAttribute("src", dataSource);86}87});8889absolutizeAttribute(htmlClone, "a[href]", "href");90absolutizeAttribute(htmlClone, "img[src], video[src], audio[src], source[src], iframe[src]", "src");91absolutizeAttribute(htmlClone, "video[poster]", "poster");92absolutizeSrcset(htmlClone, "img[srcset], source[srcset]");9394return {95html: "<!doctype html>\n" + htmlClone.outerHTML,96finalUrl: location.href,97};98})()99`;100101export async function captureNormalizedPageSnapshot(102browser: BrowserSession,103): Promise<CapturedPageSnapshot> {104return browser.evaluate<CapturedPageSnapshot>(CAPTURE_NORMALIZED_PAGE_SCRIPT);105}106