@mcp-b/smart-dom-reader 5.0.2 → 5.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"bundle-string.mjs","names":[],"sources":["../src/bundle-string.ts"],"sourcesContent":["/**\n * Auto-generated bundle module for smart-dom-reader\n * DO NOT EDIT - Generated by generate-bundle-module.mjs\n *\n * This module exports the bundled smart-dom-reader library as a string\n * that can be injected into web pages for stateless DOM extraction.\n */\n\nexport const SMART_DOM_READER_BUNDLE =\n 'var SmartDOMReaderBundle = (function(exports) {\\n\\tObject.defineProperty(exports, Symbol.toStringTag, { value: \"Module\" });\\n\\t//#region src/content-detection.ts\\n\\tvar ContentDetection = class ContentDetection {\\n\\t\\t/**\\n\\t\\t* Find the main content area of a page\\n\\t\\t* Inspired by dom-to-semantic-markdown\\'s approach\\n\\t\\t*/\\n\\t\\tstatic findMainContent(doc) {\\n\\t\\t\\tconst mainElement = doc.querySelector(\"main, [role=\\\\\"main\\\\\"]\");\\n\\t\\t\\tif (mainElement) return mainElement;\\n\\t\\t\\tif (!doc.body) return doc.documentElement;\\n\\t\\t\\treturn ContentDetection.detectMainContent(doc.body);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Detect main content using scoring algorithm\\n\\t\\t*/\\n\\t\\tstatic detectMainContent(rootElement) {\\n\\t\\t\\tconst candidates = [];\\n\\t\\t\\tContentDetection.collectCandidates(rootElement, candidates, 15);\\n\\t\\t\\tif (candidates.length === 0) return rootElement;\\n\\t\\t\\tcandidates.sort((a, b) => ContentDetection.calculateContentScore(b) - ContentDetection.calculateContentScore(a));\\n\\t\\t\\tlet bestCandidate = candidates[0];\\n\\t\\t\\tfor (let i = 1; i < candidates.length; i++) {\\n\\t\\t\\t\\tconst candidate = candidates[i];\\n\\t\\t\\t\\tif (!candidates.some((other, j) => j !== i && other.contains(candidate)) && ContentDetection.calculateContentScore(candidate) > ContentDetection.calculateContentScore(bestCandidate)) bestCandidate = candidate;\\n\\t\\t\\t}\\n\\t\\t\\treturn bestCandidate;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Collect content candidates\\n\\t\\t*/\\n\\t\\tstatic collectCandidates(element, candidates, minScore) {\\n\\t\\t\\tif (ContentDetection.calculateContentScore(element) >= minScore) candidates.push(element);\\n\\t\\t\\tArray.from(element.children).forEach((child) => {\\n\\t\\t\\t\\tContentDetection.collectCandidates(child, candidates, minScore);\\n\\t\\t\\t});\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Calculate content score for an element\\n\\t\\t*/\\n\\t\\tstatic calculateContentScore(element) {\\n\\t\\t\\tlet score = 0;\\n\\t\\t\\tconst semanticClasses = [\\n\\t\\t\\t\\t\"article\",\\n\\t\\t\\t\\t\"content\",\\n\\t\\t\\t\\t\"main-container\",\\n\\t\\t\\t\\t\"main\",\\n\\t\\t\\t\\t\"main-content\",\\n\\t\\t\\t\\t\"post\",\\n\\t\\t\\t\\t\"entry\"\\n\\t\\t\\t];\\n\\t\\t\\tconst semanticIds = [\\n\\t\\t\\t\\t\"content\",\\n\\t\\t\\t\\t\"main\",\\n\\t\\t\\t\\t\"article\",\\n\\t\\t\\t\\t\"post\",\\n\\t\\t\\t\\t\"entry\"\\n\\t\\t\\t];\\n\\t\\t\\tsemanticClasses.forEach((cls) => {\\n\\t\\t\\t\\tif (element.classList.contains(cls)) score += 10;\\n\\t\\t\\t});\\n\\t\\t\\tsemanticIds.forEach((id) => {\\n\\t\\t\\t\\tif (element.id?.toLowerCase().includes(id)) score += 10;\\n\\t\\t\\t});\\n\\t\\t\\tconst tag = element.tagName.toLowerCase();\\n\\t\\t\\tif ([\\n\\t\\t\\t\\t\"article\",\\n\\t\\t\\t\\t\"main\",\\n\\t\\t\\t\\t\"section\"\\n\\t\\t\\t].includes(tag)) score += 8;\\n\\t\\t\\tconst paragraphs = element.getElementsByTagName(\"p\").length;\\n\\t\\t\\tscore += Math.min(paragraphs * 2, 10);\\n\\t\\t\\tconst headings = element.querySelectorAll(\"h1, h2, h3\").length;\\n\\t\\t\\tscore += Math.min(headings * 3, 9);\\n\\t\\t\\tconst textLength = element.textContent?.trim().length || 0;\\n\\t\\t\\tif (textLength > 300) score += Math.min(Math.floor(textLength / 300) * 2, 10);\\n\\t\\t\\tconst linkDensity = ContentDetection.calculateLinkDensity(element);\\n\\t\\t\\tif (linkDensity < .3) score += 5;\\n\\t\\t\\telse if (linkDensity > .5) score -= 5;\\n\\t\\t\\tif (element.hasAttribute(\"data-main\") || element.hasAttribute(\"data-content\") || element.hasAttribute(\"itemprop\")) score += 8;\\n\\t\\t\\tconst role = element.getAttribute(\"role\");\\n\\t\\t\\tif (role === \"main\" || role === \"article\") score += 10;\\n\\t\\t\\tif (element.matches(\"aside, nav, header, footer, .sidebar, .navigation, .menu, .ad, .advertisement\")) score -= 10;\\n\\t\\t\\tif (element.getElementsByTagName(\"form\").length > 2) score -= 5;\\n\\t\\t\\treturn Math.max(0, score);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Calculate link density in an element\\n\\t\\t*/\\n\\t\\tstatic calculateLinkDensity(element) {\\n\\t\\t\\tconst links = element.getElementsByTagName(\"a\");\\n\\t\\t\\tlet linkTextLength = 0;\\n\\t\\t\\tfor (const link of Array.from(links)) linkTextLength += link.textContent?.length || 0;\\n\\t\\t\\tconst totalTextLength = element.textContent?.length || 1;\\n\\t\\t\\treturn linkTextLength / totalTextLength;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if an element is likely navigation\\n\\t\\t*/\\n\\t\\tstatic isNavigation(element) {\\n\\t\\t\\tif (element.tagName.toLowerCase() === \"nav\" || element.getAttribute(\"role\") === \"navigation\") return true;\\n\\t\\t\\tconst navPatterns = [\\n\\t\\t\\t\\t/nav/i,\\n\\t\\t\\t\\t/menu/i,\\n\\t\\t\\t\\t/sidebar/i,\\n\\t\\t\\t\\t/toolbar/i\\n\\t\\t\\t];\\n\\t\\t\\tconst classesAndId = `${element.className} ${element.id}`.toLowerCase();\\n\\t\\t\\treturn navPatterns.some((pattern) => pattern.test(classesAndId));\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element is likely supplementary content\\n\\t\\t*/\\n\\t\\tstatic isSupplementary(element) {\\n\\t\\t\\tif (element.tagName.toLowerCase() === \"aside\" || element.getAttribute(\"role\") === \"complementary\") return true;\\n\\t\\t\\tconst supplementaryPatterns = [\\n\\t\\t\\t\\t/sidebar/i,\\n\\t\\t\\t\\t/widget/i,\\n\\t\\t\\t\\t/related/i,\\n\\t\\t\\t\\t/advertisement/i,\\n\\t\\t\\t\\t/social/i\\n\\t\\t\\t];\\n\\t\\t\\tconst classesAndId = `${element.className} ${element.id}`.toLowerCase();\\n\\t\\t\\treturn supplementaryPatterns.some((pattern) => pattern.test(classesAndId));\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Detect page landmarks\\n\\t\\t*/\\n\\t\\tstatic detectLandmarks(doc) {\\n\\t\\t\\tconst landmarks = {\\n\\t\\t\\t\\tnavigation: [],\\n\\t\\t\\t\\tmain: [],\\n\\t\\t\\t\\tcomplementary: [],\\n\\t\\t\\t\\tcontentinfo: [],\\n\\t\\t\\t\\tbanner: [],\\n\\t\\t\\t\\tsearch: [],\\n\\t\\t\\t\\tform: [],\\n\\t\\t\\t\\tregion: []\\n\\t\\t\\t};\\n\\t\\t\\tfor (const [landmark, selector] of Object.entries({\\n\\t\\t\\t\\tnavigation: \"nav, [role=\\\\\"navigation\\\\\"]\",\\n\\t\\t\\t\\tmain: \"main, [role=\\\\\"main\\\\\"]\",\\n\\t\\t\\t\\tcomplementary: \"aside, [role=\\\\\"complementary\\\\\"]\",\\n\\t\\t\\t\\tcontentinfo: \"footer, [role=\\\\\"contentinfo\\\\\"]\",\\n\\t\\t\\t\\tbanner: \"header, [role=\\\\\"banner\\\\\"]\",\\n\\t\\t\\t\\tsearch: \"[role=\\\\\"search\\\\\"]\",\\n\\t\\t\\t\\tform: \"form[aria-label], form[aria-labelledby], [role=\\\\\"form\\\\\"]\",\\n\\t\\t\\t\\tregion: \"section[aria-label], section[aria-labelledby], [role=\\\\\"region\\\\\"]\"\\n\\t\\t\\t})) {\\n\\t\\t\\t\\tconst elements = doc.querySelectorAll(selector);\\n\\t\\t\\t\\tlandmarks[landmark] = Array.from(elements);\\n\\t\\t\\t}\\n\\t\\t\\treturn landmarks;\\n\\t\\t}\\n\\t};\\n\\t//#endregion\\n\\t//#region src/selectors.ts\\n\\tvar SelectorGenerator = class SelectorGenerator {\\n\\t\\t/**\\n\\t\\t* Generate multiple selector strategies for an element\\n\\t\\t*/\\n\\t\\tstatic generateSelectors(element) {\\n\\t\\t\\tconst doc = element.ownerDocument || document;\\n\\t\\t\\tconst candidates = [];\\n\\t\\t\\tif (element.id && SelectorGenerator.isUniqueId(element.id, doc)) candidates.push({\\n\\t\\t\\t\\ttype: \"id\",\\n\\t\\t\\t\\tvalue: `#${CSS.escape(element.id)}`,\\n\\t\\t\\t\\tscore: 100\\n\\t\\t\\t});\\n\\t\\t\\tconst testId = SelectorGenerator.getDataTestId(element);\\n\\t\\t\\tif (testId) {\\n\\t\\t\\t\\tconst v = `[data-testid=\"${CSS.escape(testId)}\"]`;\\n\\t\\t\\t\\tcandidates.push({\\n\\t\\t\\t\\t\\ttype: \"data-testid\",\\n\\t\\t\\t\\t\\tvalue: v,\\n\\t\\t\\t\\t\\tscore: 90 + (SelectorGenerator.isUniqueSelectorSafe(v, doc) ? 5 : 0)\\n\\t\\t\\t\\t});\\n\\t\\t\\t}\\n\\t\\t\\tconst role = element.getAttribute(\"role\");\\n\\t\\t\\tconst aria = element.getAttribute(\"aria-label\");\\n\\t\\t\\tif (role && aria) {\\n\\t\\t\\t\\tconst v = `[role=\"${CSS.escape(role)}\"][aria-label=\"${CSS.escape(aria)}\"]`;\\n\\t\\t\\t\\tcandidates.push({\\n\\t\\t\\t\\t\\ttype: \"role-aria\",\\n\\t\\t\\t\\t\\tvalue: v,\\n\\t\\t\\t\\t\\tscore: 85 + (SelectorGenerator.isUniqueSelectorSafe(v, doc) ? 5 : 0)\\n\\t\\t\\t\\t});\\n\\t\\t\\t}\\n\\t\\t\\tconst nameAttr = element.getAttribute(\"name\");\\n\\t\\t\\tif (nameAttr) {\\n\\t\\t\\t\\tconst v = `[name=\"${CSS.escape(nameAttr)}\"]`;\\n\\t\\t\\t\\tcandidates.push({\\n\\t\\t\\t\\t\\ttype: \"name\",\\n\\t\\t\\t\\t\\tvalue: v,\\n\\t\\t\\t\\t\\tscore: 78 + (SelectorGenerator.isUniqueSelectorSafe(v, doc) ? 5 : 0)\\n\\t\\t\\t\\t});\\n\\t\\t\\t}\\n\\t\\t\\tconst pathCss = SelectorGenerator.generateCSSSelector(element, doc);\\n\\t\\t\\tconst structuralPenalty = (pathCss.match(/:nth-child\\\\(/g) || []).length * 10;\\n\\t\\t\\tconst classBonus = pathCss.includes(\".\") ? 8 : 0;\\n\\t\\t\\tconst pathScore = Math.max(0, 70 + classBonus - structuralPenalty);\\n\\t\\t\\tcandidates.push({\\n\\t\\t\\t\\ttype: \"class-path\",\\n\\t\\t\\t\\tvalue: pathCss,\\n\\t\\t\\t\\tscore: pathScore\\n\\t\\t\\t});\\n\\t\\t\\tconst xpath = SelectorGenerator.generateXPath(element, doc);\\n\\t\\t\\tcandidates.push({\\n\\t\\t\\t\\ttype: \"xpath\",\\n\\t\\t\\t\\tvalue: xpath,\\n\\t\\t\\t\\tscore: 40\\n\\t\\t\\t});\\n\\t\\t\\tconst textBased = SelectorGenerator.generateTextBasedSelector(element);\\n\\t\\t\\tif (textBased) candidates.push({\\n\\t\\t\\t\\ttype: \"text\",\\n\\t\\t\\t\\tvalue: textBased,\\n\\t\\t\\t\\tscore: 30\\n\\t\\t\\t});\\n\\t\\t\\tcandidates.sort((a, b) => b.score - a.score);\\n\\t\\t\\tconst selector = {\\n\\t\\t\\t\\tcss: candidates.find((c) => c.type !== \"xpath\" && c.type !== \"text\")?.value || pathCss,\\n\\t\\t\\t\\txpath,\\n\\t\\t\\t\\tcandidates\\n\\t\\t\\t};\\n\\t\\t\\tif (textBased) selector.textBased = textBased;\\n\\t\\t\\tif (testId) selector.dataTestId = testId;\\n\\t\\t\\tif (aria) selector.ariaLabel = aria;\\n\\t\\t\\treturn selector;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Generate a unique CSS selector for an element\\n\\t\\t*/\\n\\t\\tstatic generateCSSSelector(element, doc) {\\n\\t\\t\\tif (element.id && SelectorGenerator.isUniqueId(element.id, doc)) return `#${CSS.escape(element.id)}`;\\n\\t\\t\\tconst testId = SelectorGenerator.getDataTestId(element);\\n\\t\\t\\tif (testId) return `[data-testid=\"${CSS.escape(testId)}\"]`;\\n\\t\\t\\tconst path = [];\\n\\t\\t\\tlet current = element;\\n\\t\\t\\twhile (current && current.nodeType === Node.ELEMENT_NODE) {\\n\\t\\t\\t\\tlet selector = current.nodeName.toLowerCase();\\n\\t\\t\\t\\tif (current.id && SelectorGenerator.isUniqueId(current.id, doc)) {\\n\\t\\t\\t\\t\\tselector = `#${CSS.escape(current.id)}`;\\n\\t\\t\\t\\t\\tpath.unshift(selector);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tconst classes = SelectorGenerator.getMeaningfulClasses(current);\\n\\t\\t\\t\\tif (classes.length > 0) selector += `.${classes.map((c) => CSS.escape(c)).join(\".\")}`;\\n\\t\\t\\t\\tconst siblings = current.parentElement?.children;\\n\\t\\t\\t\\tif (siblings && siblings.length > 1) {\\n\\t\\t\\t\\t\\tconst index = Array.from(siblings).indexOf(current);\\n\\t\\t\\t\\t\\tif (index > 0 || !SelectorGenerator.isUniqueSelector(selector, current.parentElement)) selector += `:nth-child(${index + 1})`;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tpath.unshift(selector);\\n\\t\\t\\t\\tcurrent = current.parentElement;\\n\\t\\t\\t}\\n\\t\\t\\treturn SelectorGenerator.optimizePath(path, element, doc);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Generate XPath for an element\\n\\t\\t*/\\n\\t\\tstatic generateXPath(element, doc) {\\n\\t\\t\\tif (element.id && SelectorGenerator.isUniqueId(element.id, doc)) return `//*[@id=\"${element.id}\"]`;\\n\\t\\t\\tconst path = [];\\n\\t\\t\\tlet current = element;\\n\\t\\t\\twhile (current && current.nodeType === Node.ELEMENT_NODE) {\\n\\t\\t\\t\\tconst tagName = current.nodeName.toLowerCase();\\n\\t\\t\\t\\tif (current.id && SelectorGenerator.isUniqueId(current.id, doc)) {\\n\\t\\t\\t\\t\\tpath.unshift(`//*[@id=\"${current.id}\"]`);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tlet xpath = tagName;\\n\\t\\t\\t\\tconst siblings = current.parentElement?.children;\\n\\t\\t\\t\\tif (siblings) {\\n\\t\\t\\t\\t\\tconst sameTagSiblings = Array.from(siblings).filter((s) => s.nodeName.toLowerCase() === tagName);\\n\\t\\t\\t\\t\\tif (sameTagSiblings.length > 1) {\\n\\t\\t\\t\\t\\t\\tconst index = sameTagSiblings.indexOf(current) + 1;\\n\\t\\t\\t\\t\\t\\txpath += `[${index}]`;\\n\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tpath.unshift(xpath);\\n\\t\\t\\t\\tcurrent = current.parentElement;\\n\\t\\t\\t}\\n\\t\\t\\treturn `//${path.join(\"/\")}`;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Generate a text-based selector for buttons and links\\n\\t\\t*/\\n\\t\\tstatic generateTextBasedSelector(element) {\\n\\t\\t\\tconst text = element.textContent?.trim();\\n\\t\\t\\tif (!text || text.length > 50) return void 0;\\n\\t\\t\\tconst tag = element.nodeName.toLowerCase();\\n\\t\\t\\tif ([\\n\\t\\t\\t\\t\"button\",\\n\\t\\t\\t\\t\"a\",\\n\\t\\t\\t\\t\"label\"\\n\\t\\t\\t].includes(tag)) return `${tag}:contains(\"${text.replace(/[\\'\"\\\\\\\\]/g, \"\\\\\\\\$&\")}\")`;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get data-testid or similar attributes\\n\\t\\t*/\\n\\t\\tstatic getDataTestId(element) {\\n\\t\\t\\treturn element.getAttribute(\"data-testid\") || element.getAttribute(\"data-test-id\") || element.getAttribute(\"data-test\") || element.getAttribute(\"data-cy\") || void 0;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if an ID is unique in the document\\n\\t\\t*/\\n\\t\\tstatic isUniqueId(id, doc) {\\n\\t\\t\\treturn doc.querySelectorAll(`#${CSS.escape(id)}`).length === 1;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if a selector is unique within a container\\n\\t\\t*/\\n\\t\\tstatic isUniqueSelector(selector, container) {\\n\\t\\t\\ttry {\\n\\t\\t\\t\\treturn container.querySelectorAll(selector).length === 1;\\n\\t\\t\\t} catch {\\n\\t\\t\\t\\treturn false;\\n\\t\\t\\t}\\n\\t\\t}\\n\\t\\tstatic isUniqueSelectorSafe(selector, doc) {\\n\\t\\t\\ttry {\\n\\t\\t\\t\\treturn doc.querySelectorAll(selector).length === 1;\\n\\t\\t\\t} catch {\\n\\t\\t\\t\\treturn false;\\n\\t\\t\\t}\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get meaningful classes (filtering out utility classes)\\n\\t\\t*/\\n\\t\\tstatic getMeaningfulClasses(element) {\\n\\t\\t\\tconst classes = Array.from(element.classList);\\n\\t\\t\\tconst utilityPatterns = [\\n\\t\\t\\t\\t/^(p|m|w|h|text|bg|border|flex|grid|col|row)-/,\\n\\t\\t\\t\\t/^(xs|sm|md|lg|xl|2xl):/,\\n\\t\\t\\t\\t/^(hover|focus|active|disabled|checked):/,\\n\\t\\t\\t\\t/^js-/,\\n\\t\\t\\t\\t/^is-/,\\n\\t\\t\\t\\t/^has-/\\n\\t\\t\\t];\\n\\t\\t\\treturn classes.filter((cls) => {\\n\\t\\t\\t\\tif (cls.length < 3) return false;\\n\\t\\t\\t\\treturn !utilityPatterns.some((pattern) => pattern.test(cls));\\n\\t\\t\\t}).slice(0, 2);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Optimize the selector path by removing unnecessary parts\\n\\t\\t*/\\n\\t\\tstatic optimizePath(path, element, doc) {\\n\\t\\t\\tfor (let i = 0; i < path.length - 1; i++) {\\n\\t\\t\\t\\tconst shortPath = path.slice(i).join(\" > \");\\n\\t\\t\\t\\ttry {\\n\\t\\t\\t\\t\\tconst matches = doc.querySelectorAll(shortPath);\\n\\t\\t\\t\\t\\tif (matches.length === 1 && matches[0] === element) return shortPath;\\n\\t\\t\\t\\t} catch {}\\n\\t\\t\\t}\\n\\t\\t\\treturn path.join(\" > \");\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get a human-readable path description\\n\\t\\t*/\\n\\t\\tstatic getContextPath(element) {\\n\\t\\t\\tconst path = [];\\n\\t\\t\\tlet current = element;\\n\\t\\t\\tlet depth = 0;\\n\\t\\t\\tconst maxDepth = 5;\\n\\t\\t\\twhile (current && current !== element.ownerDocument?.body && depth < maxDepth) {\\n\\t\\t\\t\\tconst tag = current.nodeName.toLowerCase();\\n\\t\\t\\t\\tlet descriptor = tag;\\n\\t\\t\\t\\tif (current.id) descriptor = `${tag}#${current.id}`;\\n\\t\\t\\t\\telse if (current.className && typeof current.className === \"string\") {\\n\\t\\t\\t\\t\\tconst firstClass = current.className.split(\" \")[0];\\n\\t\\t\\t\\t\\tif (firstClass) descriptor = `${tag}.${firstClass}`;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tconst role = current.getAttribute(\"role\");\\n\\t\\t\\t\\tif (role) descriptor += `[role=\"${role}\"]`;\\n\\t\\t\\t\\tpath.unshift(descriptor);\\n\\t\\t\\t\\tcurrent = current.parentElement;\\n\\t\\t\\t\\tdepth++;\\n\\t\\t\\t}\\n\\t\\t\\treturn path;\\n\\t\\t}\\n\\t};\\n\\t//#endregion\\n\\t//#region src/traversal.ts\\n\\tvar DOMTraversal = class DOMTraversal {\\n\\t\\t/**\\n\\t\\t* Check if a node is a Document.\\n\\t\\t*\\n\\t\\t* `instanceof Document` tests the *calling* realm\\'s constructor, so a\\n\\t\\t* document reached through an iframe (frameSelector) always fails it.\\n\\t\\t* nodeType is realm-independent — use this everywhere instead.\\n\\t\\t*/\\n\\t\\tstatic isDocument(node) {\\n\\t\\t\\treturn node.nodeType === Node.DOCUMENT_NODE;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element is visible\\n\\t\\t*/\\n\\t\\tstatic isVisible(element) {\\n\\t\\t\\tconst rect = element.getBoundingClientRect();\\n\\t\\t\\tconst style = element.ownerDocument?.defaultView?.getComputedStyle(element);\\n\\t\\t\\tif (!style) return false;\\n\\t\\t\\treturn !!(rect.width > 0 && rect.height > 0 && style.display !== \"none\" && style.visibility !== \"hidden\" && style.opacity !== \"0\");\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element is in viewport\\n\\t\\t*/\\n\\t\\tstatic isInViewport(element) {\\n\\t\\t\\tconst rect = element.getBoundingClientRect();\\n\\t\\t\\tconst view = {\\n\\t\\t\\t\\twidth: element.ownerDocument?.defaultView?.innerWidth || 0,\\n\\t\\t\\t\\theight: element.ownerDocument?.defaultView?.innerHeight || 0\\n\\t\\t\\t};\\n\\t\\t\\treturn rect.top < view.height && rect.bottom > 0 && rect.left < view.width && rect.right > 0;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element passes filter criteria\\n\\t\\t*/\\n\\t\\tstatic passesFilter(element, filter) {\\n\\t\\t\\tif (!filter) return true;\\n\\t\\t\\tif (filter.excludeSelectors?.length) {\\n\\t\\t\\t\\tfor (const selector of filter.excludeSelectors) if (element.matches(selector)) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.includeSelectors?.length) {\\n\\t\\t\\t\\tlet matches = false;\\n\\t\\t\\t\\tfor (const selector of filter.includeSelectors) if (element.matches(selector)) {\\n\\t\\t\\t\\t\\tmatches = true;\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tif (!matches) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.tags?.length && !filter.tags.includes(element.tagName.toLowerCase())) return false;\\n\\t\\t\\tconst textContent = element.textContent?.toLowerCase() || \"\";\\n\\t\\t\\tif (filter.textContains?.length) {\\n\\t\\t\\t\\tlet hasText = false;\\n\\t\\t\\t\\tfor (const text of filter.textContains) if (textContent.includes(text.toLowerCase())) {\\n\\t\\t\\t\\t\\thasText = true;\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tif (!hasText) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.textMatches?.length) {\\n\\t\\t\\t\\tlet matches = false;\\n\\t\\t\\t\\tfor (const pattern of filter.textMatches) if (pattern.test(textContent)) {\\n\\t\\t\\t\\t\\tmatches = true;\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tif (!matches) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.hasAttributes?.length) {\\n\\t\\t\\t\\tfor (const attr of filter.hasAttributes) if (!element.hasAttribute(attr)) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.attributeValues) for (const [attr, value] of Object.entries(filter.attributeValues)) {\\n\\t\\t\\t\\tconst attrValue = element.getAttribute(attr);\\n\\t\\t\\t\\tif (!attrValue) return false;\\n\\t\\t\\t\\tif (typeof value === \"string\") {\\n\\t\\t\\t\\t\\tif (attrValue !== value) return false;\\n\\t\\t\\t\\t} else if (value instanceof RegExp) {\\n\\t\\t\\t\\t\\tif (!value.test(attrValue)) return false;\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.withinSelectors?.length) {\\n\\t\\t\\t\\tlet isWithin = false;\\n\\t\\t\\t\\tfor (const selector of filter.withinSelectors) if (element.closest(selector)) {\\n\\t\\t\\t\\t\\tisWithin = true;\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tif (!isWithin) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.interactionTypes?.length) {\\n\\t\\t\\t\\tconst interaction = DOMTraversal.getInteractionInfo(element);\\n\\t\\t\\t\\tlet hasInteraction = false;\\n\\t\\t\\t\\tfor (const type of filter.interactionTypes) if (interaction[type]) {\\n\\t\\t\\t\\t\\thasInteraction = true;\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tif (!hasInteraction) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.nearText) {\\n\\t\\t\\t\\tconst parent = element.parentElement;\\n\\t\\t\\t\\tif (!parent || !parent.textContent?.toLowerCase().includes(filter.nearText.toLowerCase())) return false;\\n\\t\\t\\t}\\n\\t\\t\\treturn true;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract element information\\n\\t\\t*/\\n\\t\\tstatic extractElement(element, options, depth = 0) {\\n\\t\\t\\tif (options.maxDepth && depth > options.maxDepth) return null;\\n\\t\\t\\tif (!options.includeHidden && !DOMTraversal.isVisible(element)) return null;\\n\\t\\t\\tif (options.viewportOnly && !DOMTraversal.isInViewport(element)) return null;\\n\\t\\t\\tif (!DOMTraversal.passesFilter(element, options.filter)) return null;\\n\\t\\t\\tconst extracted = {\\n\\t\\t\\t\\ttag: element.tagName.toLowerCase(),\\n\\t\\t\\t\\ttext: DOMTraversal.getElementText(element, options),\\n\\t\\t\\t\\tselector: SelectorGenerator.generateSelectors(element),\\n\\t\\t\\t\\tattributes: DOMTraversal.getRelevantAttributes(element, options),\\n\\t\\t\\t\\tcontext: DOMTraversal.getElementContext(element),\\n\\t\\t\\t\\tinteraction: DOMTraversal.getInteractionInfo(element)\\n\\t\\t\\t};\\n\\t\\t\\tif (options.mode === \"full\" && DOMTraversal.isSemanticContainer(element)) {\\n\\t\\t\\t\\tconst children = [];\\n\\t\\t\\t\\tif (options.includeShadowDOM && element.shadowRoot) {\\n\\t\\t\\t\\t\\tconst shadowChildren = DOMTraversal.extractChildren(element.shadowRoot, options, depth + 1);\\n\\t\\t\\t\\t\\tchildren.push(...shadowChildren);\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tconst regularChildren = DOMTraversal.extractChildren(element, options, depth + 1);\\n\\t\\t\\t\\tchildren.push(...regularChildren);\\n\\t\\t\\t\\tif (children.length > 0) extracted.children = children;\\n\\t\\t\\t}\\n\\t\\t\\treturn extracted;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract children elements\\n\\t\\t*/\\n\\t\\tstatic extractChildren(container, options, depth) {\\n\\t\\t\\tconst children = [];\\n\\t\\t\\tconst elements = container.querySelectorAll(\"*\");\\n\\t\\t\\tfor (const child of Array.from(elements)) {\\n\\t\\t\\t\\tif (DOMTraversal.hasExtractedAncestor(child, elements)) continue;\\n\\t\\t\\t\\tconst extracted = DOMTraversal.extractElement(child, options, depth);\\n\\t\\t\\t\\tif (extracted) children.push(extracted);\\n\\t\\t\\t}\\n\\t\\t\\treturn children;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element has an ancestor that was already extracted\\n\\t\\t*/\\n\\t\\tstatic hasExtractedAncestor(element, extractedElements) {\\n\\t\\t\\tlet parent = element.parentElement;\\n\\t\\t\\twhile (parent) {\\n\\t\\t\\t\\tif (Array.from(extractedElements).includes(parent)) return true;\\n\\t\\t\\t\\tparent = parent.parentElement;\\n\\t\\t\\t}\\n\\t\\t\\treturn false;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get relevant attributes for an element\\n\\t\\t*/\\n\\t\\tstatic getRelevantAttributes(element, options) {\\n\\t\\t\\tconst relevant = [\\n\\t\\t\\t\\t\"id\",\\n\\t\\t\\t\\t\"class\",\\n\\t\\t\\t\\t\"name\",\\n\\t\\t\\t\\t\"type\",\\n\\t\\t\\t\\t\"value\",\\n\\t\\t\\t\\t\"placeholder\",\\n\\t\\t\\t\\t\"href\",\\n\\t\\t\\t\\t\"src\",\\n\\t\\t\\t\\t\"alt\",\\n\\t\\t\\t\\t\"title\",\\n\\t\\t\\t\\t\"action\",\\n\\t\\t\\t\\t\"method\",\\n\\t\\t\\t\\t\"aria-label\",\\n\\t\\t\\t\\t\"aria-describedby\",\\n\\t\\t\\t\\t\"aria-controls\",\\n\\t\\t\\t\\t\"role\",\\n\\t\\t\\t\\t\"disabled\",\\n\\t\\t\\t\\t\"readonly\",\\n\\t\\t\\t\\t\"required\",\\n\\t\\t\\t\\t\"checked\",\\n\\t\\t\\t\\t\"min\",\\n\\t\\t\\t\\t\"max\",\\n\\t\\t\\t\\t\"pattern\",\\n\\t\\t\\t\\t\"step\",\\n\\t\\t\\t\\t\"autocomplete\",\\n\\t\\t\\t\\t\"data-testid\",\\n\\t\\t\\t\\t\"data-test\",\\n\\t\\t\\t\\t\"data-cy\"\\n\\t\\t\\t];\\n\\t\\t\\tconst attributes = {};\\n\\t\\t\\tconst attrTruncate = options.attributeTruncateLength ?? 100;\\n\\t\\t\\tconst dataAttrTruncate = options.dataAttributeTruncateLength ?? 50;\\n\\t\\t\\tfor (const attr of relevant) {\\n\\t\\t\\t\\tconst value = element.getAttribute(attr);\\n\\t\\t\\t\\tif (value) attributes[attr] = value.length > attrTruncate ? `${value.substring(0, attrTruncate)}...` : value;\\n\\t\\t\\t}\\n\\t\\t\\tfor (const attr of element.attributes) if (attr.name.startsWith(\"data-\") && !relevant.includes(attr.name)) attributes[attr.name] = attr.value.length > dataAttrTruncate ? `${attr.value.substring(0, dataAttrTruncate)}...` : attr.value;\\n\\t\\t\\treturn attributes;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get element context information\\n\\t\\t*/\\n\\t\\tstatic getElementContext(element) {\\n\\t\\t\\tconst context = { parentChain: SelectorGenerator.getContextPath(element) };\\n\\t\\t\\tconst form = element.closest(\"form\");\\n\\t\\t\\tif (form) context.nearestForm = SelectorGenerator.generateSelectors(form).css;\\n\\t\\t\\tconst section = element.closest(\"section, [role=\\\\\"region\\\\\"]\");\\n\\t\\t\\tif (section) context.nearestSection = SelectorGenerator.generateSelectors(section).css;\\n\\t\\t\\tconst main = element.closest(\"main, [role=\\\\\"main\\\\\"]\");\\n\\t\\t\\tif (main) context.nearestMain = SelectorGenerator.generateSelectors(main).css;\\n\\t\\t\\tconst nav = element.closest(\"nav, [role=\\\\\"navigation\\\\\"]\");\\n\\t\\t\\tif (nav) context.nearestNav = SelectorGenerator.generateSelectors(nav).css;\\n\\t\\t\\treturn context;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get interaction information for an element (compact format)\\n\\t\\t*/\\n\\t\\tstatic getInteractionInfo(element) {\\n\\t\\t\\tconst htmlElement = element;\\n\\t\\t\\tconst interaction = {};\\n\\t\\t\\tif (htmlElement.onclick || element.getAttribute(\"onclick\") || element.matches(\"button, a[href], [role=\\\\\"button\\\\\"], [tabindex]:not([tabindex=\\\\\"-1\\\\\"])\")) interaction.click = true;\\n\\t\\t\\tif (htmlElement.onchange || element.getAttribute(\"onchange\") || element.matches(\"input, select, textarea\")) interaction.change = true;\\n\\t\\t\\tif (htmlElement.onsubmit || element.getAttribute(\"onsubmit\") || element.matches(\"form\")) interaction.submit = true;\\n\\t\\t\\tif (element.matches(\"a[href], button[type=\\\\\"submit\\\\\"]\")) interaction.nav = true;\\n\\t\\t\\tif (element.hasAttribute(\"disabled\") || element.getAttribute(\"aria-disabled\") === \"true\") interaction.disabled = true;\\n\\t\\t\\tif (!DOMTraversal.isVisible(element)) interaction.hidden = true;\\n\\t\\t\\tconst ariaRole = element.getAttribute(\"role\");\\n\\t\\t\\tif (ariaRole) interaction.role = ariaRole;\\n\\t\\t\\tif (element.matches(\"input, textarea, select, button\")) {\\n\\t\\t\\t\\tconst form = element.form || element.closest(\"form\");\\n\\t\\t\\t\\tif (form) interaction.form = SelectorGenerator.generateSelectors(form).css;\\n\\t\\t\\t}\\n\\t\\t\\treturn interaction;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get text content of an element (limited length)\\n\\t\\t*/\\n\\t\\tstatic getElementText(element, options) {\\n\\t\\t\\tif (element.matches(\"input, textarea\")) {\\n\\t\\t\\t\\tconst input = element;\\n\\t\\t\\t\\treturn input.value || input.placeholder || \"\";\\n\\t\\t\\t}\\n\\t\\t\\tif (element.matches(\"img\")) return element.alt || \"\";\\n\\t\\t\\tconst text = element.textContent?.trim() || \"\";\\n\\t\\t\\tconst maxLength = options?.textTruncateLength;\\n\\t\\t\\tif (maxLength && text.length > maxLength) return `${text.substring(0, maxLength)}...`;\\n\\t\\t\\treturn text;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element is a semantic container\\n\\t\\t*/\\n\\t\\tstatic isSemanticContainer(element) {\\n\\t\\t\\treturn element.matches(\"article, section, nav, aside, main, header, footer, form, table, ul, ol, dl, figure, details, dialog, [role=\\\\\"region\\\\\"], [role=\\\\\"navigation\\\\\"], [role=\\\\\"main\\\\\"], [role=\\\\\"complementary\\\\\"]\");\\n\\t\\t}\\n\\t};\\n\\t//#endregion\\n\\t//#region src/smart-dom-reader.ts\\n\\t/**\\n\\t* Smart DOM Reader - Full Extraction Approach\\n\\t*\\n\\t* This class provides complete DOM extraction in a single pass.\\n\\t* Use this when you need all information upfront and have sufficient\\n\\t* token budget for processing the complete output.\\n\\t*\\n\\t* Features:\\n\\t* - Single-pass extraction of all elements\\n\\t* - Two modes: \\'interactive\\' (UI elements) or \\'full\\' (includes content)\\n\\t* - Efficient for automation and testing scenarios\\n\\t* - Returns complete structured data immediately\\n\\t*/\\n\\tvar SmartDOMReader = class SmartDOMReader {\\n\\t\\toptions;\\n\\t\\tconstructor(options = {}) {\\n\\t\\t\\tthis.options = {\\n\\t\\t\\t\\tmode: options.mode || \"interactive\",\\n\\t\\t\\t\\tmaxDepth: options.maxDepth || 5,\\n\\t\\t\\t\\tincludeHidden: options.includeHidden || false,\\n\\t\\t\\t\\tincludeShadowDOM: options.includeShadowDOM ?? true,\\n\\t\\t\\t\\tincludeIframes: options.includeIframes || false,\\n\\t\\t\\t\\tviewportOnly: options.viewportOnly || false,\\n\\t\\t\\t\\tmainContentOnly: options.mainContentOnly || false,\\n\\t\\t\\t\\tcustomSelectors: options.customSelectors || [],\\n\\t\\t\\t\\t...options.attributeTruncateLength !== void 0 && { attributeTruncateLength: options.attributeTruncateLength },\\n\\t\\t\\t\\t...options.dataAttributeTruncateLength !== void 0 && { dataAttributeTruncateLength: options.dataAttributeTruncateLength },\\n\\t\\t\\t\\t...options.textTruncateLength !== void 0 && { textTruncateLength: options.textTruncateLength },\\n\\t\\t\\t\\t...options.filter !== void 0 && { filter: options.filter }\\n\\t\\t\\t};\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Main extraction method - extracts all data in one pass\\n\\t\\t* @param rootElement The document or element to extract from\\n\\t\\t* @param runtimeOptions Options to override constructor options\\n\\t\\t*/\\n\\t\\textract(rootElement = document, runtimeOptions) {\\n\\t\\t\\tconst startTime = Date.now();\\n\\t\\t\\tconst rootIsDocument = DOMTraversal.isDocument(rootElement);\\n\\t\\t\\tconst doc = rootIsDocument ? rootElement : rootElement.ownerDocument;\\n\\t\\t\\tconst options = {\\n\\t\\t\\t\\t...this.options,\\n\\t\\t\\t\\t...runtimeOptions\\n\\t\\t\\t};\\n\\t\\t\\tlet container = rootIsDocument ? doc : rootElement;\\n\\t\\t\\tif (options.mainContentOnly && rootIsDocument) container = ContentDetection.findMainContent(doc);\\n\\t\\t\\tconst pageState = this.extractPageState(doc);\\n\\t\\t\\tconst landmarks = this.extractLandmarks(doc);\\n\\t\\t\\tconst interactive = this.extractInteractiveElements(container, options);\\n\\t\\t\\tconst result = {\\n\\t\\t\\t\\tmode: options.mode,\\n\\t\\t\\t\\ttimestamp: startTime,\\n\\t\\t\\t\\tpage: pageState,\\n\\t\\t\\t\\tlandmarks,\\n\\t\\t\\t\\tinteractive\\n\\t\\t\\t};\\n\\t\\t\\tif (options.mode === \"full\") {\\n\\t\\t\\t\\tconst semantic = this.extractSemanticElements(container, options);\\n\\t\\t\\t\\tconst metadata = this.extractMetadata(doc, container, options);\\n\\t\\t\\t\\treturn {\\n\\t\\t\\t\\t\\t...result,\\n\\t\\t\\t\\t\\tsemantic,\\n\\t\\t\\t\\t\\tmetadata\\n\\t\\t\\t\\t};\\n\\t\\t\\t}\\n\\t\\t\\treturn result;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract page state information\\n\\t\\t*/\\n\\t\\textractPageState(doc) {\\n\\t\\t\\tconst hasFocus = this.getFocusedElement(doc);\\n\\t\\t\\treturn {\\n\\t\\t\\t\\turl: doc.location?.href || \"\",\\n\\t\\t\\t\\ttitle: doc.title || \"\",\\n\\t\\t\\t\\thasErrors: this.detectErrors(doc),\\n\\t\\t\\t\\tisLoading: this.detectLoading(doc),\\n\\t\\t\\t\\thasModals: this.detectModals(doc),\\n\\t\\t\\t\\t...hasFocus !== void 0 && { hasFocus }\\n\\t\\t\\t};\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract page landmarks\\n\\t\\t*/\\n\\t\\textractLandmarks(doc) {\\n\\t\\t\\tconst detected = ContentDetection.detectLandmarks(doc);\\n\\t\\t\\treturn {\\n\\t\\t\\t\\tnavigation: this.elementsToSelectors(detected.navigation || []),\\n\\t\\t\\t\\tmain: this.elementsToSelectors(detected.main || []),\\n\\t\\t\\t\\tforms: this.elementsToSelectors(detected.form || []),\\n\\t\\t\\t\\theaders: this.elementsToSelectors(detected.banner || []),\\n\\t\\t\\t\\tfooters: this.elementsToSelectors(detected.contentinfo || []),\\n\\t\\t\\t\\tarticles: this.elementsToSelectors(detected.region || []),\\n\\t\\t\\t\\tsections: this.elementsToSelectors(detected.region || [])\\n\\t\\t\\t};\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Convert elements to selector strings\\n\\t\\t*/\\n\\t\\telementsToSelectors(elements) {\\n\\t\\t\\treturn elements.map((el) => SelectorGenerator.generateSelectors(el).css);\\n\\t\\t}\\n\\t\\tquerySelectorAll(container, selector, includeShadowDOM) {\\n\\t\\t\\tconst matches = [...container.querySelectorAll(selector)];\\n\\t\\t\\tif (!includeShadowDOM) return matches;\\n\\t\\t\\tfor (const element of container.querySelectorAll(\"*\")) if (element.shadowRoot) matches.push(...this.querySelectorAll(element.shadowRoot, selector, true));\\n\\t\\t\\treturn matches;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract every element matching a selector.\\n\\t\\t*\\n\\t\\t* DOMTraversal.extractElement already applies the visibility, viewport and\\n\\t\\t* filter guards, so pre-filtering here would just repeat its\\n\\t\\t* getBoundingClientRect/getComputedStyle work for every element.\\n\\t\\t*/\\n\\t\\textractAll(container, selector, options) {\\n\\t\\t\\tconst extracted = [];\\n\\t\\t\\tfor (const el of this.querySelectorAll(container, selector, options.includeShadowDOM)) {\\n\\t\\t\\t\\tconst element = DOMTraversal.extractElement(el, options);\\n\\t\\t\\t\\tif (element) extracted.push(element);\\n\\t\\t\\t}\\n\\t\\t\\treturn extracted;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract interactive elements\\n\\t\\t*/\\n\\t\\textractInteractiveElements(container, options) {\\n\\t\\t\\tconst clickable = [];\\n\\t\\t\\tfor (const selector of options.customSelectors ?? []) clickable.push(...this.extractAll(container, selector, options));\\n\\t\\t\\treturn {\\n\\t\\t\\t\\tbuttons: this.extractAll(container, \"button, [role=\\\\\"button\\\\\"], input[type=\\\\\"button\\\\\"], input[type=\\\\\"submit\\\\\"]\", options),\\n\\t\\t\\t\\tlinks: this.extractAll(container, \"a[href]\", options),\\n\\t\\t\\t\\tinputs: this.extractAll(container, \"input:not([type=\\\\\"button\\\\\"]):not([type=\\\\\"submit\\\\\"]), textarea, select\", options),\\n\\t\\t\\t\\tforms: this.extractForms(container, options),\\n\\t\\t\\t\\tclickable\\n\\t\\t\\t};\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract form information\\n\\t\\t*/\\n\\t\\textractForms(container, options) {\\n\\t\\t\\tconst forms = [];\\n\\t\\t\\tthis.querySelectorAll(container, \"form\", options.includeShadowDOM).forEach((form) => {\\n\\t\\t\\t\\tif (!this.shouldIncludeElement(form, options)) return;\\n\\t\\t\\t\\tconst action = form.getAttribute(\"action\");\\n\\t\\t\\t\\tconst method = form.getAttribute(\"method\");\\n\\t\\t\\t\\tconst formInfo = {\\n\\t\\t\\t\\t\\tselector: SelectorGenerator.generateSelectors(form).css,\\n\\t\\t\\t\\t\\tinputs: this.extractAll(form, \"input:not([type=\\\\\"button\\\\\"]):not([type=\\\\\"submit\\\\\"]), textarea, select\", options),\\n\\t\\t\\t\\t\\tbuttons: this.extractAll(form, \"button, input[type=\\\\\"button\\\\\"], input[type=\\\\\"submit\\\\\"]\", options)\\n\\t\\t\\t\\t};\\n\\t\\t\\t\\tif (action) formInfo.action = action;\\n\\t\\t\\t\\tif (method) formInfo.method = method;\\n\\t\\t\\t\\tforms.push(formInfo);\\n\\t\\t\\t});\\n\\t\\t\\treturn forms;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract semantic elements (full mode only)\\n\\t\\t*/\\n\\t\\textractSemanticElements(container, options) {\\n\\t\\t\\treturn {\\n\\t\\t\\t\\theadings: this.extractAll(container, \"h1, h2, h3, h4, h5, h6\", options),\\n\\t\\t\\t\\timages: this.extractAll(container, \"img\", options),\\n\\t\\t\\t\\ttables: this.extractAll(container, \"table\", options),\\n\\t\\t\\t\\tlists: this.extractAll(container, \"ul, ol\", options),\\n\\t\\t\\t\\tarticles: this.extractAll(container, \"article, [role=\\\\\"article\\\\\"]\", options)\\n\\t\\t\\t};\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract metadata\\n\\t\\t*/\\n\\t\\textractMetadata(doc, container, options) {\\n\\t\\t\\tconst allElements = this.querySelectorAll(container, \"*\", options.includeShadowDOM);\\n\\t\\t\\tconst extractedElements = this.querySelectorAll(container, \"button, a, input, textarea, select, h1, h2, h3, h4, h5, h6, img, table, ul, ol, article\", options.includeShadowDOM).length;\\n\\t\\t\\tconst metadata = {\\n\\t\\t\\t\\ttotalElements: allElements.length,\\n\\t\\t\\t\\textractedElements\\n\\t\\t\\t};\\n\\t\\t\\tif (options.mainContentOnly && !DOMTraversal.isDocument(container)) metadata.mainContent = SelectorGenerator.generateSelectors(container).css;\\n\\t\\t\\tconst language = doc.documentElement.getAttribute(\"lang\");\\n\\t\\t\\tif (language) metadata.language = language;\\n\\t\\t\\treturn metadata;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element should be included based on options\\n\\t\\t*/\\n\\t\\tshouldIncludeElement(element, options) {\\n\\t\\t\\tif (!options.includeHidden && !DOMTraversal.isVisible(element)) return false;\\n\\t\\t\\tif (options.viewportOnly && !DOMTraversal.isInViewport(element)) return false;\\n\\t\\t\\tif (options.filter && !DOMTraversal.passesFilter(element, options.filter)) return false;\\n\\t\\t\\treturn true;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Detect errors on the page\\n\\t\\t*/\\n\\t\\tdetectErrors(doc) {\\n\\t\\t\\treturn [\\n\\t\\t\\t\\t\".error\",\\n\\t\\t\\t\\t\".alert-danger\",\\n\\t\\t\\t\\t\"[role=\\\\\"alert\\\\\"]\",\\n\\t\\t\\t\\t\".error-message\"\\n\\t\\t\\t].some((sel) => {\\n\\t\\t\\t\\tconst element = doc.querySelector(sel);\\n\\t\\t\\t\\treturn element ? DOMTraversal.isVisible(element) : false;\\n\\t\\t\\t});\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Detect if page is loading\\n\\t\\t*/\\n\\t\\tdetectLoading(doc) {\\n\\t\\t\\treturn [\\n\\t\\t\\t\\t\".loading\",\\n\\t\\t\\t\\t\".spinner\",\\n\\t\\t\\t\\t\"[aria-busy=\\\\\"true\\\\\"]\",\\n\\t\\t\\t\\t\".loader\"\\n\\t\\t\\t].some((sel) => {\\n\\t\\t\\t\\tconst element = doc.querySelector(sel);\\n\\t\\t\\t\\treturn element ? DOMTraversal.isVisible(element) : false;\\n\\t\\t\\t});\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Detect modal dialogs\\n\\t\\t*/\\n\\t\\tdetectModals(doc) {\\n\\t\\t\\treturn [\\n\\t\\t\\t\\t\"[role=\\\\\"dialog\\\\\"]\",\\n\\t\\t\\t\\t\".modal\",\\n\\t\\t\\t\\t\".popup\",\\n\\t\\t\\t\\t\".overlay\"\\n\\t\\t\\t].some((sel) => {\\n\\t\\t\\t\\tconst element = doc.querySelector(sel);\\n\\t\\t\\t\\treturn element ? DOMTraversal.isVisible(element) : false;\\n\\t\\t\\t});\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get currently focused element\\n\\t\\t*/\\n\\t\\tgetFocusedElement(doc) {\\n\\t\\t\\tconst focused = doc.activeElement;\\n\\t\\t\\tif (focused && focused !== doc.body) return SelectorGenerator.generateSelectors(focused).css;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Quick extraction for interactive elements only\\n\\t\\t* @param doc The document to extract from\\n\\t\\t* @param options Extraction options\\n\\t\\t*/\\n\\t\\tstatic extractInteractive(doc, options = {}) {\\n\\t\\t\\treturn new SmartDOMReader({\\n\\t\\t\\t\\t...options,\\n\\t\\t\\t\\tmode: \"interactive\"\\n\\t\\t\\t}).extract(doc);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Quick extraction for full content\\n\\t\\t* @param doc The document to extract from\\n\\t\\t* @param options Extraction options\\n\\t\\t*/\\n\\t\\tstatic extractFull(doc, options = {}) {\\n\\t\\t\\treturn new SmartDOMReader({\\n\\t\\t\\t\\t...options,\\n\\t\\t\\t\\tmode: \"full\"\\n\\t\\t\\t}).extract(doc);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract from a specific element\\n\\t\\t* @param element The element to extract from\\n\\t\\t* @param mode The extraction mode\\n\\t\\t* @param options Additional options\\n\\t\\t*/\\n\\t\\tstatic extractFromElement(element, mode = \"interactive\", options = {}) {\\n\\t\\t\\treturn new SmartDOMReader({\\n\\t\\t\\t\\t...options,\\n\\t\\t\\t\\tmode\\n\\t\\t\\t}).extract(element);\\n\\t\\t}\\n\\t};\\n\\t//#endregion\\n\\t//#region src/markdown-formatter.ts\\n\\tfunction truncate(text, len) {\\n\\t\\tconst t = (text ?? \"\").trim();\\n\\t\\tif (!len || t.length <= len) return t;\\n\\t\\tconst keywords = [\\n\\t\\t\\t\"login\",\\n\\t\\t\\t\"log in\",\\n\\t\\t\\t\"sign in\",\\n\\t\\t\\t\"sign up\",\\n\\t\\t\\t\"submit\",\\n\\t\\t\\t\"search\",\\n\\t\\t\\t\"filter\",\\n\\t\\t\\t\"add to cart\",\\n\\t\\t\\t\"next\",\\n\\t\\t\\t\"continue\"\\n\\t\\t];\\n\\t\\tconst lower = t.toLowerCase();\\n\\t\\tconst hit = keywords.map((k) => ({\\n\\t\\t\\tk,\\n\\t\\t\\ti: lower.indexOf(k)\\n\\t\\t})).find((x) => x.i > -1);\\n\\t\\tconst head = Math.max(0, Math.floor(len * .66));\\n\\t\\tif (hit && hit.i > head) {\\n\\t\\t\\tconst tailWindow = Math.max(12, len - head - 5);\\n\\t\\t\\tconst start = Math.max(0, hit.i - Math.floor(tailWindow / 2));\\n\\t\\t\\tconst end = Math.min(t.length, start + tailWindow);\\n\\t\\t\\treturn `${t.slice(0, head).trimEnd()} … ${t.slice(start, end).trim()}…`;\\n\\t\\t}\\n\\t\\tconst slice = t.slice(0, len);\\n\\t\\tconst lastSpace = slice.lastIndexOf(\" \");\\n\\t\\treturn `${lastSpace > 32 ? slice.slice(0, lastSpace) : slice}…`;\\n\\t}\\n\\tfunction hashId(input) {\\n\\t\\tlet h = 5381;\\n\\t\\tfor (let i = 0; i < input.length; i++) h = h * 33 ^ input.charCodeAt(i);\\n\\t\\treturn `sec-${(h >>> 0).toString(36)}`;\\n\\t}\\n\\tfunction iconForRegion(key) {\\n\\t\\tswitch (key) {\\n\\t\\t\\tcase \"header\": return \"🧭\";\\n\\t\\t\\tcase \"navigation\": return \"📑\";\\n\\t\\t\\tcase \"main\": return \"📄\";\\n\\t\\t\\tcase \"sections\": return \"🗂️\";\\n\\t\\t\\tcase \"sidebar\": return \"📚\";\\n\\t\\t\\tcase \"footer\": return \"🔻\";\\n\\t\\t\\tcase \"modals\": return \"💬\";\\n\\t\\t\\tdefault: return \"🔹\";\\n\\t\\t}\\n\\t}\\n\\tfunction elementLine(el, opts) {\\n\\t\\tconst txt = truncate(el.text || el.attributes?.ariaLabel, opts?.maxTextLength ?? 80);\\n\\t\\tconst sel = el.selector?.css || \"\";\\n\\t\\tconst tag = el.tag.toLowerCase();\\n\\t\\tconst action = el.interaction?.submit ? \"submit\" : el.interaction?.click ? \"click\" : el.interaction?.change ? \"change\" : void 0;\\n\\t\\tconst actionText = action ? ` (${action})` : \"\";\\n\\t\\treturn `- ${tag.toUpperCase()}: ${txt || \"(no text)\"} → \\\\`${sel}\\\\`${actionText}`;\\n\\t}\\n\\tfunction selectorQualitySummary(inter) {\\n\\t\\tconst all = [];\\n\\t\\tall.push(...inter.buttons.map((e) => e.selector?.css || \"\"));\\n\\t\\tall.push(...inter.links.map((e) => e.selector?.css || \"\"));\\n\\t\\tall.push(...inter.inputs.map((e) => e.selector?.css || \"\"));\\n\\t\\tall.push(...inter.clickable.map((e) => e.selector?.css || \"\"));\\n\\t\\tconst total = all.length || 1;\\n\\t\\tconst idCount = all.filter((s) => s.startsWith(\"#\")).length;\\n\\t\\tconst testIdCount = all.filter((s) => /\\\\[data-testid=/.test(s)).length;\\n\\t\\tconst nthCount = all.filter((s) => /:nth-child\\\\(/.test(s)).length;\\n\\t\\tconst stable = idCount + testIdCount;\\n\\t\\treturn `Selector quality: ${Math.round(stable / total * 100)}% stable (ID/data-testid), ${Math.round(nthCount / total * 100)}% structural (:nth-child)`;\\n\\t}\\n\\tfunction renderInteractive(inter, opts) {\\n\\t\\tconst parts = [];\\n\\t\\tconst limit = (arr) => typeof opts?.maxElements === \"number\" ? arr.slice(0, opts.maxElements) : arr;\\n\\t\\tif (inter.buttons.length) {\\n\\t\\t\\tparts.push(\"Buttons:\");\\n\\t\\t\\tfor (const el of limit(inter.buttons)) parts.push(elementLine(el, opts));\\n\\t\\t}\\n\\t\\tif (inter.links.length) {\\n\\t\\t\\tparts.push(\"Links:\");\\n\\t\\t\\tfor (const el of limit(inter.links)) parts.push(elementLine(el, opts));\\n\\t\\t}\\n\\t\\tif (inter.inputs.length) {\\n\\t\\t\\tparts.push(\"Inputs:\");\\n\\t\\t\\tfor (const el of limit(inter.inputs)) parts.push(elementLine(el, opts));\\n\\t\\t}\\n\\t\\tif (inter.clickable.length) {\\n\\t\\t\\tparts.push(\"Other Clickable:\");\\n\\t\\t\\tfor (const el of limit(inter.clickable)) parts.push(elementLine(el, opts));\\n\\t\\t}\\n\\t\\tif (inter.forms.length) {\\n\\t\\t\\tparts.push(\"Forms:\");\\n\\t\\t\\tfor (const f of limit(inter.forms)) parts.push(`- FORM: action=${f.action ?? \"-\"} method=${f.method ?? \"-\"} → \\\\`${f.selector}\\\\``);\\n\\t\\t}\\n\\t\\treturn parts.join(\"\\\\n\");\\n\\t}\\n\\tfunction renderRegionInfo(region) {\\n\\t\\tconst icon = iconForRegion(\"region\");\\n\\t\\tconst id = hashId(`${region.selector}|${region.label ?? \"\"}|${region.role ?? \"\"}`);\\n\\t\\tconst label = region.label ? ` ${region.label}` : \"\";\\n\\t\\tconst stats = [];\\n\\t\\tif (region.buttonCount) stats.push(`${region.buttonCount} buttons`);\\n\\t\\tif (region.linkCount) stats.push(`${region.linkCount} links`);\\n\\t\\tif (region.inputCount) stats.push(`${region.inputCount} inputs`);\\n\\t\\tif (region.textPreview) stats.push(`“${truncate(region.textPreview, 80)}”`);\\n\\t\\tconst statsLine = stats.length ? ` — ${stats.join(\", \")}` : \"\";\\n\\t\\treturn `${icon} ${label} → \\\\`${region.selector}\\\\` [${id}]${statsLine}`;\\n\\t}\\n\\tfunction wrapXml(body, meta, type = \"section\") {\\n\\t\\treturn `<page ${[meta?.title ? `title=\"${escapeXml(meta.title)}\"` : null, meta?.url ? `url=\"${escapeXml(meta.url)}\"` : null].filter(Boolean).join(\" \")}>\\\\n <${type}><![CDATA[\\\\n${body}\\\\n]]></${type}>\\\\n</page>`;\\n\\t}\\n\\tfunction escapeXml(s) {\\n\\t\\treturn s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\");\\n\\t}\\n\\tvar MarkdownFormatter = class {\\n\\t\\tstatic structure(overview, _opts = {}, meta) {\\n\\t\\t\\tconst lines = [];\\n\\t\\t\\tlines.push(\"# Page Outline\");\\n\\t\\t\\tif (meta?.title || meta?.url) {\\n\\t\\t\\t\\tlines.push(`Title: ${meta?.title ?? \"\"}`.trim());\\n\\t\\t\\t\\tlines.push(`URL: ${meta?.url ?? \"\"}`.trim());\\n\\t\\t\\t}\\n\\t\\t\\tlines.push(\"\");\\n\\t\\t\\tconst regions = overview.regions;\\n\\t\\t\\tconst entries = [\\n\\t\\t\\t\\t[\"header\", regions.header],\\n\\t\\t\\t\\t[\"navigation\", regions.navigation],\\n\\t\\t\\t\\t[\"main\", regions.main],\\n\\t\\t\\t\\t[\"sections\", regions.sections],\\n\\t\\t\\t\\t[\"sidebar\", regions.sidebar],\\n\\t\\t\\t\\t[\"footer\", regions.footer],\\n\\t\\t\\t\\t[\"modals\", regions.modals]\\n\\t\\t\\t];\\n\\t\\t\\tfor (const [key, value] of entries) {\\n\\t\\t\\t\\tif (!value) continue;\\n\\t\\t\\t\\tconst icon = iconForRegion(key);\\n\\t\\t\\t\\tif (Array.isArray(value)) {\\n\\t\\t\\t\\t\\tif (!value.length) continue;\\n\\t\\t\\t\\t\\tlines.push(`## ${icon} ${capitalize(key)}`);\\n\\t\\t\\t\\t\\tfor (const region of value) lines.push(renderRegionInfo(region));\\n\\t\\t\\t\\t} else {\\n\\t\\t\\t\\t\\tlines.push(`## ${icon} ${capitalize(key)}`);\\n\\t\\t\\t\\t\\tlines.push(renderRegionInfo(value));\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tif (overview.suggestions?.length) {\\n\\t\\t\\t\\tlines.push(\"## Suggestions\");\\n\\t\\t\\t\\tfor (const s of overview.suggestions) lines.push(`- ${s}`);\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tlines.push(\"Next: choose a region (by selector or [sectionId]) and call dom_extract_region for actionable details.\");\\n\\t\\t\\treturn wrapXml(lines.join(\"\\\\n\"), meta, \"outline\");\\n\\t\\t}\\n\\t\\tstatic region(result, opts = {}, meta) {\\n\\t\\t\\tconst lines = [];\\n\\t\\t\\tlines.push(\"# Region Details\");\\n\\t\\t\\tif (meta?.title || meta?.url) {\\n\\t\\t\\t\\tlines.push(`Title: ${meta?.title ?? \"\"}`.trim());\\n\\t\\t\\t\\tlines.push(`URL: ${meta?.url ?? \"\"}`.trim());\\n\\t\\t\\t}\\n\\t\\t\\tlines.push(\"\");\\n\\t\\t\\tconst inter = result.interactive;\\n\\t\\t\\tif (result.page) {\\n\\t\\t\\t\\tconst ps = [\\n\\t\\t\\t\\t\\tresult.page.hasErrors ? \"errors: yes\" : \"errors: no\",\\n\\t\\t\\t\\t\\tresult.page.isLoading ? \"loading: yes\" : \"loading: no\",\\n\\t\\t\\t\\t\\tresult.page.hasModals ? \"modals: yes\" : \"modals: no\"\\n\\t\\t\\t\\t];\\n\\t\\t\\t\\tlines.push(`Page state: ${ps.join(\", \")}`);\\n\\t\\t\\t}\\n\\t\\t\\tconst summary = [];\\n\\t\\t\\tconst count = (arr) => arr ? arr.length : 0;\\n\\t\\t\\tsummary.push(`${count(inter.buttons)} buttons`);\\n\\t\\t\\tsummary.push(`${count(inter.links)} links`);\\n\\t\\t\\tsummary.push(`${count(inter.inputs)} inputs`);\\n\\t\\t\\tif (inter.forms?.length) summary.push(`${count(inter.forms)} forms`);\\n\\t\\t\\tlines.push(`Summary: ${summary.join(\", \")}`);\\n\\t\\t\\tlines.push(selectorQualitySummary(inter));\\n\\t\\t\\tlines.push(\"\");\\n\\t\\t\\tlines.push(renderInteractive(inter, opts));\\n\\t\\t\\tlines.push(\"\");\\n\\t\\t\\tlines.push(\"Next: write a script using the most stable selectors above. If selectors look unstable, rerun dom_extract_region with higher detail or call dom_extract_content for text context.\");\\n\\t\\t\\treturn wrapXml(lines.join(\"\\\\n\"), meta, \"section\");\\n\\t\\t}\\n\\t\\tstatic content(content, opts = {}, meta) {\\n\\t\\t\\tconst lines = [];\\n\\t\\t\\tlines.push(\"# Content\");\\n\\t\\t\\tlines.push(`Selector: \\\\`${content.selector}\\\\``);\\n\\t\\t\\tlines.push(\"\");\\n\\t\\t\\tif (content.text.headings?.length) {\\n\\t\\t\\t\\tlines.push(\"Headings:\");\\n\\t\\t\\t\\tfor (const h of content.text.headings) lines.push(`- H${h.level}: ${truncate(h.text, opts.maxTextLength ?? 120)}`);\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tif (content.text.paragraphs?.length) {\\n\\t\\t\\t\\tconst limit = typeof opts.maxElements === \"number\" ? opts.maxElements : content.text.paragraphs.length;\\n\\t\\t\\t\\tlines.push(\"Paragraphs:\");\\n\\t\\t\\t\\tfor (const p of content.text.paragraphs.slice(0, limit)) lines.push(`- ${truncate(p, opts.maxTextLength ?? 200)}`);\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tif (content.text.lists?.length) {\\n\\t\\t\\t\\tlines.push(\"Lists:\");\\n\\t\\t\\t\\tfor (const list of content.text.lists) {\\n\\t\\t\\t\\t\\tlines.push(`- ${list.type.toUpperCase()}:`);\\n\\t\\t\\t\\t\\tconst limit = typeof opts.maxElements === \"number\" ? opts.maxElements : list.items.length;\\n\\t\\t\\t\\t\\tfor (const item of list.items.slice(0, limit)) lines.push(` - ${truncate(item, opts.maxTextLength ?? 120)}`);\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tif (content.tables?.length) {\\n\\t\\t\\t\\tlines.push(\"Tables:\");\\n\\t\\t\\t\\tfor (const t of content.tables) {\\n\\t\\t\\t\\t\\tlines.push(`- Headers: ${t.headers.join(\" | \")}`);\\n\\t\\t\\t\\t\\tconst limit = typeof opts.maxElements === \"number\" ? opts.maxElements : t.rows.length;\\n\\t\\t\\t\\t\\tfor (const row of t.rows.slice(0, limit)) lines.push(` - ${row.join(\" | \")}`);\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tif (content.media?.length) {\\n\\t\\t\\t\\tlines.push(\"Media:\");\\n\\t\\t\\t\\tconst limit = typeof opts.maxElements === \"number\" ? opts.maxElements : content.media.length;\\n\\t\\t\\t\\tfor (const m of content.media.slice(0, limit)) lines.push(`- ${m.type.toUpperCase()}: ${m.alt ?? \"\"} ${m.src ? `→ ${m.src}` : \"\"}`.trim());\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tlines.push(\"Next: if text is insufficient for targeting, call dom_extract_region for interactive selectors.\");\\n\\t\\t\\treturn wrapXml(lines.join(\"\\\\n\"), meta, \"content\");\\n\\t\\t}\\n\\t};\\n\\tfunction capitalize(s) {\\n\\t\\treturn s.charAt(0).toUpperCase() + s.slice(1);\\n\\t}\\n\\t//#endregion\\n\\t//#region src/progressive.ts\\n\\tvar ProgressiveExtractor = class ProgressiveExtractor {\\n\\t\\t/**\\n\\t\\t* Step 1: Extract high-level structural overview\\n\\t\\t* This provides a \"map\" of the page for the AI to understand structure\\n\\t\\t*/\\n\\t\\tstatic extractStructure(root) {\\n\\t\\t\\tconst regions = {};\\n\\t\\t\\tconst header = root.querySelector(\"header, [role=\\\\\"banner\\\\\"], .header, #header\");\\n\\t\\t\\tif (header) regions.header = ProgressiveExtractor.analyzeRegion(header);\\n\\t\\t\\tconst navs = root.querySelectorAll(\"nav, [role=\\\\\"navigation\\\\\"], .nav, .navigation\");\\n\\t\\t\\tif (navs.length > 0) regions.navigation = Array.from(navs).map((nav) => ProgressiveExtractor.analyzeRegion(nav));\\n\\t\\t\\tif (DOMTraversal.isDocument(root)) {\\n\\t\\t\\t\\tconst main = ContentDetection.findMainContent(root);\\n\\t\\t\\t\\tif (main) {\\n\\t\\t\\t\\t\\tregions.main = ProgressiveExtractor.analyzeRegion(main);\\n\\t\\t\\t\\t\\tconst sections = main.querySelectorAll(\"section, article, [role=\\\\\"region\\\\\"]\");\\n\\t\\t\\t\\t\\tif (sections.length > 0) regions.sections = Array.from(sections).filter((section) => !section.closest(\"nav, header, footer\")).map((section) => ProgressiveExtractor.analyzeRegion(section));\\n\\t\\t\\t\\t}\\n\\t\\t\\t} else {\\n\\t\\t\\t\\tregions.main = ProgressiveExtractor.analyzeRegion(root);\\n\\t\\t\\t\\tconst sections = root.querySelectorAll(\"section, article, [role=\\\\\"region\\\\\"]\");\\n\\t\\t\\t\\tif (sections.length > 0) regions.sections = Array.from(sections).filter((section) => !section.closest(\"nav, header, footer\")).map((section) => ProgressiveExtractor.analyzeRegion(section));\\n\\t\\t\\t}\\n\\t\\t\\tconst sidebars = root.querySelectorAll(\"aside, [role=\\\\\"complementary\\\\\"], .sidebar, #sidebar\");\\n\\t\\t\\tif (sidebars.length > 0) regions.sidebar = Array.from(sidebars).map((sidebar) => ProgressiveExtractor.analyzeRegion(sidebar));\\n\\t\\t\\tconst footer = root.querySelector(\"footer, [role=\\\\\"contentinfo\\\\\"], .footer, #footer\");\\n\\t\\t\\tif (footer) regions.footer = ProgressiveExtractor.analyzeRegion(footer);\\n\\t\\t\\tconst modals = root.querySelectorAll(\"[role=\\\\\"dialog\\\\\"], .modal, .popup, .overlay\");\\n\\t\\t\\tconst visibleModals = Array.from(modals).filter((modal) => DOMTraversal.isVisible(modal));\\n\\t\\t\\tif (visibleModals.length > 0) regions.modals = visibleModals.map((modal) => ProgressiveExtractor.analyzeRegion(modal));\\n\\t\\t\\tconst forms = ProgressiveExtractor.extractFormOverview(root);\\n\\t\\t\\tconst summary = ProgressiveExtractor.calculateSummary(root, regions, forms);\\n\\t\\t\\treturn {\\n\\t\\t\\t\\tregions,\\n\\t\\t\\t\\tforms,\\n\\t\\t\\t\\tsummary,\\n\\t\\t\\t\\tsuggestions: ProgressiveExtractor.generateSuggestions(regions, summary)\\n\\t\\t\\t};\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Step 2: Extract detailed information from a specific region\\n\\t\\t*/\\n\\t\\tstatic extractRegion(selector, doc, options = {}) {\\n\\t\\t\\tconst element = doc.querySelector(selector);\\n\\t\\t\\tif (!element) return null;\\n\\t\\t\\treturn new SmartDOMReader(options).extract(element, options);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Step 3: Extract readable content from a region\\n\\t\\t*/\\n\\t\\tstatic extractContent(selector, doc, options = {}) {\\n\\t\\t\\tconst element = doc.querySelector(selector);\\n\\t\\t\\tif (!element) return null;\\n\\t\\t\\tconst result = {\\n\\t\\t\\t\\tselector,\\n\\t\\t\\t\\ttext: {},\\n\\t\\t\\t\\tmetadata: {\\n\\t\\t\\t\\t\\twordCount: 0,\\n\\t\\t\\t\\t\\thasInteractive: false\\n\\t\\t\\t\\t}\\n\\t\\t\\t};\\n\\t\\t\\tif (options.includeHeadings !== false) {\\n\\t\\t\\t\\tconst headings = element.querySelectorAll(\"h1, h2, h3, h4, h5, h6\");\\n\\t\\t\\t\\tresult.text.headings = Array.from(headings).map((h) => ({\\n\\t\\t\\t\\t\\tlevel: Number.parseInt(h.tagName[1], 10),\\n\\t\\t\\t\\t\\ttext: ProgressiveExtractor.getTextContent(h, options.maxTextLength)\\n\\t\\t\\t\\t}));\\n\\t\\t\\t}\\n\\t\\t\\tconst paragraphs = element.querySelectorAll(\"p\");\\n\\t\\t\\tif (paragraphs.length > 0) result.text.paragraphs = Array.from(paragraphs).map((p) => ProgressiveExtractor.getTextContent(p, options.maxTextLength)).filter((text) => text.length > 0);\\n\\t\\t\\tif (options.includeLists !== false) {\\n\\t\\t\\t\\tconst lists = element.querySelectorAll(\"ul, ol\");\\n\\t\\t\\t\\tresult.text.lists = Array.from(lists).map((list) => ({\\n\\t\\t\\t\\t\\ttype: list.tagName.toLowerCase(),\\n\\t\\t\\t\\t\\titems: Array.from(list.querySelectorAll(\"li\")).map((li) => ProgressiveExtractor.getTextContent(li, options.maxTextLength))\\n\\t\\t\\t\\t}));\\n\\t\\t\\t}\\n\\t\\t\\tif (options.includeTables !== false) {\\n\\t\\t\\t\\tconst tables = element.querySelectorAll(\"table\");\\n\\t\\t\\t\\tresult.tables = Array.from(tables).map((table) => {\\n\\t\\t\\t\\t\\treturn {\\n\\t\\t\\t\\t\\t\\theaders: Array.from(table.querySelectorAll(\"th\")).map((th) => ProgressiveExtractor.getTextContent(th)),\\n\\t\\t\\t\\t\\t\\trows: Array.from(table.querySelectorAll(\"tr\")).filter((tr) => tr.querySelector(\"td\")).map((tr) => Array.from(tr.querySelectorAll(\"td\")).map((td) => ProgressiveExtractor.getTextContent(td)))\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t});\\n\\t\\t\\t}\\n\\t\\t\\tif (options.includeMedia !== false) {\\n\\t\\t\\t\\tconst images = element.querySelectorAll(\"img\");\\n\\t\\t\\t\\tconst videos = element.querySelectorAll(\"video\");\\n\\t\\t\\t\\tconst audios = element.querySelectorAll(\"audio\");\\n\\t\\t\\t\\tresult.media = [\\n\\t\\t\\t\\t\\t...Array.from(images).map((img) => {\\n\\t\\t\\t\\t\\t\\tconst item = { type: \"img\" };\\n\\t\\t\\t\\t\\t\\tconst alt = img.getAttribute(\"alt\");\\n\\t\\t\\t\\t\\t\\tconst src = img.getAttribute(\"src\");\\n\\t\\t\\t\\t\\t\\tif (alt) item.alt = alt;\\n\\t\\t\\t\\t\\t\\tif (src) item.src = src;\\n\\t\\t\\t\\t\\t\\treturn item;\\n\\t\\t\\t\\t\\t}),\\n\\t\\t\\t\\t\\t...Array.from(videos).map((video) => {\\n\\t\\t\\t\\t\\t\\tconst item = { type: \"video\" };\\n\\t\\t\\t\\t\\t\\tconst src = video.getAttribute(\"src\");\\n\\t\\t\\t\\t\\t\\tif (src) item.src = src;\\n\\t\\t\\t\\t\\t\\treturn item;\\n\\t\\t\\t\\t\\t}),\\n\\t\\t\\t\\t\\t...Array.from(audios).map((audio) => {\\n\\t\\t\\t\\t\\t\\tconst item = { type: \"audio\" };\\n\\t\\t\\t\\t\\t\\tconst src = audio.getAttribute(\"src\");\\n\\t\\t\\t\\t\\t\\tif (src) item.src = src;\\n\\t\\t\\t\\t\\t\\treturn item;\\n\\t\\t\\t\\t\\t})\\n\\t\\t\\t\\t];\\n\\t\\t\\t}\\n\\t\\t\\tconst allText = element.textContent || \"\";\\n\\t\\t\\tresult.metadata.wordCount = allText.trim().split(/\\\\s+/).length;\\n\\t\\t\\tresult.metadata.hasInteractive = element.querySelectorAll(\"button, a, input, textarea, select\").length > 0;\\n\\t\\t\\treturn result;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Analyze a region and extract summary information\\n\\t\\t*/\\n\\t\\tstatic analyzeRegion(element) {\\n\\t\\t\\tconst selector = SelectorGenerator.generateSelectors(element).css;\\n\\t\\t\\tconst buttons = element.querySelectorAll(\"button, [role=\\\\\"button\\\\\"]\");\\n\\t\\t\\tconst links = element.querySelectorAll(\"a[href]\");\\n\\t\\t\\tconst inputs = element.querySelectorAll(\"input, textarea, select\");\\n\\t\\t\\tconst forms = element.querySelectorAll(\"form\");\\n\\t\\t\\tconst lists = element.querySelectorAll(\"ul, ol\");\\n\\t\\t\\tconst tables = element.querySelectorAll(\"table\");\\n\\t\\t\\tconst media = element.querySelectorAll(\"img, video, audio\");\\n\\t\\t\\tconst interactiveCount = buttons.length + links.length + inputs.length;\\n\\t\\t\\tlet label;\\n\\t\\t\\tconst ariaLabel = element.getAttribute(\"aria-label\");\\n\\t\\t\\tif (ariaLabel) label = ariaLabel;\\n\\t\\t\\telse if (element.getAttribute(\"aria-labelledby\")) {\\n\\t\\t\\t\\tconst labelId = element.getAttribute(\"aria-labelledby\");\\n\\t\\t\\t\\tif (labelId) {\\n\\t\\t\\t\\t\\tconst labelElement = element.ownerDocument?.getElementById(labelId);\\n\\t\\t\\t\\t\\tif (labelElement) label = labelElement.textContent?.trim();\\n\\t\\t\\t\\t}\\n\\t\\t\\t} else {\\n\\t\\t\\t\\tconst heading = element.querySelector(\"h1, h2, h3\");\\n\\t\\t\\t\\tif (heading) label = heading.textContent?.trim();\\n\\t\\t\\t}\\n\\t\\t\\tconst textContent = element.textContent?.trim() || \"\";\\n\\t\\t\\tconst textPreview = textContent.length > 50 ? `${textContent.substring(0, 50)}...` : textContent;\\n\\t\\t\\tconst regionInfo = {\\n\\t\\t\\t\\tselector,\\n\\t\\t\\t\\tinteractiveCount,\\n\\t\\t\\t\\thasForm: forms.length > 0,\\n\\t\\t\\t\\thasList: lists.length > 0,\\n\\t\\t\\t\\thasTable: tables.length > 0,\\n\\t\\t\\t\\thasMedia: media.length > 0\\n\\t\\t\\t};\\n\\t\\t\\tif (label) regionInfo.label = label;\\n\\t\\t\\tconst role = element.getAttribute(\"role\");\\n\\t\\t\\tif (role) regionInfo.role = role;\\n\\t\\t\\tif (buttons.length > 0) regionInfo.buttonCount = buttons.length;\\n\\t\\t\\tif (links.length > 0) regionInfo.linkCount = links.length;\\n\\t\\t\\tif (inputs.length > 0) regionInfo.inputCount = inputs.length;\\n\\t\\t\\tif (textPreview.length > 0) regionInfo.textPreview = textPreview;\\n\\t\\t\\treturn regionInfo;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract overview of forms on the page\\n\\t\\t*/\\n\\t\\tstatic extractFormOverview(root) {\\n\\t\\t\\tconst forms = root.querySelectorAll(\"form\");\\n\\t\\t\\treturn Array.from(forms).map((form) => {\\n\\t\\t\\t\\tconst inputs = form.querySelectorAll(\"input, textarea, select\");\\n\\t\\t\\t\\tconst selector = SelectorGenerator.generateSelectors(form).css;\\n\\t\\t\\t\\tlet location = \"unknown\";\\n\\t\\t\\t\\tif (form.closest(\"header, [role=\\\\\"banner\\\\\"]\")) location = \"header\";\\n\\t\\t\\t\\telse if (form.closest(\"nav, [role=\\\\\"navigation\\\\\"]\")) location = \"navigation\";\\n\\t\\t\\t\\telse if (form.closest(\"main, [role=\\\\\"main\\\\\"]\")) location = \"main\";\\n\\t\\t\\t\\telse if (form.closest(\"aside, [role=\\\\\"complementary\\\\\"]\")) location = \"sidebar\";\\n\\t\\t\\t\\telse if (form.closest(\"footer, [role=\\\\\"contentinfo\\\\\"]\")) location = \"footer\";\\n\\t\\t\\t\\tlet purpose;\\n\\t\\t\\t\\tconst formId = form.getAttribute(\"id\")?.toLowerCase();\\n\\t\\t\\t\\tconst formClass = form.getAttribute(\"class\")?.toLowerCase();\\n\\t\\t\\t\\tconst formAction = form.getAttribute(\"action\")?.toLowerCase();\\n\\t\\t\\t\\tconst hasEmail = form.querySelector(\"input[type=\\\\\"email\\\\\"]\");\\n\\t\\t\\t\\tconst hasPassword = form.querySelector(\"input[type=\\\\\"password\\\\\"]\");\\n\\t\\t\\t\\tif (form.querySelector(\"input[type=\\\\\"search\\\\\"]\") || formId?.includes(\"search\") || formClass?.includes(\"search\")) purpose = \"search\";\\n\\t\\t\\t\\telse if (hasPassword && hasEmail) purpose = \"login\";\\n\\t\\t\\t\\telse if (hasPassword) purpose = \"authentication\";\\n\\t\\t\\t\\telse if (formId?.includes(\"contact\") || formClass?.includes(\"contact\")) purpose = \"contact\";\\n\\t\\t\\t\\telse if (formId?.includes(\"subscribe\") || formClass?.includes(\"subscribe\")) purpose = \"subscription\";\\n\\t\\t\\t\\telse if (formAction?.includes(\"checkout\") || formClass?.includes(\"checkout\")) purpose = \"checkout\";\\n\\t\\t\\t\\tconst formOverview = {\\n\\t\\t\\t\\t\\tselector,\\n\\t\\t\\t\\t\\tlocation,\\n\\t\\t\\t\\t\\tinputCount: inputs.length\\n\\t\\t\\t\\t};\\n\\t\\t\\t\\tif (purpose) formOverview.purpose = purpose;\\n\\t\\t\\t\\treturn formOverview;\\n\\t\\t\\t});\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Calculate summary statistics\\n\\t\\t*/\\n\\t\\tstatic calculateSummary(root, regions, forms) {\\n\\t\\t\\tconst allInteractive = root.querySelectorAll(\"button, a[href], input, textarea, select\");\\n\\t\\t\\tconst allSections = root.querySelectorAll(\"section, article, [role=\\\\\"region\\\\\"]\");\\n\\t\\t\\tconst hasModals = (regions.modals?.length || 0) > 0;\\n\\t\\t\\tconst hasErrors = [\\n\\t\\t\\t\\t\".error\",\\n\\t\\t\\t\\t\".alert-danger\",\\n\\t\\t\\t\\t\"[role=\\\\\"alert\\\\\"]\"\\n\\t\\t\\t].some((sel) => {\\n\\t\\t\\t\\tconst element = root.querySelector(sel);\\n\\t\\t\\t\\treturn element ? DOMTraversal.isVisible(element) : false;\\n\\t\\t\\t});\\n\\t\\t\\tconst isLoading = [\\n\\t\\t\\t\\t\".loading\",\\n\\t\\t\\t\\t\".spinner\",\\n\\t\\t\\t\\t\"[aria-busy=\\\\\"true\\\\\"]\"\\n\\t\\t\\t].some((sel) => {\\n\\t\\t\\t\\tconst element = root.querySelector(sel);\\n\\t\\t\\t\\treturn element ? DOMTraversal.isVisible(element) : false;\\n\\t\\t\\t});\\n\\t\\t\\tconst summary = {\\n\\t\\t\\t\\ttotalInteractive: allInteractive.length,\\n\\t\\t\\t\\ttotalForms: forms.length,\\n\\t\\t\\t\\ttotalSections: allSections.length,\\n\\t\\t\\t\\thasModals,\\n\\t\\t\\t\\thasErrors,\\n\\t\\t\\t\\tisLoading\\n\\t\\t\\t};\\n\\t\\t\\tconst mainContentSelector = regions.main?.selector;\\n\\t\\t\\tif (mainContentSelector) summary.mainContentSelector = mainContentSelector;\\n\\t\\t\\treturn summary;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Generate AI-friendly suggestions\\n\\t\\t*/\\n\\t\\tstatic generateSuggestions(regions, summary) {\\n\\t\\t\\tconst suggestions = [];\\n\\t\\t\\tif (summary.hasErrors) suggestions.push(\"Page has error indicators - check error messages before interacting\");\\n\\t\\t\\tif (summary.isLoading) suggestions.push(\"Page appears to be loading - wait or check loading state\");\\n\\t\\t\\tif (summary.hasModals) suggestions.push(\"Modal/dialog is open - may need to interact with or close it first\");\\n\\t\\t\\tif (regions.main && regions.main.interactiveCount > 10) suggestions.push(`Main content has ${regions.main.interactiveCount} interactive elements - consider filtering`);\\n\\t\\t\\tif (summary.totalForms > 0) suggestions.push(`Found ${summary.totalForms} form(s) on the page`);\\n\\t\\t\\tif (!regions.main) suggestions.push(\"No clear main content area detected - may need to explore regions\");\\n\\t\\t\\treturn suggestions;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get text content with optional truncation\\n\\t\\t*/\\n\\t\\tstatic getTextContent(element, maxLength) {\\n\\t\\t\\tconst text = element.textContent?.trim() || \"\";\\n\\t\\t\\tif (maxLength && text.length > maxLength) return `${text.substring(0, maxLength)}...`;\\n\\t\\t\\treturn text;\\n\\t\\t}\\n\\t};\\n\\t//#endregion\\n\\t//#region src/bundle-entry.ts\\n\\tfunction resolveDocument(frameSelector) {\\n\\t\\tif (!frameSelector) return document;\\n\\t\\tconst iframe = document.querySelector(frameSelector);\\n\\t\\tif (!(iframe instanceof HTMLIFrameElement) || !iframe.contentDocument) throw new Error(`Cannot access iframe: ${frameSelector}`);\\n\\t\\treturn iframe.contentDocument;\\n\\t}\\n\\tfunction executeExtraction(method, args) {\\n\\t\\ttry {\\n\\t\\t\\tlet result;\\n\\t\\t\\tswitch (method) {\\n\\t\\t\\t\\tcase \"extractStructure\": {\\n\\t\\t\\t\\t\\tconst { selector, frameSelector, formatOptions } = args;\\n\\t\\t\\t\\t\\tconst doc = resolveDocument(frameSelector);\\n\\t\\t\\t\\t\\tconst target = selector ? doc.querySelector(selector) ?? doc : doc;\\n\\t\\t\\t\\t\\tconst overview = ProgressiveExtractor.extractStructure(target);\\n\\t\\t\\t\\t\\tconst meta = {\\n\\t\\t\\t\\t\\t\\ttitle: document.title,\\n\\t\\t\\t\\t\\t\\turl: location.href\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t\\tresult = MarkdownFormatter.structure(overview, formatOptions ?? { detail: \"summary\" }, meta);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tcase \"extractRegion\": {\\n\\t\\t\\t\\t\\tconst { selector, mode, frameSelector, options, formatOptions } = args;\\n\\t\\t\\t\\t\\tconst doc = resolveDocument(frameSelector);\\n\\t\\t\\t\\t\\tconst extractOptions = {\\n\\t\\t\\t\\t\\t\\t...options,\\n\\t\\t\\t\\t\\t\\tmode: mode || \"interactive\"\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t\\tconst extractResult = ProgressiveExtractor.extractRegion(selector, doc, extractOptions);\\n\\t\\t\\t\\t\\tif (!extractResult) return { error: `No element found matching selector: ${selector}` };\\n\\t\\t\\t\\t\\tconst meta = {\\n\\t\\t\\t\\t\\t\\ttitle: document.title,\\n\\t\\t\\t\\t\\t\\turl: location.href\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t\\tresult = MarkdownFormatter.region(extractResult, formatOptions ?? { detail: \"region\" }, meta);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tcase \"extractContent\": {\\n\\t\\t\\t\\t\\tconst { selector, frameSelector, options, formatOptions } = args;\\n\\t\\t\\t\\t\\tconst doc = resolveDocument(frameSelector);\\n\\t\\t\\t\\t\\tconst extractOptions = options || {};\\n\\t\\t\\t\\t\\tconst extractResult = ProgressiveExtractor.extractContent(selector, doc, extractOptions);\\n\\t\\t\\t\\t\\tif (!extractResult) return { error: `No element found matching selector: ${selector}` };\\n\\t\\t\\t\\t\\tconst meta = {\\n\\t\\t\\t\\t\\t\\ttitle: document.title,\\n\\t\\t\\t\\t\\t\\turl: location.href\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t\\tresult = MarkdownFormatter.content(extractResult, formatOptions ?? { detail: \"region\" }, meta);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tcase \"extractInteractive\": {\\n\\t\\t\\t\\t\\tconst { selector, frameSelector, options, formatOptions } = args;\\n\\t\\t\\t\\t\\tconst doc = resolveDocument(frameSelector);\\n\\t\\t\\t\\t\\tconst target = selector ? doc.querySelector(selector) : null;\\n\\t\\t\\t\\t\\tif (selector && !target) return { error: `No element found matching selector: ${selector}` };\\n\\t\\t\\t\\t\\tconst extractResult = target ? SmartDOMReader.extractFromElement(target, \"interactive\", options || {}) : SmartDOMReader.extractInteractive(doc, options || {});\\n\\t\\t\\t\\t\\tconst meta = {\\n\\t\\t\\t\\t\\t\\ttitle: document.title,\\n\\t\\t\\t\\t\\t\\turl: location.href\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t\\tresult = MarkdownFormatter.region(extractResult, formatOptions ?? { detail: \"region\" }, meta);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tcase \"extractFull\": {\\n\\t\\t\\t\\t\\tconst { selector, frameSelector, options, formatOptions } = args;\\n\\t\\t\\t\\t\\tconst doc = resolveDocument(frameSelector);\\n\\t\\t\\t\\t\\tconst target = selector ? doc.querySelector(selector) : null;\\n\\t\\t\\t\\t\\tif (selector && !target) return { error: `No element found matching selector: ${selector}` };\\n\\t\\t\\t\\t\\tconst extractResult = target ? SmartDOMReader.extractFromElement(target, \"full\", options || {}) : SmartDOMReader.extractFull(doc, options || {});\\n\\t\\t\\t\\t\\tconst meta = {\\n\\t\\t\\t\\t\\t\\ttitle: document.title,\\n\\t\\t\\t\\t\\t\\turl: location.href\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t\\tresult = MarkdownFormatter.region(extractResult, formatOptions ?? { detail: \"deep\" }, meta);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tdefault: return { error: `Unknown method: ${method}` };\\n\\t\\t\\t}\\n\\t\\t\\treturn result;\\n\\t\\t} catch (error) {\\n\\t\\t\\treturn { error: error instanceof Error ? error.message : String(error) };\\n\\t\\t}\\n\\t}\\n\\t//#endregion\\n\\texports.executeExtraction = executeExtraction;\\n\\treturn exports;\\n})({});\\n';\n\nexport const SMART_DOM_READER_VERSION = '5.0.2';\n"],"mappings":";;;;;;;;AAQA,MAAa,0BACX;AAEF,MAAa,2BAA2B"}
1
+ {"version":3,"file":"bundle-string.mjs","names":[],"sources":["../src/bundle-string.ts"],"sourcesContent":["/**\n * Auto-generated bundle module for smart-dom-reader\n * DO NOT EDIT - Generated by generate-bundle-module.mjs\n *\n * This module exports the bundled smart-dom-reader library as a string\n * that can be injected into web pages for stateless DOM extraction.\n */\n\nexport const SMART_DOM_READER_BUNDLE =\n 'var SmartDOMReaderBundle = (function(exports) {\\n\\tObject.defineProperty(exports, Symbol.toStringTag, { value: \"Module\" });\\n\\t//#region src/content-detection.ts\\n\\tvar ContentDetection = class ContentDetection {\\n\\t\\t/**\\n\\t\\t* Find the main content area of a page\\n\\t\\t* Inspired by dom-to-semantic-markdown\\'s approach\\n\\t\\t*/\\n\\t\\tstatic findMainContent(doc) {\\n\\t\\t\\tconst mainElement = doc.querySelector(\"main, [role=\\\\\"main\\\\\"]\");\\n\\t\\t\\tif (mainElement) return mainElement;\\n\\t\\t\\tif (!doc.body) return doc.documentElement;\\n\\t\\t\\treturn ContentDetection.detectMainContent(doc.body);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Detect main content using scoring algorithm\\n\\t\\t*/\\n\\t\\tstatic detectMainContent(rootElement) {\\n\\t\\t\\tconst candidates = [];\\n\\t\\t\\tContentDetection.collectCandidates(rootElement, candidates, 15);\\n\\t\\t\\tif (candidates.length === 0) return rootElement;\\n\\t\\t\\tcandidates.sort((a, b) => ContentDetection.calculateContentScore(b) - ContentDetection.calculateContentScore(a));\\n\\t\\t\\tlet bestCandidate = candidates[0];\\n\\t\\t\\tfor (let i = 1; i < candidates.length; i++) {\\n\\t\\t\\t\\tconst candidate = candidates[i];\\n\\t\\t\\t\\tif (!candidates.some((other, j) => j !== i && other.contains(candidate)) && ContentDetection.calculateContentScore(candidate) > ContentDetection.calculateContentScore(bestCandidate)) bestCandidate = candidate;\\n\\t\\t\\t}\\n\\t\\t\\treturn bestCandidate;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Collect content candidates\\n\\t\\t*/\\n\\t\\tstatic collectCandidates(element, candidates, minScore) {\\n\\t\\t\\tif (ContentDetection.calculateContentScore(element) >= minScore) candidates.push(element);\\n\\t\\t\\tArray.from(element.children).forEach((child) => {\\n\\t\\t\\t\\tContentDetection.collectCandidates(child, candidates, minScore);\\n\\t\\t\\t});\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Calculate content score for an element\\n\\t\\t*/\\n\\t\\tstatic calculateContentScore(element) {\\n\\t\\t\\tlet score = 0;\\n\\t\\t\\tconst semanticClasses = [\\n\\t\\t\\t\\t\"article\",\\n\\t\\t\\t\\t\"content\",\\n\\t\\t\\t\\t\"main-container\",\\n\\t\\t\\t\\t\"main\",\\n\\t\\t\\t\\t\"main-content\",\\n\\t\\t\\t\\t\"post\",\\n\\t\\t\\t\\t\"entry\"\\n\\t\\t\\t];\\n\\t\\t\\tconst semanticIds = [\\n\\t\\t\\t\\t\"content\",\\n\\t\\t\\t\\t\"main\",\\n\\t\\t\\t\\t\"article\",\\n\\t\\t\\t\\t\"post\",\\n\\t\\t\\t\\t\"entry\"\\n\\t\\t\\t];\\n\\t\\t\\tsemanticClasses.forEach((cls) => {\\n\\t\\t\\t\\tif (element.classList.contains(cls)) score += 10;\\n\\t\\t\\t});\\n\\t\\t\\tsemanticIds.forEach((id) => {\\n\\t\\t\\t\\tif (element.id?.toLowerCase().includes(id)) score += 10;\\n\\t\\t\\t});\\n\\t\\t\\tconst tag = element.tagName.toLowerCase();\\n\\t\\t\\tif ([\\n\\t\\t\\t\\t\"article\",\\n\\t\\t\\t\\t\"main\",\\n\\t\\t\\t\\t\"section\"\\n\\t\\t\\t].includes(tag)) score += 8;\\n\\t\\t\\tconst paragraphs = element.getElementsByTagName(\"p\").length;\\n\\t\\t\\tscore += Math.min(paragraphs * 2, 10);\\n\\t\\t\\tconst headings = element.querySelectorAll(\"h1, h2, h3\").length;\\n\\t\\t\\tscore += Math.min(headings * 3, 9);\\n\\t\\t\\tconst textLength = element.textContent?.trim().length || 0;\\n\\t\\t\\tif (textLength > 300) score += Math.min(Math.floor(textLength / 300) * 2, 10);\\n\\t\\t\\tconst linkDensity = ContentDetection.calculateLinkDensity(element);\\n\\t\\t\\tif (linkDensity < .3) score += 5;\\n\\t\\t\\telse if (linkDensity > .5) score -= 5;\\n\\t\\t\\tif (element.hasAttribute(\"data-main\") || element.hasAttribute(\"data-content\") || element.hasAttribute(\"itemprop\")) score += 8;\\n\\t\\t\\tconst role = element.getAttribute(\"role\");\\n\\t\\t\\tif (role === \"main\" || role === \"article\") score += 10;\\n\\t\\t\\tif (element.matches(\"aside, nav, header, footer, .sidebar, .navigation, .menu, .ad, .advertisement\")) score -= 10;\\n\\t\\t\\tif (element.getElementsByTagName(\"form\").length > 2) score -= 5;\\n\\t\\t\\treturn Math.max(0, score);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Calculate link density in an element\\n\\t\\t*/\\n\\t\\tstatic calculateLinkDensity(element) {\\n\\t\\t\\tconst links = element.getElementsByTagName(\"a\");\\n\\t\\t\\tlet linkTextLength = 0;\\n\\t\\t\\tfor (const link of Array.from(links)) linkTextLength += link.textContent?.length || 0;\\n\\t\\t\\tconst totalTextLength = element.textContent?.length || 1;\\n\\t\\t\\treturn linkTextLength / totalTextLength;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if an element is likely navigation\\n\\t\\t*/\\n\\t\\tstatic isNavigation(element) {\\n\\t\\t\\tif (element.tagName.toLowerCase() === \"nav\" || element.getAttribute(\"role\") === \"navigation\") return true;\\n\\t\\t\\tconst navPatterns = [\\n\\t\\t\\t\\t/nav/i,\\n\\t\\t\\t\\t/menu/i,\\n\\t\\t\\t\\t/sidebar/i,\\n\\t\\t\\t\\t/toolbar/i\\n\\t\\t\\t];\\n\\t\\t\\tconst classesAndId = `${element.className} ${element.id}`.toLowerCase();\\n\\t\\t\\treturn navPatterns.some((pattern) => pattern.test(classesAndId));\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element is likely supplementary content\\n\\t\\t*/\\n\\t\\tstatic isSupplementary(element) {\\n\\t\\t\\tif (element.tagName.toLowerCase() === \"aside\" || element.getAttribute(\"role\") === \"complementary\") return true;\\n\\t\\t\\tconst supplementaryPatterns = [\\n\\t\\t\\t\\t/sidebar/i,\\n\\t\\t\\t\\t/widget/i,\\n\\t\\t\\t\\t/related/i,\\n\\t\\t\\t\\t/advertisement/i,\\n\\t\\t\\t\\t/social/i\\n\\t\\t\\t];\\n\\t\\t\\tconst classesAndId = `${element.className} ${element.id}`.toLowerCase();\\n\\t\\t\\treturn supplementaryPatterns.some((pattern) => pattern.test(classesAndId));\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Detect page landmarks\\n\\t\\t*/\\n\\t\\tstatic detectLandmarks(doc) {\\n\\t\\t\\tconst landmarks = {\\n\\t\\t\\t\\tnavigation: [],\\n\\t\\t\\t\\tmain: [],\\n\\t\\t\\t\\tcomplementary: [],\\n\\t\\t\\t\\tcontentinfo: [],\\n\\t\\t\\t\\tbanner: [],\\n\\t\\t\\t\\tsearch: [],\\n\\t\\t\\t\\tform: [],\\n\\t\\t\\t\\tregion: []\\n\\t\\t\\t};\\n\\t\\t\\tfor (const [landmark, selector] of Object.entries({\\n\\t\\t\\t\\tnavigation: \"nav, [role=\\\\\"navigation\\\\\"]\",\\n\\t\\t\\t\\tmain: \"main, [role=\\\\\"main\\\\\"]\",\\n\\t\\t\\t\\tcomplementary: \"aside, [role=\\\\\"complementary\\\\\"]\",\\n\\t\\t\\t\\tcontentinfo: \"footer, [role=\\\\\"contentinfo\\\\\"]\",\\n\\t\\t\\t\\tbanner: \"header, [role=\\\\\"banner\\\\\"]\",\\n\\t\\t\\t\\tsearch: \"[role=\\\\\"search\\\\\"]\",\\n\\t\\t\\t\\tform: \"form[aria-label], form[aria-labelledby], [role=\\\\\"form\\\\\"]\",\\n\\t\\t\\t\\tregion: \"section[aria-label], section[aria-labelledby], [role=\\\\\"region\\\\\"]\"\\n\\t\\t\\t})) {\\n\\t\\t\\t\\tconst elements = doc.querySelectorAll(selector);\\n\\t\\t\\t\\tlandmarks[landmark] = Array.from(elements);\\n\\t\\t\\t}\\n\\t\\t\\treturn landmarks;\\n\\t\\t}\\n\\t};\\n\\t//#endregion\\n\\t//#region src/selectors.ts\\n\\tvar SelectorGenerator = class SelectorGenerator {\\n\\t\\t/**\\n\\t\\t* Generate multiple selector strategies for an element\\n\\t\\t*/\\n\\t\\tstatic generateSelectors(element) {\\n\\t\\t\\tconst doc = element.ownerDocument || document;\\n\\t\\t\\tconst candidates = [];\\n\\t\\t\\tif (element.id && SelectorGenerator.isUniqueId(element.id, doc)) candidates.push({\\n\\t\\t\\t\\ttype: \"id\",\\n\\t\\t\\t\\tvalue: `#${CSS.escape(element.id)}`,\\n\\t\\t\\t\\tscore: 100\\n\\t\\t\\t});\\n\\t\\t\\tconst testId = SelectorGenerator.getDataTestId(element);\\n\\t\\t\\tif (testId) {\\n\\t\\t\\t\\tconst v = `[${testId.name}=\"${CSS.escape(testId.value)}\"]`;\\n\\t\\t\\t\\tcandidates.push({\\n\\t\\t\\t\\t\\ttype: \"data-testid\",\\n\\t\\t\\t\\t\\tvalue: v,\\n\\t\\t\\t\\t\\tscore: 90 + (SelectorGenerator.isUniqueSelector(v, doc) ? 5 : 0)\\n\\t\\t\\t\\t});\\n\\t\\t\\t}\\n\\t\\t\\tconst role = element.getAttribute(\"role\");\\n\\t\\t\\tconst aria = element.getAttribute(\"aria-label\");\\n\\t\\t\\tif (role && aria) {\\n\\t\\t\\t\\tconst v = `[role=\"${CSS.escape(role)}\"][aria-label=\"${CSS.escape(aria)}\"]`;\\n\\t\\t\\t\\tcandidates.push({\\n\\t\\t\\t\\t\\ttype: \"role-aria\",\\n\\t\\t\\t\\t\\tvalue: v,\\n\\t\\t\\t\\t\\tscore: 85 + (SelectorGenerator.isUniqueSelector(v, doc) ? 5 : 0)\\n\\t\\t\\t\\t});\\n\\t\\t\\t}\\n\\t\\t\\tconst nameAttr = element.getAttribute(\"name\");\\n\\t\\t\\tif (nameAttr) {\\n\\t\\t\\t\\tconst v = `[name=\"${CSS.escape(nameAttr)}\"]`;\\n\\t\\t\\t\\tcandidates.push({\\n\\t\\t\\t\\t\\ttype: \"name\",\\n\\t\\t\\t\\t\\tvalue: v,\\n\\t\\t\\t\\t\\tscore: 78 + (SelectorGenerator.isUniqueSelector(v, doc) ? 5 : 0)\\n\\t\\t\\t\\t});\\n\\t\\t\\t}\\n\\t\\t\\tconst pathCss = SelectorGenerator.generateCSSSelector(element, doc);\\n\\t\\t\\tconst structuralPenalty = (pathCss.match(/:nth-child\\\\(/g) || []).length * 10;\\n\\t\\t\\tconst classBonus = pathCss.includes(\".\") ? 8 : 0;\\n\\t\\t\\tconst pathScore = Math.max(0, 70 + classBonus - structuralPenalty);\\n\\t\\t\\tcandidates.push({\\n\\t\\t\\t\\ttype: \"class-path\",\\n\\t\\t\\t\\tvalue: pathCss,\\n\\t\\t\\t\\tscore: pathScore\\n\\t\\t\\t});\\n\\t\\t\\tconst xpath = SelectorGenerator.generateXPath(element, doc);\\n\\t\\t\\tcandidates.push({\\n\\t\\t\\t\\ttype: \"xpath\",\\n\\t\\t\\t\\tvalue: xpath,\\n\\t\\t\\t\\tscore: 40\\n\\t\\t\\t});\\n\\t\\t\\tconst textBased = SelectorGenerator.generateTextBasedSelector(element);\\n\\t\\t\\tif (textBased) candidates.push({\\n\\t\\t\\t\\ttype: \"text\",\\n\\t\\t\\t\\tvalue: textBased,\\n\\t\\t\\t\\tscore: 30\\n\\t\\t\\t});\\n\\t\\t\\tcandidates.sort((a, b) => b.score - a.score);\\n\\t\\t\\tconst selector = {\\n\\t\\t\\t\\tcss: candidates.find((c) => c.type !== \"xpath\" && c.type !== \"text\" && SelectorGenerator.isUniqueSelector(c.value, doc, element))?.value || pathCss,\\n\\t\\t\\t\\txpath,\\n\\t\\t\\t\\tcandidates\\n\\t\\t\\t};\\n\\t\\t\\tif (textBased) selector.textBased = textBased;\\n\\t\\t\\tif (testId) selector.dataTestId = testId.value;\\n\\t\\t\\tif (aria) selector.ariaLabel = aria;\\n\\t\\t\\treturn selector;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Generate a unique CSS selector for an element\\n\\t\\t*/\\n\\t\\tstatic generateCSSSelector(element, doc) {\\n\\t\\t\\tif (element.id && SelectorGenerator.isUniqueId(element.id, doc)) return `#${CSS.escape(element.id)}`;\\n\\t\\t\\tconst testId = SelectorGenerator.getDataTestId(element);\\n\\t\\t\\tif (testId) {\\n\\t\\t\\t\\tconst selector = `[${testId.name}=\"${CSS.escape(testId.value)}\"]`;\\n\\t\\t\\t\\tif (SelectorGenerator.isUniqueSelector(selector, doc, element)) return selector;\\n\\t\\t\\t}\\n\\t\\t\\tconst path = [];\\n\\t\\t\\tlet current = element;\\n\\t\\t\\twhile (current && current.nodeType === Node.ELEMENT_NODE) {\\n\\t\\t\\t\\tlet selector = current.nodeName.toLowerCase();\\n\\t\\t\\t\\tif (current.id && SelectorGenerator.isUniqueId(current.id, doc)) {\\n\\t\\t\\t\\t\\tselector = `#${CSS.escape(current.id)}`;\\n\\t\\t\\t\\t\\tpath.unshift(selector);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tconst classes = SelectorGenerator.getMeaningfulClasses(current);\\n\\t\\t\\t\\tif (classes.length > 0) selector += `.${classes.map((c) => CSS.escape(c)).join(\".\")}`;\\n\\t\\t\\t\\tconst siblings = current.parentElement?.children;\\n\\t\\t\\t\\tif (siblings && siblings.length > 1) {\\n\\t\\t\\t\\t\\tconst index = Array.from(siblings).indexOf(current);\\n\\t\\t\\t\\t\\tif (index > 0 || !SelectorGenerator.isUniqueSelector(selector, current.parentElement)) selector += `:nth-child(${index + 1})`;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tpath.unshift(selector);\\n\\t\\t\\t\\tcurrent = current.parentElement;\\n\\t\\t\\t}\\n\\t\\t\\treturn SelectorGenerator.optimizePath(path, element, doc);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Generate XPath for an element\\n\\t\\t*/\\n\\t\\tstatic generateXPath(element, doc) {\\n\\t\\t\\tconst path = [];\\n\\t\\t\\tlet current = element;\\n\\t\\t\\twhile (current && current.nodeType === Node.ELEMENT_NODE) {\\n\\t\\t\\t\\tconst tagName = current.nodeName.toLowerCase();\\n\\t\\t\\t\\tif (current.id && !current.id.includes(\"\\\\\"\") && SelectorGenerator.isUniqueId(current.id, doc)) {\\n\\t\\t\\t\\t\\tpath.unshift(`*[@id=\"${current.id}\"]`);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tlet xpath = tagName;\\n\\t\\t\\t\\tconst siblings = current.parentElement?.children;\\n\\t\\t\\t\\tif (siblings) {\\n\\t\\t\\t\\t\\tconst sameTagSiblings = Array.from(siblings).filter((s) => s.nodeName.toLowerCase() === tagName);\\n\\t\\t\\t\\t\\tif (sameTagSiblings.length > 1) {\\n\\t\\t\\t\\t\\t\\tconst index = sameTagSiblings.indexOf(current) + 1;\\n\\t\\t\\t\\t\\t\\txpath += `[${index}]`;\\n\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tpath.unshift(xpath);\\n\\t\\t\\t\\tcurrent = current.parentElement;\\n\\t\\t\\t}\\n\\t\\t\\treturn `//${path.join(\"/\")}`;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Generate a text-based selector for buttons and links\\n\\t\\t*/\\n\\t\\tstatic generateTextBasedSelector(element) {\\n\\t\\t\\tconst text = element.textContent?.trim();\\n\\t\\t\\tif (!text || text.length > 50) return void 0;\\n\\t\\t\\tconst tag = element.nodeName.toLowerCase();\\n\\t\\t\\tif ([\\n\\t\\t\\t\\t\"button\",\\n\\t\\t\\t\\t\"a\",\\n\\t\\t\\t\\t\"label\"\\n\\t\\t\\t].includes(tag)) return `${tag}:contains(\"${text.replace(/[\\'\"\\\\\\\\]/g, \"\\\\\\\\$&\")}\")`;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get data-testid or similar attributes\\n\\t\\t*/\\n\\t\\tstatic getDataTestId(element) {\\n\\t\\t\\tfor (const name of [\\n\\t\\t\\t\\t\"data-testid\",\\n\\t\\t\\t\\t\"data-test-id\",\\n\\t\\t\\t\\t\"data-test\",\\n\\t\\t\\t\\t\"data-cy\"\\n\\t\\t\\t]) {\\n\\t\\t\\t\\tconst attribute = element.getAttributeNode(name);\\n\\t\\t\\t\\tif (attribute?.value) return attribute;\\n\\t\\t\\t}\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if an ID is unique in the document\\n\\t\\t*/\\n\\t\\tstatic isUniqueId(id, doc) {\\n\\t\\t\\treturn doc.querySelectorAll(`#${CSS.escape(id)}`).length === 1;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if a selector is unique within a container\\n\\t\\t*/\\n\\t\\tstatic isUniqueSelector(selector, container, element) {\\n\\t\\t\\ttry {\\n\\t\\t\\t\\tconst matches = container.querySelectorAll(selector);\\n\\t\\t\\t\\treturn matches.length === 1 && (element === void 0 || matches[0] === element);\\n\\t\\t\\t} catch {\\n\\t\\t\\t\\treturn false;\\n\\t\\t\\t}\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get meaningful classes (filtering out utility classes)\\n\\t\\t*/\\n\\t\\tstatic getMeaningfulClasses(element) {\\n\\t\\t\\tconst classes = Array.from(element.classList);\\n\\t\\t\\tconst utilityPatterns = [\\n\\t\\t\\t\\t/^(p|m|w|h|text|bg|border|flex|grid|col|row)-/,\\n\\t\\t\\t\\t/^(xs|sm|md|lg|xl|2xl):/,\\n\\t\\t\\t\\t/^(hover|focus|active|disabled|checked):/,\\n\\t\\t\\t\\t/^js-/,\\n\\t\\t\\t\\t/^is-/,\\n\\t\\t\\t\\t/^has-/\\n\\t\\t\\t];\\n\\t\\t\\treturn classes.filter((cls) => {\\n\\t\\t\\t\\tif (cls.length < 3) return false;\\n\\t\\t\\t\\treturn !utilityPatterns.some((pattern) => pattern.test(cls));\\n\\t\\t\\t}).slice(0, 2);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Optimize the selector path by removing unnecessary parts\\n\\t\\t*/\\n\\t\\tstatic optimizePath(path, element, doc) {\\n\\t\\t\\tfor (let i = path.length - 1; i >= 0; i--) {\\n\\t\\t\\t\\tconst shortPath = path.slice(i).join(\" > \");\\n\\t\\t\\t\\ttry {\\n\\t\\t\\t\\t\\tconst matches = doc.querySelectorAll(shortPath);\\n\\t\\t\\t\\t\\tif (matches.length === 1 && matches[0] === element) return shortPath;\\n\\t\\t\\t\\t} catch {}\\n\\t\\t\\t}\\n\\t\\t\\treturn path.join(\" > \");\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get a human-readable path description\\n\\t\\t*/\\n\\t\\tstatic getContextPath(element) {\\n\\t\\t\\tconst path = [];\\n\\t\\t\\tlet current = element;\\n\\t\\t\\tlet depth = 0;\\n\\t\\t\\tconst maxDepth = 5;\\n\\t\\t\\twhile (current && current !== element.ownerDocument?.body && depth < maxDepth) {\\n\\t\\t\\t\\tconst tag = current.nodeName.toLowerCase();\\n\\t\\t\\t\\tlet descriptor = tag;\\n\\t\\t\\t\\tif (current.id) descriptor = `${tag}#${current.id}`;\\n\\t\\t\\t\\telse if (current.className && typeof current.className === \"string\") {\\n\\t\\t\\t\\t\\tconst firstClass = current.className.split(\" \")[0];\\n\\t\\t\\t\\t\\tif (firstClass) descriptor = `${tag}.${firstClass}`;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tconst role = current.getAttribute(\"role\");\\n\\t\\t\\t\\tif (role) descriptor += `[role=\"${role}\"]`;\\n\\t\\t\\t\\tpath.unshift(descriptor);\\n\\t\\t\\t\\tcurrent = current.parentElement;\\n\\t\\t\\t\\tdepth++;\\n\\t\\t\\t}\\n\\t\\t\\treturn path;\\n\\t\\t}\\n\\t};\\n\\t//#endregion\\n\\t//#region src/traversal.ts\\n\\tvar DOMTraversal = class DOMTraversal {\\n\\t\\t/**\\n\\t\\t* Check if a node is a Document.\\n\\t\\t*\\n\\t\\t* `instanceof Document` tests the *calling* realm\\'s constructor, so a\\n\\t\\t* document reached through an iframe (frameSelector) always fails it.\\n\\t\\t* nodeType is realm-independent — use this everywhere instead.\\n\\t\\t*/\\n\\t\\tstatic isDocument(node) {\\n\\t\\t\\treturn node.nodeType === Node.DOCUMENT_NODE;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element is visible\\n\\t\\t*/\\n\\t\\tstatic isVisible(element) {\\n\\t\\t\\tconst rect = element.getBoundingClientRect();\\n\\t\\t\\tconst style = element.ownerDocument?.defaultView?.getComputedStyle(element);\\n\\t\\t\\tif (!style) return false;\\n\\t\\t\\treturn !!(rect.width > 0 && rect.height > 0 && style.display !== \"none\" && style.visibility !== \"hidden\" && style.opacity !== \"0\");\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element is in viewport\\n\\t\\t*/\\n\\t\\tstatic isInViewport(element) {\\n\\t\\t\\tconst rect = element.getBoundingClientRect();\\n\\t\\t\\tconst view = {\\n\\t\\t\\t\\twidth: element.ownerDocument?.defaultView?.innerWidth || 0,\\n\\t\\t\\t\\theight: element.ownerDocument?.defaultView?.innerHeight || 0\\n\\t\\t\\t};\\n\\t\\t\\treturn rect.top < view.height && rect.bottom > 0 && rect.left < view.width && rect.right > 0;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element passes filter criteria\\n\\t\\t*/\\n\\t\\tstatic passesFilter(element, filter) {\\n\\t\\t\\tif (!filter) return true;\\n\\t\\t\\tif (filter.excludeSelectors?.length) {\\n\\t\\t\\t\\tfor (const selector of filter.excludeSelectors) if (element.matches(selector)) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.includeSelectors?.length) {\\n\\t\\t\\t\\tlet matches = false;\\n\\t\\t\\t\\tfor (const selector of filter.includeSelectors) if (element.matches(selector)) {\\n\\t\\t\\t\\t\\tmatches = true;\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tif (!matches) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.tags?.length && !filter.tags.includes(element.tagName.toLowerCase())) return false;\\n\\t\\t\\tconst textContent = element.textContent?.toLowerCase() || \"\";\\n\\t\\t\\tif (filter.textContains?.length) {\\n\\t\\t\\t\\tlet hasText = false;\\n\\t\\t\\t\\tfor (const text of filter.textContains) if (textContent.includes(text.toLowerCase())) {\\n\\t\\t\\t\\t\\thasText = true;\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tif (!hasText) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.textMatches?.length) {\\n\\t\\t\\t\\tlet matches = false;\\n\\t\\t\\t\\tfor (const pattern of filter.textMatches) if (pattern.test(textContent)) {\\n\\t\\t\\t\\t\\tmatches = true;\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tif (!matches) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.hasAttributes?.length) {\\n\\t\\t\\t\\tfor (const attr of filter.hasAttributes) if (!element.hasAttribute(attr)) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.attributeValues) for (const [attr, value] of Object.entries(filter.attributeValues)) {\\n\\t\\t\\t\\tconst attrValue = element.getAttribute(attr);\\n\\t\\t\\t\\tif (!attrValue) return false;\\n\\t\\t\\t\\tif (typeof value === \"string\") {\\n\\t\\t\\t\\t\\tif (attrValue !== value) return false;\\n\\t\\t\\t\\t} else if (value instanceof RegExp) {\\n\\t\\t\\t\\t\\tif (!value.test(attrValue)) return false;\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.withinSelectors?.length) {\\n\\t\\t\\t\\tlet isWithin = false;\\n\\t\\t\\t\\tfor (const selector of filter.withinSelectors) if (element.closest(selector)) {\\n\\t\\t\\t\\t\\tisWithin = true;\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tif (!isWithin) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.interactionTypes?.length) {\\n\\t\\t\\t\\tconst interaction = DOMTraversal.getInteractionInfo(element);\\n\\t\\t\\t\\tlet hasInteraction = false;\\n\\t\\t\\t\\tfor (const type of filter.interactionTypes) if (interaction[type]) {\\n\\t\\t\\t\\t\\thasInteraction = true;\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tif (!hasInteraction) return false;\\n\\t\\t\\t}\\n\\t\\t\\tif (filter.nearText) {\\n\\t\\t\\t\\tconst parent = element.parentElement;\\n\\t\\t\\t\\tif (!parent || !parent.textContent?.toLowerCase().includes(filter.nearText.toLowerCase())) return false;\\n\\t\\t\\t}\\n\\t\\t\\treturn true;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract element information\\n\\t\\t*/\\n\\t\\tstatic extractElement(element, options, depth = 0) {\\n\\t\\t\\tif (options.maxDepth !== void 0 && depth > options.maxDepth) return null;\\n\\t\\t\\tif (!options.includeHidden && !DOMTraversal.isVisible(element)) return null;\\n\\t\\t\\tif (options.viewportOnly && !DOMTraversal.isInViewport(element)) return null;\\n\\t\\t\\tif (!DOMTraversal.passesFilter(element, options.filter)) return null;\\n\\t\\t\\tconst extracted = {\\n\\t\\t\\t\\ttag: element.tagName.toLowerCase(),\\n\\t\\t\\t\\ttext: DOMTraversal.getElementText(element, options),\\n\\t\\t\\t\\tselector: SelectorGenerator.generateSelectors(element),\\n\\t\\t\\t\\tattributes: DOMTraversal.getRelevantAttributes(element, options),\\n\\t\\t\\t\\tcontext: DOMTraversal.getElementContext(element),\\n\\t\\t\\t\\tinteraction: DOMTraversal.getInteractionInfo(element)\\n\\t\\t\\t};\\n\\t\\t\\tif (options.mode === \"full\" && DOMTraversal.isSemanticContainer(element)) {\\n\\t\\t\\t\\tconst children = [];\\n\\t\\t\\t\\tif (options.includeShadowDOM && element.shadowRoot) {\\n\\t\\t\\t\\t\\tconst shadowChildren = DOMTraversal.extractChildren(element.shadowRoot, options, depth + 1);\\n\\t\\t\\t\\t\\tchildren.push(...shadowChildren);\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tconst regularChildren = DOMTraversal.extractChildren(element, options, depth + 1);\\n\\t\\t\\t\\tchildren.push(...regularChildren);\\n\\t\\t\\t\\tif (children.length > 0) extracted.children = children;\\n\\t\\t\\t}\\n\\t\\t\\treturn extracted;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract children elements\\n\\t\\t*/\\n\\t\\tstatic extractChildren(container, options, depth) {\\n\\t\\t\\tconst children = [];\\n\\t\\t\\tfor (const child of Array.from(container.children)) {\\n\\t\\t\\t\\tconst extracted = DOMTraversal.extractElement(child, options, depth);\\n\\t\\t\\t\\tif (extracted) children.push(extracted);\\n\\t\\t\\t}\\n\\t\\t\\treturn children;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get relevant attributes for an element\\n\\t\\t*/\\n\\t\\tstatic getRelevantAttributes(element, options) {\\n\\t\\t\\tconst relevant = [\\n\\t\\t\\t\\t\"id\",\\n\\t\\t\\t\\t\"class\",\\n\\t\\t\\t\\t\"name\",\\n\\t\\t\\t\\t\"type\",\\n\\t\\t\\t\\t\"value\",\\n\\t\\t\\t\\t\"placeholder\",\\n\\t\\t\\t\\t\"href\",\\n\\t\\t\\t\\t\"src\",\\n\\t\\t\\t\\t\"alt\",\\n\\t\\t\\t\\t\"title\",\\n\\t\\t\\t\\t\"action\",\\n\\t\\t\\t\\t\"method\",\\n\\t\\t\\t\\t\"aria-label\",\\n\\t\\t\\t\\t\"aria-describedby\",\\n\\t\\t\\t\\t\"aria-controls\",\\n\\t\\t\\t\\t\"role\",\\n\\t\\t\\t\\t\"disabled\",\\n\\t\\t\\t\\t\"readonly\",\\n\\t\\t\\t\\t\"required\",\\n\\t\\t\\t\\t\"checked\",\\n\\t\\t\\t\\t\"min\",\\n\\t\\t\\t\\t\"max\",\\n\\t\\t\\t\\t\"pattern\",\\n\\t\\t\\t\\t\"step\",\\n\\t\\t\\t\\t\"autocomplete\",\\n\\t\\t\\t\\t\"data-testid\",\\n\\t\\t\\t\\t\"data-test\",\\n\\t\\t\\t\\t\"data-cy\"\\n\\t\\t\\t];\\n\\t\\t\\tconst attributes = {};\\n\\t\\t\\tconst attrTruncate = options.attributeTruncateLength ?? 100;\\n\\t\\t\\tconst dataAttrTruncate = options.dataAttributeTruncateLength ?? 50;\\n\\t\\t\\tfor (const attr of relevant) {\\n\\t\\t\\t\\tconst value = element.getAttribute(attr);\\n\\t\\t\\t\\tif (value) attributes[attr] = value.length > attrTruncate ? `${value.substring(0, attrTruncate)}...` : value;\\n\\t\\t\\t}\\n\\t\\t\\tfor (const attr of element.attributes) if (attr.name.startsWith(\"data-\") && !relevant.includes(attr.name)) attributes[attr.name] = attr.value.length > dataAttrTruncate ? `${attr.value.substring(0, dataAttrTruncate)}...` : attr.value;\\n\\t\\t\\treturn attributes;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get element context information\\n\\t\\t*/\\n\\t\\tstatic getElementContext(element) {\\n\\t\\t\\tconst context = { parentChain: SelectorGenerator.getContextPath(element) };\\n\\t\\t\\tconst form = element.closest(\"form\");\\n\\t\\t\\tif (form) context.nearestForm = SelectorGenerator.generateSelectors(form).css;\\n\\t\\t\\tconst section = element.closest(\"section, [role=\\\\\"region\\\\\"]\");\\n\\t\\t\\tif (section) context.nearestSection = SelectorGenerator.generateSelectors(section).css;\\n\\t\\t\\tconst main = element.closest(\"main, [role=\\\\\"main\\\\\"]\");\\n\\t\\t\\tif (main) context.nearestMain = SelectorGenerator.generateSelectors(main).css;\\n\\t\\t\\tconst nav = element.closest(\"nav, [role=\\\\\"navigation\\\\\"]\");\\n\\t\\t\\tif (nav) context.nearestNav = SelectorGenerator.generateSelectors(nav).css;\\n\\t\\t\\treturn context;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get interaction information for an element (compact format)\\n\\t\\t*/\\n\\t\\tstatic getInteractionInfo(element) {\\n\\t\\t\\tconst htmlElement = element;\\n\\t\\t\\tconst interaction = {};\\n\\t\\t\\tif (htmlElement.onclick || element.getAttribute(\"onclick\") || element.matches(\"button, a[href], [role=\\\\\"button\\\\\"], [tabindex]:not([tabindex=\\\\\"-1\\\\\"])\")) interaction.click = true;\\n\\t\\t\\tif (htmlElement.onchange || element.getAttribute(\"onchange\") || element.matches(\"input, select, textarea\")) interaction.change = true;\\n\\t\\t\\tif (htmlElement.onsubmit || element.getAttribute(\"onsubmit\") || element.matches(\"form\")) interaction.submit = true;\\n\\t\\t\\tif (element.matches(\"a[href], button[type=\\\\\"submit\\\\\"]\")) interaction.nav = true;\\n\\t\\t\\tif (element.hasAttribute(\"disabled\") || element.getAttribute(\"aria-disabled\") === \"true\") interaction.disabled = true;\\n\\t\\t\\tif (!DOMTraversal.isVisible(element)) interaction.hidden = true;\\n\\t\\t\\tconst ariaRole = element.getAttribute(\"role\");\\n\\t\\t\\tif (ariaRole) interaction.role = ariaRole;\\n\\t\\t\\tif (element.matches(\"input, textarea, select, button\")) {\\n\\t\\t\\t\\tconst form = element.form || element.closest(\"form\");\\n\\t\\t\\t\\tif (form) interaction.form = SelectorGenerator.generateSelectors(form).css;\\n\\t\\t\\t}\\n\\t\\t\\treturn interaction;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get text content of an element (limited length)\\n\\t\\t*/\\n\\t\\tstatic getElementText(element, options) {\\n\\t\\t\\tif (element.matches(\"input, textarea\")) {\\n\\t\\t\\t\\tconst input = element;\\n\\t\\t\\t\\treturn input.value || input.placeholder || \"\";\\n\\t\\t\\t}\\n\\t\\t\\tif (element.matches(\"img\")) return element.alt || \"\";\\n\\t\\t\\tconst text = element.textContent?.trim() || \"\";\\n\\t\\t\\tconst maxLength = options?.textTruncateLength;\\n\\t\\t\\tif (maxLength && text.length > maxLength) return `${text.substring(0, maxLength)}...`;\\n\\t\\t\\treturn text;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element is a semantic container\\n\\t\\t*/\\n\\t\\tstatic isSemanticContainer(element) {\\n\\t\\t\\treturn element.matches(\"article, section, nav, aside, main, header, footer, form, table, ul, ol, dl, figure, details, dialog, [role=\\\\\"region\\\\\"], [role=\\\\\"navigation\\\\\"], [role=\\\\\"main\\\\\"], [role=\\\\\"complementary\\\\\"]\");\\n\\t\\t}\\n\\t};\\n\\t//#endregion\\n\\t//#region src/smart-dom-reader.ts\\n\\t/**\\n\\t* Smart DOM Reader - Full Extraction Approach\\n\\t*\\n\\t* This class provides complete DOM extraction in a single pass.\\n\\t* Use this when you need all information upfront and have sufficient\\n\\t* token budget for processing the complete output.\\n\\t*\\n\\t* Features:\\n\\t* - Single-pass extraction of all elements\\n\\t* - Two modes: \\'interactive\\' (UI elements) or \\'full\\' (includes content)\\n\\t* - Efficient for automation and testing scenarios\\n\\t* - Returns complete structured data immediately\\n\\t*/\\n\\tvar SmartDOMReader = class SmartDOMReader {\\n\\t\\toptions;\\n\\t\\tconstructor(options = {}) {\\n\\t\\t\\tthis.options = {\\n\\t\\t\\t\\tmode: options.mode || \"interactive\",\\n\\t\\t\\t\\tmaxDepth: options.maxDepth ?? 5,\\n\\t\\t\\t\\tincludeHidden: options.includeHidden || false,\\n\\t\\t\\t\\tincludeShadowDOM: options.includeShadowDOM ?? true,\\n\\t\\t\\t\\tincludeIframes: options.includeIframes || false,\\n\\t\\t\\t\\tviewportOnly: options.viewportOnly || false,\\n\\t\\t\\t\\tmainContentOnly: options.mainContentOnly || false,\\n\\t\\t\\t\\tcustomSelectors: options.customSelectors || [],\\n\\t\\t\\t\\t...options.attributeTruncateLength !== void 0 && { attributeTruncateLength: options.attributeTruncateLength },\\n\\t\\t\\t\\t...options.dataAttributeTruncateLength !== void 0 && { dataAttributeTruncateLength: options.dataAttributeTruncateLength },\\n\\t\\t\\t\\t...options.textTruncateLength !== void 0 && { textTruncateLength: options.textTruncateLength },\\n\\t\\t\\t\\t...options.filter !== void 0 && { filter: options.filter }\\n\\t\\t\\t};\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Main extraction method - extracts all data in one pass\\n\\t\\t* @param rootElement The document or element to extract from\\n\\t\\t* @param runtimeOptions Options to override constructor options\\n\\t\\t*/\\n\\t\\textract(rootElement = document, runtimeOptions) {\\n\\t\\t\\tconst startTime = Date.now();\\n\\t\\t\\tconst rootIsDocument = DOMTraversal.isDocument(rootElement);\\n\\t\\t\\tconst doc = rootIsDocument ? rootElement : rootElement.ownerDocument;\\n\\t\\t\\tconst options = {\\n\\t\\t\\t\\t...this.options,\\n\\t\\t\\t\\t...runtimeOptions\\n\\t\\t\\t};\\n\\t\\t\\tlet container = rootIsDocument ? doc : rootElement;\\n\\t\\t\\tif (options.mainContentOnly && rootIsDocument) container = ContentDetection.findMainContent(doc);\\n\\t\\t\\tconst pageState = this.extractPageState(doc);\\n\\t\\t\\tconst landmarks = this.extractLandmarks(doc);\\n\\t\\t\\tconst interactive = this.extractInteractiveElements(container, options);\\n\\t\\t\\tconst result = {\\n\\t\\t\\t\\tmode: options.mode,\\n\\t\\t\\t\\ttimestamp: startTime,\\n\\t\\t\\t\\tpage: pageState,\\n\\t\\t\\t\\tlandmarks,\\n\\t\\t\\t\\tinteractive\\n\\t\\t\\t};\\n\\t\\t\\tif (options.mode === \"full\") {\\n\\t\\t\\t\\tconst semantic = this.extractSemanticElements(container, options);\\n\\t\\t\\t\\tconst metadata = this.extractMetadata(doc, container, options);\\n\\t\\t\\t\\treturn {\\n\\t\\t\\t\\t\\t...result,\\n\\t\\t\\t\\t\\tsemantic,\\n\\t\\t\\t\\t\\tmetadata\\n\\t\\t\\t\\t};\\n\\t\\t\\t}\\n\\t\\t\\treturn result;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract page state information\\n\\t\\t*/\\n\\t\\textractPageState(doc) {\\n\\t\\t\\tconst hasFocus = this.getFocusedElement(doc);\\n\\t\\t\\treturn {\\n\\t\\t\\t\\turl: doc.location?.href || \"\",\\n\\t\\t\\t\\ttitle: doc.title || \"\",\\n\\t\\t\\t\\thasErrors: this.detectErrors(doc),\\n\\t\\t\\t\\tisLoading: this.detectLoading(doc),\\n\\t\\t\\t\\thasModals: this.detectModals(doc),\\n\\t\\t\\t\\t...hasFocus !== void 0 && { hasFocus }\\n\\t\\t\\t};\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract page landmarks\\n\\t\\t*/\\n\\t\\textractLandmarks(doc) {\\n\\t\\t\\tconst detected = ContentDetection.detectLandmarks(doc);\\n\\t\\t\\treturn {\\n\\t\\t\\t\\tnavigation: this.elementsToSelectors(detected.navigation || []),\\n\\t\\t\\t\\tmain: this.elementsToSelectors(detected.main || []),\\n\\t\\t\\t\\tforms: this.elementsToSelectors(detected.form || []),\\n\\t\\t\\t\\theaders: this.elementsToSelectors(detected.banner || []),\\n\\t\\t\\t\\tfooters: this.elementsToSelectors(detected.contentinfo || []),\\n\\t\\t\\t\\tarticles: this.elementsToSelectors(detected.region || []),\\n\\t\\t\\t\\tsections: this.elementsToSelectors(detected.region || [])\\n\\t\\t\\t};\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Convert elements to selector strings\\n\\t\\t*/\\n\\t\\telementsToSelectors(elements) {\\n\\t\\t\\treturn elements.map((el) => SelectorGenerator.generateSelectors(el).css);\\n\\t\\t}\\n\\t\\tquerySelectorAll(container, selector, includeShadowDOM) {\\n\\t\\t\\tconst matches = [...container.querySelectorAll(selector)];\\n\\t\\t\\tif (!includeShadowDOM) return matches;\\n\\t\\t\\tif (\"shadowRoot\" in container && container.shadowRoot) matches.push(...this.querySelectorAll(container.shadowRoot, selector, true));\\n\\t\\t\\tfor (const element of container.querySelectorAll(\"*\")) if (element.shadowRoot) matches.push(...this.querySelectorAll(element.shadowRoot, selector, true));\\n\\t\\t\\treturn matches;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract every element matching a selector.\\n\\t\\t*\\n\\t\\t* DOMTraversal.extractElement already applies the visibility, viewport and\\n\\t\\t* filter guards, so pre-filtering here would just repeat its\\n\\t\\t* getBoundingClientRect/getComputedStyle work for every element.\\n\\t\\t*/\\n\\t\\textractAll(container, selector, options) {\\n\\t\\t\\tconst extracted = [];\\n\\t\\t\\tfor (const el of this.querySelectorAll(container, selector, options.includeShadowDOM)) {\\n\\t\\t\\t\\tconst element = DOMTraversal.extractElement(el, options);\\n\\t\\t\\t\\tif (element) extracted.push(element);\\n\\t\\t\\t}\\n\\t\\t\\treturn extracted;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract interactive elements\\n\\t\\t*/\\n\\t\\textractInteractiveElements(container, options) {\\n\\t\\t\\tconst clickable = [];\\n\\t\\t\\tfor (const selector of options.customSelectors ?? []) clickable.push(...this.extractAll(container, selector, options));\\n\\t\\t\\treturn {\\n\\t\\t\\t\\tbuttons: this.extractAll(container, \"button, [role=\\\\\"button\\\\\"], input[type=\\\\\"button\\\\\"], input[type=\\\\\"submit\\\\\"]\", options),\\n\\t\\t\\t\\tlinks: this.extractAll(container, \"a[href]\", options),\\n\\t\\t\\t\\tinputs: this.extractAll(container, \"input:not([type=\\\\\"button\\\\\"]):not([type=\\\\\"submit\\\\\"]), textarea, select\", options),\\n\\t\\t\\t\\tforms: this.extractForms(container, options),\\n\\t\\t\\t\\tclickable\\n\\t\\t\\t};\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract form information\\n\\t\\t*/\\n\\t\\textractForms(container, options) {\\n\\t\\t\\tconst forms = [];\\n\\t\\t\\tthis.querySelectorAll(container, \"form\", options.includeShadowDOM).forEach((form) => {\\n\\t\\t\\t\\tif (!this.shouldIncludeElement(form, options)) return;\\n\\t\\t\\t\\tconst action = form.getAttribute(\"action\");\\n\\t\\t\\t\\tconst method = form.getAttribute(\"method\");\\n\\t\\t\\t\\tconst formInfo = {\\n\\t\\t\\t\\t\\tselector: SelectorGenerator.generateSelectors(form).css,\\n\\t\\t\\t\\t\\tinputs: this.extractAll(form, \"input:not([type=\\\\\"button\\\\\"]):not([type=\\\\\"submit\\\\\"]), textarea, select\", options),\\n\\t\\t\\t\\t\\tbuttons: this.extractAll(form, \"button, input[type=\\\\\"button\\\\\"], input[type=\\\\\"submit\\\\\"]\", options)\\n\\t\\t\\t\\t};\\n\\t\\t\\t\\tif (action) formInfo.action = action;\\n\\t\\t\\t\\tif (method) formInfo.method = method;\\n\\t\\t\\t\\tforms.push(formInfo);\\n\\t\\t\\t});\\n\\t\\t\\treturn forms;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract semantic elements (full mode only)\\n\\t\\t*/\\n\\t\\textractSemanticElements(container, options) {\\n\\t\\t\\treturn {\\n\\t\\t\\t\\theadings: this.extractAll(container, \"h1, h2, h3, h4, h5, h6\", options),\\n\\t\\t\\t\\timages: this.extractAll(container, \"img\", options),\\n\\t\\t\\t\\ttables: this.extractAll(container, \"table\", options),\\n\\t\\t\\t\\tlists: this.extractAll(container, \"ul, ol\", options),\\n\\t\\t\\t\\tarticles: this.extractAll(container, \"article, [role=\\\\\"article\\\\\"]\", options)\\n\\t\\t\\t};\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract metadata\\n\\t\\t*/\\n\\t\\textractMetadata(doc, container, options) {\\n\\t\\t\\tconst allElements = this.querySelectorAll(container, \"*\", options.includeShadowDOM);\\n\\t\\t\\tconst extractedElements = this.querySelectorAll(container, \"button, a, input, textarea, select, h1, h2, h3, h4, h5, h6, img, table, ul, ol, article\", options.includeShadowDOM).length;\\n\\t\\t\\tconst metadata = {\\n\\t\\t\\t\\ttotalElements: allElements.length,\\n\\t\\t\\t\\textractedElements\\n\\t\\t\\t};\\n\\t\\t\\tif (options.mainContentOnly && !DOMTraversal.isDocument(container)) metadata.mainContent = SelectorGenerator.generateSelectors(container).css;\\n\\t\\t\\tconst language = doc.documentElement.getAttribute(\"lang\");\\n\\t\\t\\tif (language) metadata.language = language;\\n\\t\\t\\treturn metadata;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Check if element should be included based on options\\n\\t\\t*/\\n\\t\\tshouldIncludeElement(element, options) {\\n\\t\\t\\tif (!options.includeHidden && !DOMTraversal.isVisible(element)) return false;\\n\\t\\t\\tif (options.viewportOnly && !DOMTraversal.isInViewport(element)) return false;\\n\\t\\t\\tif (options.filter && !DOMTraversal.passesFilter(element, options.filter)) return false;\\n\\t\\t\\treturn true;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Detect errors on the page\\n\\t\\t*/\\n\\t\\tdetectErrors(doc) {\\n\\t\\t\\treturn [\\n\\t\\t\\t\\t\".error\",\\n\\t\\t\\t\\t\".alert-danger\",\\n\\t\\t\\t\\t\"[role=\\\\\"alert\\\\\"]\",\\n\\t\\t\\t\\t\".error-message\"\\n\\t\\t\\t].some((sel) => {\\n\\t\\t\\t\\tconst element = doc.querySelector(sel);\\n\\t\\t\\t\\treturn element ? DOMTraversal.isVisible(element) : false;\\n\\t\\t\\t});\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Detect if page is loading\\n\\t\\t*/\\n\\t\\tdetectLoading(doc) {\\n\\t\\t\\treturn [\\n\\t\\t\\t\\t\".loading\",\\n\\t\\t\\t\\t\".spinner\",\\n\\t\\t\\t\\t\"[aria-busy=\\\\\"true\\\\\"]\",\\n\\t\\t\\t\\t\".loader\"\\n\\t\\t\\t].some((sel) => {\\n\\t\\t\\t\\tconst element = doc.querySelector(sel);\\n\\t\\t\\t\\treturn element ? DOMTraversal.isVisible(element) : false;\\n\\t\\t\\t});\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Detect modal dialogs\\n\\t\\t*/\\n\\t\\tdetectModals(doc) {\\n\\t\\t\\treturn [\\n\\t\\t\\t\\t\"[role=\\\\\"dialog\\\\\"]\",\\n\\t\\t\\t\\t\".modal\",\\n\\t\\t\\t\\t\".popup\",\\n\\t\\t\\t\\t\".overlay\"\\n\\t\\t\\t].some((sel) => {\\n\\t\\t\\t\\tconst element = doc.querySelector(sel);\\n\\t\\t\\t\\treturn element ? DOMTraversal.isVisible(element) : false;\\n\\t\\t\\t});\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get currently focused element\\n\\t\\t*/\\n\\t\\tgetFocusedElement(doc) {\\n\\t\\t\\tconst focused = doc.activeElement;\\n\\t\\t\\tif (focused && focused !== doc.body) return SelectorGenerator.generateSelectors(focused).css;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Quick extraction for interactive elements only\\n\\t\\t* @param doc The document to extract from\\n\\t\\t* @param options Extraction options\\n\\t\\t*/\\n\\t\\tstatic extractInteractive(doc, options = {}) {\\n\\t\\t\\treturn new SmartDOMReader({\\n\\t\\t\\t\\t...options,\\n\\t\\t\\t\\tmode: \"interactive\"\\n\\t\\t\\t}).extract(doc);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Quick extraction for full content\\n\\t\\t* @param doc The document to extract from\\n\\t\\t* @param options Extraction options\\n\\t\\t*/\\n\\t\\tstatic extractFull(doc, options = {}) {\\n\\t\\t\\treturn new SmartDOMReader({\\n\\t\\t\\t\\t...options,\\n\\t\\t\\t\\tmode: \"full\"\\n\\t\\t\\t}).extract(doc);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract from a specific element\\n\\t\\t* @param element The element to extract from\\n\\t\\t* @param mode The extraction mode\\n\\t\\t* @param options Additional options\\n\\t\\t*/\\n\\t\\tstatic extractFromElement(element, mode = \"interactive\", options = {}) {\\n\\t\\t\\treturn new SmartDOMReader({\\n\\t\\t\\t\\t...options,\\n\\t\\t\\t\\tmode\\n\\t\\t\\t}).extract(element);\\n\\t\\t}\\n\\t};\\n\\t//#endregion\\n\\t//#region src/markdown-formatter.ts\\n\\tfunction truncate(text, len) {\\n\\t\\tconst t = (text ?? \"\").trim();\\n\\t\\tif (!len || t.length <= len) return t;\\n\\t\\tconst keywords = [\\n\\t\\t\\t\"login\",\\n\\t\\t\\t\"log in\",\\n\\t\\t\\t\"sign in\",\\n\\t\\t\\t\"sign up\",\\n\\t\\t\\t\"submit\",\\n\\t\\t\\t\"search\",\\n\\t\\t\\t\"filter\",\\n\\t\\t\\t\"add to cart\",\\n\\t\\t\\t\"next\",\\n\\t\\t\\t\"continue\"\\n\\t\\t];\\n\\t\\tconst lower = t.toLowerCase();\\n\\t\\tconst hit = keywords.map((k) => ({\\n\\t\\t\\tk,\\n\\t\\t\\ti: lower.indexOf(k)\\n\\t\\t})).find((x) => x.i > -1);\\n\\t\\tconst head = Math.max(0, Math.floor(len * .66));\\n\\t\\tif (hit && hit.i > head) {\\n\\t\\t\\tconst tailWindow = Math.max(12, len - head - 5);\\n\\t\\t\\tconst start = Math.max(0, hit.i - Math.floor(tailWindow / 2));\\n\\t\\t\\tconst end = Math.min(t.length, start + tailWindow);\\n\\t\\t\\treturn `${t.slice(0, head).trimEnd()} … ${t.slice(start, end).trim()}…`;\\n\\t\\t}\\n\\t\\tconst slice = t.slice(0, len);\\n\\t\\tconst lastSpace = slice.lastIndexOf(\" \");\\n\\t\\treturn `${lastSpace > 32 ? slice.slice(0, lastSpace) : slice}…`;\\n\\t}\\n\\tfunction hashId(input) {\\n\\t\\tlet h = 5381;\\n\\t\\tfor (let i = 0; i < input.length; i++) h = h * 33 ^ input.charCodeAt(i);\\n\\t\\treturn `sec-${(h >>> 0).toString(36)}`;\\n\\t}\\n\\tfunction iconForRegion(key) {\\n\\t\\tswitch (key) {\\n\\t\\t\\tcase \"header\": return \"🧭\";\\n\\t\\t\\tcase \"navigation\": return \"📑\";\\n\\t\\t\\tcase \"main\": return \"📄\";\\n\\t\\t\\tcase \"sections\": return \"🗂️\";\\n\\t\\t\\tcase \"sidebar\": return \"📚\";\\n\\t\\t\\tcase \"footer\": return \"🔻\";\\n\\t\\t\\tcase \"modals\": return \"💬\";\\n\\t\\t\\tdefault: return \"🔹\";\\n\\t\\t}\\n\\t}\\n\\tfunction elementLine(el, opts) {\\n\\t\\tconst txt = truncate(el.text || el.attributes?.ariaLabel, opts?.maxTextLength ?? 80);\\n\\t\\tconst sel = el.selector?.css || \"\";\\n\\t\\tconst tag = el.tag.toLowerCase();\\n\\t\\tconst action = el.interaction?.submit ? \"submit\" : el.interaction?.click ? \"click\" : el.interaction?.change ? \"change\" : void 0;\\n\\t\\tconst actionText = action ? ` (${action})` : \"\";\\n\\t\\treturn `- ${tag.toUpperCase()}: ${txt || \"(no text)\"} → \\\\`${sel}\\\\`${actionText}`;\\n\\t}\\n\\tfunction selectorQualitySummary(inter) {\\n\\t\\tconst all = [];\\n\\t\\tall.push(...inter.buttons.map((e) => e.selector?.css || \"\"));\\n\\t\\tall.push(...inter.links.map((e) => e.selector?.css || \"\"));\\n\\t\\tall.push(...inter.inputs.map((e) => e.selector?.css || \"\"));\\n\\t\\tall.push(...inter.clickable.map((e) => e.selector?.css || \"\"));\\n\\t\\tconst total = all.length || 1;\\n\\t\\tconst idCount = all.filter((s) => s.startsWith(\"#\")).length;\\n\\t\\tconst testIdCount = all.filter((s) => /\\\\[data-testid=/.test(s)).length;\\n\\t\\tconst nthCount = all.filter((s) => /:nth-child\\\\(/.test(s)).length;\\n\\t\\tconst stable = idCount + testIdCount;\\n\\t\\treturn `Selector quality: ${Math.round(stable / total * 100)}% stable (ID/data-testid), ${Math.round(nthCount / total * 100)}% structural (:nth-child)`;\\n\\t}\\n\\tfunction renderInteractive(inter, opts) {\\n\\t\\tconst parts = [];\\n\\t\\tconst limit = (arr) => typeof opts?.maxElements === \"number\" ? arr.slice(0, opts.maxElements) : arr;\\n\\t\\tif (inter.buttons.length) {\\n\\t\\t\\tparts.push(\"Buttons:\");\\n\\t\\t\\tfor (const el of limit(inter.buttons)) parts.push(elementLine(el, opts));\\n\\t\\t}\\n\\t\\tif (inter.links.length) {\\n\\t\\t\\tparts.push(\"Links:\");\\n\\t\\t\\tfor (const el of limit(inter.links)) parts.push(elementLine(el, opts));\\n\\t\\t}\\n\\t\\tif (inter.inputs.length) {\\n\\t\\t\\tparts.push(\"Inputs:\");\\n\\t\\t\\tfor (const el of limit(inter.inputs)) parts.push(elementLine(el, opts));\\n\\t\\t}\\n\\t\\tif (inter.clickable.length) {\\n\\t\\t\\tparts.push(\"Other Clickable:\");\\n\\t\\t\\tfor (const el of limit(inter.clickable)) parts.push(elementLine(el, opts));\\n\\t\\t}\\n\\t\\tif (inter.forms.length) {\\n\\t\\t\\tparts.push(\"Forms:\");\\n\\t\\t\\tfor (const f of limit(inter.forms)) parts.push(`- FORM: action=${f.action ?? \"-\"} method=${f.method ?? \"-\"} → \\\\`${f.selector}\\\\``);\\n\\t\\t}\\n\\t\\treturn parts.join(\"\\\\n\");\\n\\t}\\n\\tfunction renderRegionInfo(region) {\\n\\t\\tconst icon = iconForRegion(\"region\");\\n\\t\\tconst id = hashId(`${region.selector}|${region.label ?? \"\"}|${region.role ?? \"\"}`);\\n\\t\\tconst label = region.label ? ` ${region.label}` : \"\";\\n\\t\\tconst stats = [];\\n\\t\\tif (region.buttonCount) stats.push(`${region.buttonCount} buttons`);\\n\\t\\tif (region.linkCount) stats.push(`${region.linkCount} links`);\\n\\t\\tif (region.inputCount) stats.push(`${region.inputCount} inputs`);\\n\\t\\tif (region.textPreview) stats.push(`“${truncate(region.textPreview, 80)}”`);\\n\\t\\tconst statsLine = stats.length ? ` — ${stats.join(\", \")}` : \"\";\\n\\t\\treturn `${icon} ${label} → \\\\`${region.selector}\\\\` [${id}]${statsLine}`;\\n\\t}\\n\\tfunction wrapXml(body, meta, type = \"section\") {\\n\\t\\treturn `<page ${[meta?.title ? `title=\"${escapeXml(meta.title)}\"` : null, meta?.url ? `url=\"${escapeXml(meta.url)}\"` : null].filter(Boolean).join(\" \")}>\\\\n <${type}><![CDATA[\\\\n${body}\\\\n]]></${type}>\\\\n</page>`;\\n\\t}\\n\\tfunction escapeXml(s) {\\n\\t\\treturn s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\");\\n\\t}\\n\\tvar MarkdownFormatter = class {\\n\\t\\tstatic structure(overview, _opts = {}, meta) {\\n\\t\\t\\tconst lines = [];\\n\\t\\t\\tlines.push(\"# Page Outline\");\\n\\t\\t\\tif (meta?.title || meta?.url) {\\n\\t\\t\\t\\tlines.push(`Title: ${meta?.title ?? \"\"}`.trim());\\n\\t\\t\\t\\tlines.push(`URL: ${meta?.url ?? \"\"}`.trim());\\n\\t\\t\\t}\\n\\t\\t\\tlines.push(\"\");\\n\\t\\t\\tconst regions = overview.regions;\\n\\t\\t\\tconst entries = [\\n\\t\\t\\t\\t[\"header\", regions.header],\\n\\t\\t\\t\\t[\"navigation\", regions.navigation],\\n\\t\\t\\t\\t[\"main\", regions.main],\\n\\t\\t\\t\\t[\"sections\", regions.sections],\\n\\t\\t\\t\\t[\"sidebar\", regions.sidebar],\\n\\t\\t\\t\\t[\"footer\", regions.footer],\\n\\t\\t\\t\\t[\"modals\", regions.modals]\\n\\t\\t\\t];\\n\\t\\t\\tfor (const [key, value] of entries) {\\n\\t\\t\\t\\tif (!value) continue;\\n\\t\\t\\t\\tconst icon = iconForRegion(key);\\n\\t\\t\\t\\tif (Array.isArray(value)) {\\n\\t\\t\\t\\t\\tif (!value.length) continue;\\n\\t\\t\\t\\t\\tlines.push(`## ${icon} ${capitalize(key)}`);\\n\\t\\t\\t\\t\\tfor (const region of value) lines.push(renderRegionInfo(region));\\n\\t\\t\\t\\t} else {\\n\\t\\t\\t\\t\\tlines.push(`## ${icon} ${capitalize(key)}`);\\n\\t\\t\\t\\t\\tlines.push(renderRegionInfo(value));\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tif (overview.suggestions?.length) {\\n\\t\\t\\t\\tlines.push(\"## Suggestions\");\\n\\t\\t\\t\\tfor (const s of overview.suggestions) lines.push(`- ${s}`);\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tlines.push(\"Next: choose a region (by selector or [sectionId]) and call dom_extract_region for actionable details.\");\\n\\t\\t\\treturn wrapXml(lines.join(\"\\\\n\"), meta, \"outline\");\\n\\t\\t}\\n\\t\\tstatic region(result, opts = {}, meta) {\\n\\t\\t\\tconst lines = [];\\n\\t\\t\\tlines.push(\"# Region Details\");\\n\\t\\t\\tif (meta?.title || meta?.url) {\\n\\t\\t\\t\\tlines.push(`Title: ${meta?.title ?? \"\"}`.trim());\\n\\t\\t\\t\\tlines.push(`URL: ${meta?.url ?? \"\"}`.trim());\\n\\t\\t\\t}\\n\\t\\t\\tlines.push(\"\");\\n\\t\\t\\tconst inter = result.interactive;\\n\\t\\t\\tif (result.page) {\\n\\t\\t\\t\\tconst ps = [\\n\\t\\t\\t\\t\\tresult.page.hasErrors ? \"errors: yes\" : \"errors: no\",\\n\\t\\t\\t\\t\\tresult.page.isLoading ? \"loading: yes\" : \"loading: no\",\\n\\t\\t\\t\\t\\tresult.page.hasModals ? \"modals: yes\" : \"modals: no\"\\n\\t\\t\\t\\t];\\n\\t\\t\\t\\tlines.push(`Page state: ${ps.join(\", \")}`);\\n\\t\\t\\t}\\n\\t\\t\\tconst summary = [];\\n\\t\\t\\tconst count = (arr) => arr ? arr.length : 0;\\n\\t\\t\\tsummary.push(`${count(inter.buttons)} buttons`);\\n\\t\\t\\tsummary.push(`${count(inter.links)} links`);\\n\\t\\t\\tsummary.push(`${count(inter.inputs)} inputs`);\\n\\t\\t\\tif (inter.forms?.length) summary.push(`${count(inter.forms)} forms`);\\n\\t\\t\\tlines.push(`Summary: ${summary.join(\", \")}`);\\n\\t\\t\\tlines.push(selectorQualitySummary(inter));\\n\\t\\t\\tlines.push(\"\");\\n\\t\\t\\tlines.push(renderInteractive(inter, opts));\\n\\t\\t\\tlines.push(\"\");\\n\\t\\t\\tlines.push(\"Next: write a script using the most stable selectors above. If selectors look unstable, rerun dom_extract_region with higher detail or call dom_extract_content for text context.\");\\n\\t\\t\\treturn wrapXml(lines.join(\"\\\\n\"), meta, \"section\");\\n\\t\\t}\\n\\t\\tstatic content(content, opts = {}, meta) {\\n\\t\\t\\tconst lines = [];\\n\\t\\t\\tlines.push(\"# Content\");\\n\\t\\t\\tlines.push(`Selector: \\\\`${content.selector}\\\\``);\\n\\t\\t\\tlines.push(\"\");\\n\\t\\t\\tif (content.text.headings?.length) {\\n\\t\\t\\t\\tlines.push(\"Headings:\");\\n\\t\\t\\t\\tfor (const h of content.text.headings) lines.push(`- H${h.level}: ${truncate(h.text, opts.maxTextLength ?? 120)}`);\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tif (content.text.paragraphs?.length) {\\n\\t\\t\\t\\tconst limit = typeof opts.maxElements === \"number\" ? opts.maxElements : content.text.paragraphs.length;\\n\\t\\t\\t\\tlines.push(\"Paragraphs:\");\\n\\t\\t\\t\\tfor (const p of content.text.paragraphs.slice(0, limit)) lines.push(`- ${truncate(p, opts.maxTextLength ?? 200)}`);\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tif (content.text.lists?.length) {\\n\\t\\t\\t\\tlines.push(\"Lists:\");\\n\\t\\t\\t\\tfor (const list of content.text.lists) {\\n\\t\\t\\t\\t\\tlines.push(`- ${list.type.toUpperCase()}:`);\\n\\t\\t\\t\\t\\tconst limit = typeof opts.maxElements === \"number\" ? opts.maxElements : list.items.length;\\n\\t\\t\\t\\t\\tfor (const item of list.items.slice(0, limit)) lines.push(` - ${truncate(item, opts.maxTextLength ?? 120)}`);\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tif (content.tables?.length) {\\n\\t\\t\\t\\tlines.push(\"Tables:\");\\n\\t\\t\\t\\tfor (const t of content.tables) {\\n\\t\\t\\t\\t\\tlines.push(`- Headers: ${t.headers.join(\" | \")}`);\\n\\t\\t\\t\\t\\tconst limit = typeof opts.maxElements === \"number\" ? opts.maxElements : t.rows.length;\\n\\t\\t\\t\\t\\tfor (const row of t.rows.slice(0, limit)) lines.push(` - ${row.join(\" | \")}`);\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tif (content.media?.length) {\\n\\t\\t\\t\\tlines.push(\"Media:\");\\n\\t\\t\\t\\tconst limit = typeof opts.maxElements === \"number\" ? opts.maxElements : content.media.length;\\n\\t\\t\\t\\tfor (const m of content.media.slice(0, limit)) lines.push(`- ${m.type.toUpperCase()}: ${m.alt ?? \"\"} ${m.src ? `→ ${m.src}` : \"\"}`.trim());\\n\\t\\t\\t\\tlines.push(\"\");\\n\\t\\t\\t}\\n\\t\\t\\tlines.push(\"Next: if text is insufficient for targeting, call dom_extract_region for interactive selectors.\");\\n\\t\\t\\treturn wrapXml(lines.join(\"\\\\n\"), meta, \"content\");\\n\\t\\t}\\n\\t};\\n\\tfunction capitalize(s) {\\n\\t\\treturn s.charAt(0).toUpperCase() + s.slice(1);\\n\\t}\\n\\t//#endregion\\n\\t//#region src/progressive.ts\\n\\tvar ProgressiveExtractor = class ProgressiveExtractor {\\n\\t\\t/**\\n\\t\\t* Step 1: Extract high-level structural overview\\n\\t\\t* This provides a \"map\" of the page for the AI to understand structure\\n\\t\\t*/\\n\\t\\tstatic extractStructure(root) {\\n\\t\\t\\tconst regions = {};\\n\\t\\t\\tconst header = root.querySelector(\"header, [role=\\\\\"banner\\\\\"], .header, #header\");\\n\\t\\t\\tif (header) regions.header = ProgressiveExtractor.analyzeRegion(header);\\n\\t\\t\\tconst navs = root.querySelectorAll(\"nav, [role=\\\\\"navigation\\\\\"], .nav, .navigation\");\\n\\t\\t\\tif (navs.length > 0) regions.navigation = Array.from(navs).map((nav) => ProgressiveExtractor.analyzeRegion(nav));\\n\\t\\t\\tif (DOMTraversal.isDocument(root)) {\\n\\t\\t\\t\\tconst main = ContentDetection.findMainContent(root);\\n\\t\\t\\t\\tif (main) {\\n\\t\\t\\t\\t\\tregions.main = ProgressiveExtractor.analyzeRegion(main);\\n\\t\\t\\t\\t\\tconst sections = main.querySelectorAll(\"section, article, [role=\\\\\"region\\\\\"]\");\\n\\t\\t\\t\\t\\tif (sections.length > 0) regions.sections = Array.from(sections).filter((section) => !section.closest(\"nav, header, footer\")).map((section) => ProgressiveExtractor.analyzeRegion(section));\\n\\t\\t\\t\\t}\\n\\t\\t\\t} else {\\n\\t\\t\\t\\tregions.main = ProgressiveExtractor.analyzeRegion(root);\\n\\t\\t\\t\\tconst sections = root.querySelectorAll(\"section, article, [role=\\\\\"region\\\\\"]\");\\n\\t\\t\\t\\tif (sections.length > 0) regions.sections = Array.from(sections).filter((section) => !section.closest(\"nav, header, footer\")).map((section) => ProgressiveExtractor.analyzeRegion(section));\\n\\t\\t\\t}\\n\\t\\t\\tconst sidebars = root.querySelectorAll(\"aside, [role=\\\\\"complementary\\\\\"], .sidebar, #sidebar\");\\n\\t\\t\\tif (sidebars.length > 0) regions.sidebar = Array.from(sidebars).map((sidebar) => ProgressiveExtractor.analyzeRegion(sidebar));\\n\\t\\t\\tconst footer = root.querySelector(\"footer, [role=\\\\\"contentinfo\\\\\"], .footer, #footer\");\\n\\t\\t\\tif (footer) regions.footer = ProgressiveExtractor.analyzeRegion(footer);\\n\\t\\t\\tconst modals = root.querySelectorAll(\"[role=\\\\\"dialog\\\\\"], .modal, .popup, .overlay\");\\n\\t\\t\\tconst visibleModals = Array.from(modals).filter((modal) => DOMTraversal.isVisible(modal));\\n\\t\\t\\tif (visibleModals.length > 0) regions.modals = visibleModals.map((modal) => ProgressiveExtractor.analyzeRegion(modal));\\n\\t\\t\\tconst forms = ProgressiveExtractor.extractFormOverview(root);\\n\\t\\t\\tconst summary = ProgressiveExtractor.calculateSummary(root, regions, forms);\\n\\t\\t\\treturn {\\n\\t\\t\\t\\tregions,\\n\\t\\t\\t\\tforms,\\n\\t\\t\\t\\tsummary,\\n\\t\\t\\t\\tsuggestions: ProgressiveExtractor.generateSuggestions(regions, summary)\\n\\t\\t\\t};\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Step 2: Extract detailed information from a specific region\\n\\t\\t*/\\n\\t\\tstatic extractRegion(selector, doc, options = {}) {\\n\\t\\t\\tconst element = doc.querySelector(selector);\\n\\t\\t\\tif (!element) return null;\\n\\t\\t\\treturn new SmartDOMReader(options).extract(element, options);\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Step 3: Extract readable content from a region\\n\\t\\t*/\\n\\t\\tstatic extractContent(selector, doc, options = {}) {\\n\\t\\t\\tconst element = doc.querySelector(selector);\\n\\t\\t\\tif (!element) return null;\\n\\t\\t\\tconst result = {\\n\\t\\t\\t\\tselector,\\n\\t\\t\\t\\ttext: {},\\n\\t\\t\\t\\tmetadata: {\\n\\t\\t\\t\\t\\twordCount: 0,\\n\\t\\t\\t\\t\\thasInteractive: false\\n\\t\\t\\t\\t}\\n\\t\\t\\t};\\n\\t\\t\\tif (options.includeHeadings !== false) {\\n\\t\\t\\t\\tconst headings = element.querySelectorAll(\"h1, h2, h3, h4, h5, h6\");\\n\\t\\t\\t\\tresult.text.headings = Array.from(headings).map((h) => ({\\n\\t\\t\\t\\t\\tlevel: Number.parseInt(h.tagName[1], 10),\\n\\t\\t\\t\\t\\ttext: ProgressiveExtractor.getTextContent(h, options.maxTextLength)\\n\\t\\t\\t\\t}));\\n\\t\\t\\t}\\n\\t\\t\\tconst paragraphs = element.querySelectorAll(\"p\");\\n\\t\\t\\tif (paragraphs.length > 0) result.text.paragraphs = Array.from(paragraphs).map((p) => ProgressiveExtractor.getTextContent(p, options.maxTextLength)).filter((text) => text.length > 0);\\n\\t\\t\\tif (options.includeLists !== false) {\\n\\t\\t\\t\\tconst lists = element.querySelectorAll(\"ul, ol\");\\n\\t\\t\\t\\tresult.text.lists = Array.from(lists).map((list) => ({\\n\\t\\t\\t\\t\\ttype: list.tagName.toLowerCase(),\\n\\t\\t\\t\\t\\titems: Array.from(list.querySelectorAll(\"li\")).map((li) => ProgressiveExtractor.getTextContent(li, options.maxTextLength))\\n\\t\\t\\t\\t}));\\n\\t\\t\\t}\\n\\t\\t\\tif (options.includeTables !== false) {\\n\\t\\t\\t\\tconst tables = element.querySelectorAll(\"table\");\\n\\t\\t\\t\\tresult.tables = Array.from(tables).map((table) => {\\n\\t\\t\\t\\t\\treturn {\\n\\t\\t\\t\\t\\t\\theaders: Array.from(table.querySelectorAll(\"th\")).map((th) => ProgressiveExtractor.getTextContent(th)),\\n\\t\\t\\t\\t\\t\\trows: Array.from(table.querySelectorAll(\"tr\")).filter((tr) => tr.querySelector(\"td\")).map((tr) => Array.from(tr.querySelectorAll(\"td\")).map((td) => ProgressiveExtractor.getTextContent(td)))\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t});\\n\\t\\t\\t}\\n\\t\\t\\tif (options.includeMedia !== false) {\\n\\t\\t\\t\\tconst images = element.querySelectorAll(\"img\");\\n\\t\\t\\t\\tconst videos = element.querySelectorAll(\"video\");\\n\\t\\t\\t\\tconst audios = element.querySelectorAll(\"audio\");\\n\\t\\t\\t\\tresult.media = [\\n\\t\\t\\t\\t\\t...Array.from(images).map((img) => {\\n\\t\\t\\t\\t\\t\\tconst item = { type: \"img\" };\\n\\t\\t\\t\\t\\t\\tconst alt = img.getAttribute(\"alt\");\\n\\t\\t\\t\\t\\t\\tconst src = img.getAttribute(\"src\");\\n\\t\\t\\t\\t\\t\\tif (alt) item.alt = alt;\\n\\t\\t\\t\\t\\t\\tif (src) item.src = src;\\n\\t\\t\\t\\t\\t\\treturn item;\\n\\t\\t\\t\\t\\t}),\\n\\t\\t\\t\\t\\t...Array.from(videos).map((video) => {\\n\\t\\t\\t\\t\\t\\tconst item = { type: \"video\" };\\n\\t\\t\\t\\t\\t\\tconst src = video.getAttribute(\"src\");\\n\\t\\t\\t\\t\\t\\tif (src) item.src = src;\\n\\t\\t\\t\\t\\t\\treturn item;\\n\\t\\t\\t\\t\\t}),\\n\\t\\t\\t\\t\\t...Array.from(audios).map((audio) => {\\n\\t\\t\\t\\t\\t\\tconst item = { type: \"audio\" };\\n\\t\\t\\t\\t\\t\\tconst src = audio.getAttribute(\"src\");\\n\\t\\t\\t\\t\\t\\tif (src) item.src = src;\\n\\t\\t\\t\\t\\t\\treturn item;\\n\\t\\t\\t\\t\\t})\\n\\t\\t\\t\\t];\\n\\t\\t\\t}\\n\\t\\t\\tconst allText = element.textContent || \"\";\\n\\t\\t\\tresult.metadata.wordCount = allText.trim().split(/\\\\s+/).length;\\n\\t\\t\\tresult.metadata.hasInteractive = element.querySelectorAll(\"button, a, input, textarea, select\").length > 0;\\n\\t\\t\\treturn result;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Analyze a region and extract summary information\\n\\t\\t*/\\n\\t\\tstatic analyzeRegion(element) {\\n\\t\\t\\tconst selector = SelectorGenerator.generateSelectors(element).css;\\n\\t\\t\\tconst buttons = element.querySelectorAll(\"button, [role=\\\\\"button\\\\\"]\");\\n\\t\\t\\tconst links = element.querySelectorAll(\"a[href]\");\\n\\t\\t\\tconst inputs = element.querySelectorAll(\"input, textarea, select\");\\n\\t\\t\\tconst forms = element.querySelectorAll(\"form\");\\n\\t\\t\\tconst lists = element.querySelectorAll(\"ul, ol\");\\n\\t\\t\\tconst tables = element.querySelectorAll(\"table\");\\n\\t\\t\\tconst media = element.querySelectorAll(\"img, video, audio\");\\n\\t\\t\\tconst interactiveCount = buttons.length + links.length + inputs.length;\\n\\t\\t\\tlet label;\\n\\t\\t\\tconst ariaLabel = element.getAttribute(\"aria-label\");\\n\\t\\t\\tif (ariaLabel) label = ariaLabel;\\n\\t\\t\\telse if (element.getAttribute(\"aria-labelledby\")) {\\n\\t\\t\\t\\tconst labelId = element.getAttribute(\"aria-labelledby\");\\n\\t\\t\\t\\tif (labelId) {\\n\\t\\t\\t\\t\\tconst labelElement = element.ownerDocument?.getElementById(labelId);\\n\\t\\t\\t\\t\\tif (labelElement) label = labelElement.textContent?.trim();\\n\\t\\t\\t\\t}\\n\\t\\t\\t} else {\\n\\t\\t\\t\\tconst heading = element.querySelector(\"h1, h2, h3\");\\n\\t\\t\\t\\tif (heading) label = heading.textContent?.trim();\\n\\t\\t\\t}\\n\\t\\t\\tconst textContent = element.textContent?.trim() || \"\";\\n\\t\\t\\tconst textPreview = textContent.length > 50 ? `${textContent.substring(0, 50)}...` : textContent;\\n\\t\\t\\tconst regionInfo = {\\n\\t\\t\\t\\tselector,\\n\\t\\t\\t\\tinteractiveCount,\\n\\t\\t\\t\\thasForm: forms.length > 0,\\n\\t\\t\\t\\thasList: lists.length > 0,\\n\\t\\t\\t\\thasTable: tables.length > 0,\\n\\t\\t\\t\\thasMedia: media.length > 0\\n\\t\\t\\t};\\n\\t\\t\\tif (label) regionInfo.label = label;\\n\\t\\t\\tconst role = element.getAttribute(\"role\");\\n\\t\\t\\tif (role) regionInfo.role = role;\\n\\t\\t\\tif (buttons.length > 0) regionInfo.buttonCount = buttons.length;\\n\\t\\t\\tif (links.length > 0) regionInfo.linkCount = links.length;\\n\\t\\t\\tif (inputs.length > 0) regionInfo.inputCount = inputs.length;\\n\\t\\t\\tif (textPreview.length > 0) regionInfo.textPreview = textPreview;\\n\\t\\t\\treturn regionInfo;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Extract overview of forms on the page\\n\\t\\t*/\\n\\t\\tstatic extractFormOverview(root) {\\n\\t\\t\\tconst forms = root.querySelectorAll(\"form\");\\n\\t\\t\\treturn Array.from(forms).map((form) => {\\n\\t\\t\\t\\tconst inputs = form.querySelectorAll(\"input, textarea, select\");\\n\\t\\t\\t\\tconst selector = SelectorGenerator.generateSelectors(form).css;\\n\\t\\t\\t\\tlet location = \"unknown\";\\n\\t\\t\\t\\tif (form.closest(\"header, [role=\\\\\"banner\\\\\"]\")) location = \"header\";\\n\\t\\t\\t\\telse if (form.closest(\"nav, [role=\\\\\"navigation\\\\\"]\")) location = \"navigation\";\\n\\t\\t\\t\\telse if (form.closest(\"main, [role=\\\\\"main\\\\\"]\")) location = \"main\";\\n\\t\\t\\t\\telse if (form.closest(\"aside, [role=\\\\\"complementary\\\\\"]\")) location = \"sidebar\";\\n\\t\\t\\t\\telse if (form.closest(\"footer, [role=\\\\\"contentinfo\\\\\"]\")) location = \"footer\";\\n\\t\\t\\t\\tlet purpose;\\n\\t\\t\\t\\tconst formId = form.getAttribute(\"id\")?.toLowerCase();\\n\\t\\t\\t\\tconst formClass = form.getAttribute(\"class\")?.toLowerCase();\\n\\t\\t\\t\\tconst formAction = form.getAttribute(\"action\")?.toLowerCase();\\n\\t\\t\\t\\tconst hasEmail = form.querySelector(\"input[type=\\\\\"email\\\\\"]\");\\n\\t\\t\\t\\tconst hasPassword = form.querySelector(\"input[type=\\\\\"password\\\\\"]\");\\n\\t\\t\\t\\tif (form.querySelector(\"input[type=\\\\\"search\\\\\"]\") || formId?.includes(\"search\") || formClass?.includes(\"search\")) purpose = \"search\";\\n\\t\\t\\t\\telse if (hasPassword && hasEmail) purpose = \"login\";\\n\\t\\t\\t\\telse if (hasPassword) purpose = \"authentication\";\\n\\t\\t\\t\\telse if (formId?.includes(\"contact\") || formClass?.includes(\"contact\")) purpose = \"contact\";\\n\\t\\t\\t\\telse if (formId?.includes(\"subscribe\") || formClass?.includes(\"subscribe\")) purpose = \"subscription\";\\n\\t\\t\\t\\telse if (formAction?.includes(\"checkout\") || formClass?.includes(\"checkout\")) purpose = \"checkout\";\\n\\t\\t\\t\\tconst formOverview = {\\n\\t\\t\\t\\t\\tselector,\\n\\t\\t\\t\\t\\tlocation,\\n\\t\\t\\t\\t\\tinputCount: inputs.length\\n\\t\\t\\t\\t};\\n\\t\\t\\t\\tif (purpose) formOverview.purpose = purpose;\\n\\t\\t\\t\\treturn formOverview;\\n\\t\\t\\t});\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Calculate summary statistics\\n\\t\\t*/\\n\\t\\tstatic calculateSummary(root, regions, forms) {\\n\\t\\t\\tconst allInteractive = root.querySelectorAll(\"button, a[href], input, textarea, select\");\\n\\t\\t\\tconst allSections = root.querySelectorAll(\"section, article, [role=\\\\\"region\\\\\"]\");\\n\\t\\t\\tconst hasModals = (regions.modals?.length || 0) > 0;\\n\\t\\t\\tconst hasErrors = [\\n\\t\\t\\t\\t\".error\",\\n\\t\\t\\t\\t\".alert-danger\",\\n\\t\\t\\t\\t\"[role=\\\\\"alert\\\\\"]\"\\n\\t\\t\\t].some((sel) => {\\n\\t\\t\\t\\tconst element = root.querySelector(sel);\\n\\t\\t\\t\\treturn element ? DOMTraversal.isVisible(element) : false;\\n\\t\\t\\t});\\n\\t\\t\\tconst isLoading = [\\n\\t\\t\\t\\t\".loading\",\\n\\t\\t\\t\\t\".spinner\",\\n\\t\\t\\t\\t\"[aria-busy=\\\\\"true\\\\\"]\"\\n\\t\\t\\t].some((sel) => {\\n\\t\\t\\t\\tconst element = root.querySelector(sel);\\n\\t\\t\\t\\treturn element ? DOMTraversal.isVisible(element) : false;\\n\\t\\t\\t});\\n\\t\\t\\tconst summary = {\\n\\t\\t\\t\\ttotalInteractive: allInteractive.length,\\n\\t\\t\\t\\ttotalForms: forms.length,\\n\\t\\t\\t\\ttotalSections: allSections.length,\\n\\t\\t\\t\\thasModals,\\n\\t\\t\\t\\thasErrors,\\n\\t\\t\\t\\tisLoading\\n\\t\\t\\t};\\n\\t\\t\\tconst mainContentSelector = regions.main?.selector;\\n\\t\\t\\tif (mainContentSelector) summary.mainContentSelector = mainContentSelector;\\n\\t\\t\\treturn summary;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Generate AI-friendly suggestions\\n\\t\\t*/\\n\\t\\tstatic generateSuggestions(regions, summary) {\\n\\t\\t\\tconst suggestions = [];\\n\\t\\t\\tif (summary.hasErrors) suggestions.push(\"Page has error indicators - check error messages before interacting\");\\n\\t\\t\\tif (summary.isLoading) suggestions.push(\"Page appears to be loading - wait or check loading state\");\\n\\t\\t\\tif (summary.hasModals) suggestions.push(\"Modal/dialog is open - may need to interact with or close it first\");\\n\\t\\t\\tif (regions.main && regions.main.interactiveCount > 10) suggestions.push(`Main content has ${regions.main.interactiveCount} interactive elements - consider filtering`);\\n\\t\\t\\tif (summary.totalForms > 0) suggestions.push(`Found ${summary.totalForms} form(s) on the page`);\\n\\t\\t\\tif (!regions.main) suggestions.push(\"No clear main content area detected - may need to explore regions\");\\n\\t\\t\\treturn suggestions;\\n\\t\\t}\\n\\t\\t/**\\n\\t\\t* Get text content with optional truncation\\n\\t\\t*/\\n\\t\\tstatic getTextContent(element, maxLength) {\\n\\t\\t\\tconst text = element.textContent?.trim() || \"\";\\n\\t\\t\\tif (maxLength && text.length > maxLength) return `${text.substring(0, maxLength)}...`;\\n\\t\\t\\treturn text;\\n\\t\\t}\\n\\t};\\n\\t//#endregion\\n\\t//#region src/bundle-entry.ts\\n\\tfunction resolveDocument(frameSelector) {\\n\\t\\tif (!frameSelector) return document;\\n\\t\\tconst iframe = document.querySelector(frameSelector);\\n\\t\\tif (!(iframe instanceof HTMLIFrameElement) || !iframe.contentDocument) throw new Error(`Cannot access iframe: ${frameSelector}`);\\n\\t\\treturn iframe.contentDocument;\\n\\t}\\n\\tfunction executeExtraction(method, args) {\\n\\t\\ttry {\\n\\t\\t\\tlet result;\\n\\t\\t\\tswitch (method) {\\n\\t\\t\\t\\tcase \"extractStructure\": {\\n\\t\\t\\t\\t\\tconst { selector, frameSelector, formatOptions } = args;\\n\\t\\t\\t\\t\\tconst doc = resolveDocument(frameSelector);\\n\\t\\t\\t\\t\\tconst target = selector ? doc.querySelector(selector) : doc;\\n\\t\\t\\t\\t\\tif (!target) return { error: `No element found matching selector: ${selector}` };\\n\\t\\t\\t\\t\\tconst overview = ProgressiveExtractor.extractStructure(target);\\n\\t\\t\\t\\t\\tconst meta = {\\n\\t\\t\\t\\t\\t\\ttitle: document.title,\\n\\t\\t\\t\\t\\t\\turl: location.href\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t\\tresult = MarkdownFormatter.structure(overview, formatOptions ?? { detail: \"summary\" }, meta);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tcase \"extractRegion\": {\\n\\t\\t\\t\\t\\tconst { selector, mode, frameSelector, options, formatOptions } = args;\\n\\t\\t\\t\\t\\tconst doc = resolveDocument(frameSelector);\\n\\t\\t\\t\\t\\tconst extractOptions = {\\n\\t\\t\\t\\t\\t\\t...options,\\n\\t\\t\\t\\t\\t\\tmode: mode || \"interactive\"\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t\\tconst extractResult = ProgressiveExtractor.extractRegion(selector, doc, extractOptions);\\n\\t\\t\\t\\t\\tif (!extractResult) return { error: `No element found matching selector: ${selector}` };\\n\\t\\t\\t\\t\\tconst meta = {\\n\\t\\t\\t\\t\\t\\ttitle: document.title,\\n\\t\\t\\t\\t\\t\\turl: location.href\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t\\tresult = MarkdownFormatter.region(extractResult, formatOptions ?? { detail: \"region\" }, meta);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tcase \"extractContent\": {\\n\\t\\t\\t\\t\\tconst { selector, frameSelector, options, formatOptions } = args;\\n\\t\\t\\t\\t\\tconst doc = resolveDocument(frameSelector);\\n\\t\\t\\t\\t\\tconst extractOptions = options || {};\\n\\t\\t\\t\\t\\tconst extractResult = ProgressiveExtractor.extractContent(selector, doc, extractOptions);\\n\\t\\t\\t\\t\\tif (!extractResult) return { error: `No element found matching selector: ${selector}` };\\n\\t\\t\\t\\t\\tconst meta = {\\n\\t\\t\\t\\t\\t\\ttitle: document.title,\\n\\t\\t\\t\\t\\t\\turl: location.href\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t\\tresult = MarkdownFormatter.content(extractResult, formatOptions ?? { detail: \"region\" }, meta);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tcase \"extractInteractive\": {\\n\\t\\t\\t\\t\\tconst { selector, frameSelector, options, formatOptions } = args;\\n\\t\\t\\t\\t\\tconst doc = resolveDocument(frameSelector);\\n\\t\\t\\t\\t\\tconst target = selector ? doc.querySelector(selector) : null;\\n\\t\\t\\t\\t\\tif (selector && !target) return { error: `No element found matching selector: ${selector}` };\\n\\t\\t\\t\\t\\tconst extractResult = target ? SmartDOMReader.extractFromElement(target, \"interactive\", options || {}) : SmartDOMReader.extractInteractive(doc, options || {});\\n\\t\\t\\t\\t\\tconst meta = {\\n\\t\\t\\t\\t\\t\\ttitle: document.title,\\n\\t\\t\\t\\t\\t\\turl: location.href\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t\\tresult = MarkdownFormatter.region(extractResult, formatOptions ?? { detail: \"region\" }, meta);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tcase \"extractFull\": {\\n\\t\\t\\t\\t\\tconst { selector, frameSelector, options, formatOptions } = args;\\n\\t\\t\\t\\t\\tconst doc = resolveDocument(frameSelector);\\n\\t\\t\\t\\t\\tconst target = selector ? doc.querySelector(selector) : null;\\n\\t\\t\\t\\t\\tif (selector && !target) return { error: `No element found matching selector: ${selector}` };\\n\\t\\t\\t\\t\\tconst extractResult = target ? SmartDOMReader.extractFromElement(target, \"full\", options || {}) : SmartDOMReader.extractFull(doc, options || {});\\n\\t\\t\\t\\t\\tconst meta = {\\n\\t\\t\\t\\t\\t\\ttitle: document.title,\\n\\t\\t\\t\\t\\t\\turl: location.href\\n\\t\\t\\t\\t\\t};\\n\\t\\t\\t\\t\\tresult = MarkdownFormatter.region(extractResult, formatOptions ?? { detail: \"deep\" }, meta);\\n\\t\\t\\t\\t\\tbreak;\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tdefault: return { error: `Unknown method: ${method}` };\\n\\t\\t\\t}\\n\\t\\t\\treturn result;\\n\\t\\t} catch (error) {\\n\\t\\t\\treturn { error: error instanceof Error ? error.message : String(error) };\\n\\t\\t}\\n\\t}\\n\\t//#endregion\\n\\texports.executeExtraction = executeExtraction;\\n\\treturn exports;\\n})({});\\n';\n\nexport const SMART_DOM_READER_VERSION = '5.0.3';\n"],"mappings":";;;;;;;;AAQA,MAAa,0BACX;AAEF,MAAa,2BAA2B"}
package/dist/index.d.mts CHANGED
@@ -359,7 +359,6 @@ declare class SelectorGenerator {
359
359
  * Check if a selector is unique within a container
360
360
  */
361
361
  private static isUniqueSelector;
362
- private static isUniqueSelectorSafe;
363
362
  /**
364
363
  * Get meaningful classes (filtering out utility classes)
365
364
  */
package/dist/index.mjs CHANGED
@@ -400,11 +400,11 @@ var SelectorGenerator = class SelectorGenerator {
400
400
  });
401
401
  const testId = SelectorGenerator.getDataTestId(element);
402
402
  if (testId) {
403
- const v = `[data-testid="${CSS.escape(testId)}"]`;
403
+ const v = `[${testId.name}="${CSS.escape(testId.value)}"]`;
404
404
  candidates.push({
405
405
  type: "data-testid",
406
406
  value: v,
407
- score: 90 + (SelectorGenerator.isUniqueSelectorSafe(v, doc) ? 5 : 0)
407
+ score: 90 + (SelectorGenerator.isUniqueSelector(v, doc) ? 5 : 0)
408
408
  });
409
409
  }
410
410
  const role = element.getAttribute("role");
@@ -414,7 +414,7 @@ var SelectorGenerator = class SelectorGenerator {
414
414
  candidates.push({
415
415
  type: "role-aria",
416
416
  value: v,
417
- score: 85 + (SelectorGenerator.isUniqueSelectorSafe(v, doc) ? 5 : 0)
417
+ score: 85 + (SelectorGenerator.isUniqueSelector(v, doc) ? 5 : 0)
418
418
  });
419
419
  }
420
420
  const nameAttr = element.getAttribute("name");
@@ -423,7 +423,7 @@ var SelectorGenerator = class SelectorGenerator {
423
423
  candidates.push({
424
424
  type: "name",
425
425
  value: v,
426
- score: 78 + (SelectorGenerator.isUniqueSelectorSafe(v, doc) ? 5 : 0)
426
+ score: 78 + (SelectorGenerator.isUniqueSelector(v, doc) ? 5 : 0)
427
427
  });
428
428
  }
429
429
  const pathCss = SelectorGenerator.generateCSSSelector(element, doc);
@@ -449,12 +449,12 @@ var SelectorGenerator = class SelectorGenerator {
449
449
  });
450
450
  candidates.sort((a, b) => b.score - a.score);
451
451
  const selector = {
452
- css: candidates.find((c) => c.type !== "xpath" && c.type !== "text")?.value || pathCss,
452
+ css: candidates.find((c) => c.type !== "xpath" && c.type !== "text" && SelectorGenerator.isUniqueSelector(c.value, doc, element))?.value || pathCss,
453
453
  xpath,
454
454
  candidates
455
455
  };
456
456
  if (textBased) selector.textBased = textBased;
457
- if (testId) selector.dataTestId = testId;
457
+ if (testId) selector.dataTestId = testId.value;
458
458
  if (aria) selector.ariaLabel = aria;
459
459
  return selector;
460
460
  }
@@ -464,7 +464,10 @@ var SelectorGenerator = class SelectorGenerator {
464
464
  static generateCSSSelector(element, doc) {
465
465
  if (element.id && SelectorGenerator.isUniqueId(element.id, doc)) return `#${CSS.escape(element.id)}`;
466
466
  const testId = SelectorGenerator.getDataTestId(element);
467
- if (testId) return `[data-testid="${CSS.escape(testId)}"]`;
467
+ if (testId) {
468
+ const selector = `[${testId.name}="${CSS.escape(testId.value)}"]`;
469
+ if (SelectorGenerator.isUniqueSelector(selector, doc, element)) return selector;
470
+ }
468
471
  const path = [];
469
472
  let current = element;
470
473
  while (current && current.nodeType === Node.ELEMENT_NODE) {
@@ -490,13 +493,12 @@ var SelectorGenerator = class SelectorGenerator {
490
493
  * Generate XPath for an element
491
494
  */
492
495
  static generateXPath(element, doc) {
493
- if (element.id && SelectorGenerator.isUniqueId(element.id, doc)) return `//*[@id="${element.id}"]`;
494
496
  const path = [];
495
497
  let current = element;
496
498
  while (current && current.nodeType === Node.ELEMENT_NODE) {
497
499
  const tagName = current.nodeName.toLowerCase();
498
- if (current.id && SelectorGenerator.isUniqueId(current.id, doc)) {
499
- path.unshift(`//*[@id="${current.id}"]`);
500
+ if (current.id && !current.id.includes("\"") && SelectorGenerator.isUniqueId(current.id, doc)) {
501
+ path.unshift(`*[@id="${current.id}"]`);
500
502
  break;
501
503
  }
502
504
  let xpath = tagName;
@@ -530,7 +532,15 @@ var SelectorGenerator = class SelectorGenerator {
530
532
  * Get data-testid or similar attributes
531
533
  */
532
534
  static getDataTestId(element) {
533
- return element.getAttribute("data-testid") || element.getAttribute("data-test-id") || element.getAttribute("data-test") || element.getAttribute("data-cy") || void 0;
535
+ for (const name of [
536
+ "data-testid",
537
+ "data-test-id",
538
+ "data-test",
539
+ "data-cy"
540
+ ]) {
541
+ const attribute = element.getAttributeNode(name);
542
+ if (attribute?.value) return attribute;
543
+ }
534
544
  }
535
545
  /**
536
546
  * Check if an ID is unique in the document
@@ -541,16 +551,10 @@ var SelectorGenerator = class SelectorGenerator {
541
551
  /**
542
552
  * Check if a selector is unique within a container
543
553
  */
544
- static isUniqueSelector(selector, container) {
545
- try {
546
- return container.querySelectorAll(selector).length === 1;
547
- } catch {
548
- return false;
549
- }
550
- }
551
- static isUniqueSelectorSafe(selector, doc) {
554
+ static isUniqueSelector(selector, container, element) {
552
555
  try {
553
- return doc.querySelectorAll(selector).length === 1;
556
+ const matches = container.querySelectorAll(selector);
557
+ return matches.length === 1 && (element === void 0 || matches[0] === element);
554
558
  } catch {
555
559
  return false;
556
560
  }
@@ -577,7 +581,7 @@ var SelectorGenerator = class SelectorGenerator {
577
581
  * Optimize the selector path by removing unnecessary parts
578
582
  */
579
583
  static optimizePath(path, element, doc) {
580
- for (let i = 0; i < path.length - 1; i++) {
584
+ for (let i = path.length - 1; i >= 0; i--) {
581
585
  const shortPath = path.slice(i).join(" > ");
582
586
  try {
583
587
  const matches = doc.querySelectorAll(shortPath);
@@ -717,7 +721,7 @@ var DOMTraversal = class DOMTraversal {
717
721
  * Extract element information
718
722
  */
719
723
  static extractElement(element, options, depth = 0) {
720
- if (options.maxDepth && depth > options.maxDepth) return null;
724
+ if (options.maxDepth !== void 0 && depth > options.maxDepth) return null;
721
725
  if (!options.includeHidden && !DOMTraversal.isVisible(element)) return null;
722
726
  if (options.viewportOnly && !DOMTraversal.isInViewport(element)) return null;
723
727
  if (!DOMTraversal.passesFilter(element, options.filter)) return null;
@@ -746,26 +750,13 @@ var DOMTraversal = class DOMTraversal {
746
750
  */
747
751
  static extractChildren(container, options, depth) {
748
752
  const children = [];
749
- const elements = container.querySelectorAll("*");
750
- for (const child of Array.from(elements)) {
751
- if (DOMTraversal.hasExtractedAncestor(child, elements)) continue;
753
+ for (const child of Array.from(container.children)) {
752
754
  const extracted = DOMTraversal.extractElement(child, options, depth);
753
755
  if (extracted) children.push(extracted);
754
756
  }
755
757
  return children;
756
758
  }
757
759
  /**
758
- * Check if element has an ancestor that was already extracted
759
- */
760
- static hasExtractedAncestor(element, extractedElements) {
761
- let parent = element.parentElement;
762
- while (parent) {
763
- if (Array.from(extractedElements).includes(parent)) return true;
764
- parent = parent.parentElement;
765
- }
766
- return false;
767
- }
768
- /**
769
760
  * Get relevant attributes for an element
770
761
  */
771
762
  static getRelevantAttributes(element, options) {
@@ -885,7 +876,7 @@ var SmartDOMReader = class SmartDOMReader {
885
876
  constructor(options = {}) {
886
877
  this.options = {
887
878
  mode: options.mode || "interactive",
888
- maxDepth: options.maxDepth || 5,
879
+ maxDepth: options.maxDepth ?? 5,
889
880
  includeHidden: options.includeHidden || false,
890
881
  includeShadowDOM: options.includeShadowDOM ?? true,
891
882
  includeIframes: options.includeIframes || false,
@@ -972,6 +963,7 @@ var SmartDOMReader = class SmartDOMReader {
972
963
  querySelectorAll(container, selector, includeShadowDOM) {
973
964
  const matches = [...container.querySelectorAll(selector)];
974
965
  if (!includeShadowDOM) return matches;
966
+ if ("shadowRoot" in container && container.shadowRoot) matches.push(...this.querySelectorAll(container.shadowRoot, selector, true));
975
967
  for (const element of container.querySelectorAll("*")) if (element.shadowRoot) matches.push(...this.querySelectorAll(element.shadowRoot, selector, true));
976
968
  return matches;
977
969
  }