@mcp-b/smart-dom-reader 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -68
- package/dist/bundle-string.d.mts +13 -0
- package/dist/bundle-string.d.mts.map +1 -0
- package/dist/bundle-string.mjs +14 -0
- package/dist/bundle-string.mjs.map +1 -0
- package/dist/{index.d.ts → index.d.mts} +2 -3
- package/dist/index.d.mts.map +1 -0
- package/dist/{index.js → index.mjs} +24 -34
- package/dist/index.mjs.map +1 -0
- package/package.json +42 -42
- package/dist/bundle-string.d.ts +0 -13
- package/dist/bundle-string.d.ts.map +0 -1
- package/dist/bundle-string.js +0 -14
- package/dist/bundle-string.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
|
|
15
15
|
## Why Use @mcp-b/smart-dom-reader?
|
|
16
16
|
|
|
17
|
-
| Feature
|
|
18
|
-
|
|
19
|
-
| **Token-Efficient**
|
|
20
|
-
| **Stable Selectors**
|
|
21
|
-
| **AI-Optimized Output** | Structured data designed for LLM understanding
|
|
22
|
-
| **Zero Dependencies**
|
|
23
|
-
| **Shadow DOM Support**
|
|
24
|
-
| **Stateless API**
|
|
17
|
+
| Feature | Benefit |
|
|
18
|
+
| ----------------------- | -------------------------------------------------------------------------------- |
|
|
19
|
+
| **Token-Efficient** | Progressive extraction minimizes LLM context window usage |
|
|
20
|
+
| **Stable Selectors** | Ranked CSS selectors (ID > data-testid > ARIA > classes) for reliable automation |
|
|
21
|
+
| **AI-Optimized Output** | Structured data designed for LLM understanding |
|
|
22
|
+
| **Zero Dependencies** | Lightweight, runs in any browser environment |
|
|
23
|
+
| **Shadow DOM Support** | Traverses shadow roots and iframes |
|
|
24
|
+
| **Stateless API** | Works with any document context - Puppeteer, Playwright, browser extensions |
|
|
25
25
|
|
|
26
26
|
## Use Cases
|
|
27
27
|
|
|
@@ -69,7 +69,7 @@ const fullData = SmartDOMReader.extractFull(doc);
|
|
|
69
69
|
const customData = SmartDOMReader.extractInteractive(doc, {
|
|
70
70
|
mainContentOnly: true,
|
|
71
71
|
viewportOnly: true,
|
|
72
|
-
includeHidden: false
|
|
72
|
+
includeHidden: false,
|
|
73
73
|
});
|
|
74
74
|
```
|
|
75
75
|
|
|
@@ -95,11 +95,10 @@ const mainContent = ProgressiveExtractor.extractRegion(
|
|
|
95
95
|
);
|
|
96
96
|
|
|
97
97
|
// Step 3: Extract readable content from a region
|
|
98
|
-
const articleText = ProgressiveExtractor.extractContent(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
98
|
+
const articleText = ProgressiveExtractor.extractContent('article.main-article', document, {
|
|
99
|
+
includeHeadings: true,
|
|
100
|
+
includeLists: true,
|
|
101
|
+
});
|
|
103
102
|
|
|
104
103
|
// Structure scoped to a container (e.g., navigation only)
|
|
105
104
|
const nav = document.querySelector('nav');
|
|
@@ -112,7 +111,9 @@ if (nav) {
|
|
|
112
111
|
## Extraction Modes
|
|
113
112
|
|
|
114
113
|
### Interactive Mode
|
|
114
|
+
|
|
115
115
|
Focuses on elements users can interact with:
|
|
116
|
+
|
|
116
117
|
- Buttons and button-like elements
|
|
117
118
|
- Links
|
|
118
119
|
- Form inputs (text, select, textarea)
|
|
@@ -120,7 +121,9 @@ Focuses on elements users can interact with:
|
|
|
120
121
|
- Form structures and associations
|
|
121
122
|
|
|
122
123
|
### Full Mode
|
|
124
|
+
|
|
123
125
|
Includes everything from interactive mode plus:
|
|
126
|
+
|
|
124
127
|
- Semantic HTML elements (articles, sections, nav)
|
|
125
128
|
- Headings hierarchy
|
|
126
129
|
- Images with alt text
|
|
@@ -136,7 +139,7 @@ Includes everything from interactive mode plus:
|
|
|
136
139
|
const reader = new SmartDOMReader({
|
|
137
140
|
mode: 'interactive',
|
|
138
141
|
mainContentOnly: true,
|
|
139
|
-
viewportOnly: false
|
|
142
|
+
viewportOnly: false,
|
|
140
143
|
});
|
|
141
144
|
const result = reader.extract(document);
|
|
142
145
|
|
|
@@ -154,19 +157,11 @@ const overview = ProgressiveExtractor.extractStructure(document);
|
|
|
154
157
|
// Returns: regions, forms, summary, suggestions
|
|
155
158
|
|
|
156
159
|
// Step 2: Region extraction
|
|
157
|
-
const region = ProgressiveExtractor.extractRegion(
|
|
158
|
-
selector,
|
|
159
|
-
document,
|
|
160
|
-
options
|
|
161
|
-
);
|
|
160
|
+
const region = ProgressiveExtractor.extractRegion(selector, document, options);
|
|
162
161
|
// Returns: Full SmartDOMResult for that region
|
|
163
162
|
|
|
164
163
|
// Step 3: Content extraction
|
|
165
|
-
const content = ProgressiveExtractor.extractContent(
|
|
166
|
-
selector,
|
|
167
|
-
document,
|
|
168
|
-
{ includeMedia: true }
|
|
169
|
-
);
|
|
164
|
+
const content = ProgressiveExtractor.extractContent(selector, document, { includeMedia: true });
|
|
170
165
|
// Returns: Text content, headings, lists, tables, media
|
|
171
166
|
```
|
|
172
167
|
|
|
@@ -178,7 +173,7 @@ Both approaches return structured data optimized for AI processing:
|
|
|
178
173
|
interface SmartDOMResult {
|
|
179
174
|
mode: 'interactive' | 'full';
|
|
180
175
|
timestamp: number;
|
|
181
|
-
|
|
176
|
+
|
|
182
177
|
page: {
|
|
183
178
|
url: string;
|
|
184
179
|
title: string;
|
|
@@ -187,7 +182,7 @@ interface SmartDOMResult {
|
|
|
187
182
|
hasModals: boolean;
|
|
188
183
|
hasFocus?: string;
|
|
189
184
|
};
|
|
190
|
-
|
|
185
|
+
|
|
191
186
|
landmarks: {
|
|
192
187
|
navigation: string[];
|
|
193
188
|
main: string[];
|
|
@@ -197,7 +192,7 @@ interface SmartDOMResult {
|
|
|
197
192
|
articles: string[];
|
|
198
193
|
sections: string[];
|
|
199
194
|
};
|
|
200
|
-
|
|
195
|
+
|
|
201
196
|
interactive: {
|
|
202
197
|
buttons: ExtractedElement[];
|
|
203
198
|
links: ExtractedElement[];
|
|
@@ -205,16 +200,18 @@ interface SmartDOMResult {
|
|
|
205
200
|
forms: FormInfo[];
|
|
206
201
|
clickable: ExtractedElement[];
|
|
207
202
|
};
|
|
208
|
-
|
|
209
|
-
semantic?: {
|
|
203
|
+
|
|
204
|
+
semantic?: {
|
|
205
|
+
// Only in full mode
|
|
210
206
|
headings: ExtractedElement[];
|
|
211
207
|
images: ExtractedElement[];
|
|
212
208
|
tables: ExtractedElement[];
|
|
213
209
|
lists: ExtractedElement[];
|
|
214
210
|
articles: ExtractedElement[];
|
|
215
211
|
};
|
|
216
|
-
|
|
217
|
-
metadata?: {
|
|
212
|
+
|
|
213
|
+
metadata?: {
|
|
214
|
+
// Only in full mode
|
|
218
215
|
totalElements: number;
|
|
219
216
|
extractedElements: number;
|
|
220
217
|
mainContent?: string;
|
|
@@ -233,15 +230,23 @@ interface ExtractedElement {
|
|
|
233
230
|
text: string;
|
|
234
231
|
|
|
235
232
|
selector: {
|
|
236
|
-
css: string;
|
|
237
|
-
xpath: string;
|
|
238
|
-
textBased?: string;
|
|
233
|
+
css: string; // Best CSS selector (ranked stable-first)
|
|
234
|
+
xpath: string; // XPath selector
|
|
235
|
+
textBased?: string; // Text-content based hint
|
|
239
236
|
dataTestId?: string; // data-testid if available
|
|
240
|
-
ariaLabel?: string;
|
|
237
|
+
ariaLabel?: string; // ARIA label if available
|
|
241
238
|
candidates?: Array<{
|
|
242
|
-
type:
|
|
239
|
+
type:
|
|
240
|
+
| 'id'
|
|
241
|
+
| 'data-testid'
|
|
242
|
+
| 'role-aria'
|
|
243
|
+
| 'name'
|
|
244
|
+
| 'class-path'
|
|
245
|
+
| 'css-path'
|
|
246
|
+
| 'xpath'
|
|
247
|
+
| 'text';
|
|
243
248
|
value: string;
|
|
244
|
-
score: number;
|
|
249
|
+
score: number; // Higher = more stable/robust
|
|
245
250
|
}>;
|
|
246
251
|
};
|
|
247
252
|
|
|
@@ -271,20 +276,21 @@ interface ExtractedElement {
|
|
|
271
276
|
|
|
272
277
|
## Options
|
|
273
278
|
|
|
274
|
-
| Option
|
|
275
|
-
|
|
276
|
-
| `mode`
|
|
277
|
-
| `maxDepth`
|
|
278
|
-
| `includeHidden`
|
|
279
|
-
| `includeShadowDOM` | `boolean`
|
|
280
|
-
| `includeIframes`
|
|
281
|
-
| `viewportOnly`
|
|
282
|
-
| `mainContentOnly`
|
|
283
|
-
| `customSelectors`
|
|
279
|
+
| Option | Type | Default | Description |
|
|
280
|
+
| ------------------ | ------------------------- | --------------- | ------------------------------- |
|
|
281
|
+
| `mode` | `'interactive' \| 'full'` | `'interactive'` | Extraction mode |
|
|
282
|
+
| `maxDepth` | `number` | `5` | Maximum traversal depth |
|
|
283
|
+
| `includeHidden` | `boolean` | `false` | Include hidden elements |
|
|
284
|
+
| `includeShadowDOM` | `boolean` | `true` | Traverse shadow DOM |
|
|
285
|
+
| `includeIframes` | `boolean` | `false` | Traverse iframes |
|
|
286
|
+
| `viewportOnly` | `boolean` | `false` | Only visible viewport elements |
|
|
287
|
+
| `mainContentOnly` | `boolean` | `false` | Focus on main content area |
|
|
288
|
+
| `customSelectors` | `string[]` | `[]` | Additional selectors to extract |
|
|
284
289
|
|
|
285
290
|
## Use Cases
|
|
286
291
|
|
|
287
292
|
### AI Userscript Generation (Progressive Approach)
|
|
293
|
+
|
|
288
294
|
```typescript
|
|
289
295
|
// First, understand the page structure
|
|
290
296
|
const structure = ProgressiveExtractor.extractStructure(document);
|
|
@@ -293,31 +299,31 @@ const structure = ProgressiveExtractor.extractStructure(document);
|
|
|
293
299
|
const targetRegion = structure.regions.main?.selector || 'body';
|
|
294
300
|
|
|
295
301
|
// Extract detailed information from chosen region
|
|
296
|
-
const details = ProgressiveExtractor.extractRegion(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
);
|
|
302
|
+
const details = ProgressiveExtractor.extractRegion(targetRegion, document, {
|
|
303
|
+
mode: 'interactive',
|
|
304
|
+
viewportOnly: true,
|
|
305
|
+
});
|
|
301
306
|
|
|
302
307
|
// Generate userscript prompt with focused context
|
|
303
308
|
const prompt = `
|
|
304
309
|
Page: ${details.page.title}
|
|
305
310
|
Main form: ${details.interactive.forms[0]?.selector}
|
|
306
|
-
Submit button: ${details.interactive.buttons.find(b => b.text.includes('Submit'))?.selector.css}
|
|
311
|
+
Submit button: ${details.interactive.buttons.find((b) => b.text.includes('Submit'))?.selector.css}
|
|
307
312
|
|
|
308
313
|
Write a userscript to auto-fill and submit this form.
|
|
309
314
|
`;
|
|
310
315
|
```
|
|
311
316
|
|
|
312
317
|
### Test Automation (Full Extraction)
|
|
318
|
+
|
|
313
319
|
```typescript
|
|
314
320
|
// Get all interactive elements at once
|
|
315
321
|
const testData = SmartDOMReader.extractInteractive(document, {
|
|
316
|
-
customSelectors: ['[data-test]', '[data-cy]']
|
|
322
|
+
customSelectors: ['[data-test]', '[data-cy]'],
|
|
317
323
|
});
|
|
318
324
|
|
|
319
325
|
// Use multiple selector strategies for robust testing
|
|
320
|
-
testData.interactive.buttons.forEach(button => {
|
|
326
|
+
testData.interactive.buttons.forEach((button) => {
|
|
321
327
|
console.log(`Button: ${button.text}`);
|
|
322
328
|
console.log(` CSS: ${button.selector.css}`);
|
|
323
329
|
console.log(` XPath: ${button.selector.xpath}`);
|
|
@@ -327,6 +333,7 @@ testData.interactive.buttons.forEach(button => {
|
|
|
327
333
|
```
|
|
328
334
|
|
|
329
335
|
### Content Analysis (Progressive Approach)
|
|
336
|
+
|
|
330
337
|
```typescript
|
|
331
338
|
// Get structure first
|
|
332
339
|
const structure = ProgressiveExtractor.extractStructure(document);
|
|
@@ -401,6 +408,7 @@ This library is designed to provide:
|
|
|
401
408
|
### How is this different from Cheerio or jsdom?
|
|
402
409
|
|
|
403
410
|
This library is **AI-optimized**:
|
|
411
|
+
|
|
404
412
|
- Outputs structured data designed for LLM consumption
|
|
405
413
|
- Provides ranked selectors with stability scores
|
|
406
414
|
- Progressive extraction minimizes token usage
|
|
@@ -421,6 +429,7 @@ const result = await page.evaluate(() => {
|
|
|
421
429
|
### How do selector rankings work?
|
|
422
430
|
|
|
423
431
|
Selectors are ranked by stability (higher = more reliable):
|
|
432
|
+
|
|
424
433
|
1. **ID selectors** (score: 100) - `#unique-id`
|
|
425
434
|
2. **data-testid** (score: 90) - `[data-testid="submit"]`
|
|
426
435
|
3. **ARIA** (score: 80) - `[role="button"][aria-label="Submit"]`
|
|
@@ -437,18 +446,19 @@ Progressive extraction can reduce token usage by 80-95% compared to raw HTML, de
|
|
|
437
446
|
|
|
438
447
|
## Comparison with Alternatives
|
|
439
448
|
|
|
440
|
-
| Feature
|
|
441
|
-
|
|
442
|
-
| AI-Optimized Output | Yes
|
|
443
|
-
| Ranked Selectors
|
|
444
|
-
| Token Efficiency
|
|
445
|
-
| Shadow DOM
|
|
446
|
-
| Browser Environment | Native
|
|
447
|
-
| Zero Dependencies
|
|
449
|
+
| Feature | @mcp-b/smart-dom-reader | Cheerio | jsdom | Raw DOM |
|
|
450
|
+
| ------------------- | ----------------------- | ---------- | --------- | ------- |
|
|
451
|
+
| AI-Optimized Output | Yes | No | No | No |
|
|
452
|
+
| Ranked Selectors | Yes | No | No | No |
|
|
453
|
+
| Token Efficiency | Progressive | N/A | N/A | N/A |
|
|
454
|
+
| Shadow DOM | Yes | No | Limited | Yes |
|
|
455
|
+
| Browser Environment | Native | Parse only | Simulated | Native |
|
|
456
|
+
| Zero Dependencies | Yes | No | No | Yes |
|
|
448
457
|
|
|
449
458
|
## Credits
|
|
450
459
|
|
|
451
460
|
Inspired by:
|
|
461
|
+
|
|
452
462
|
- [stacking-contexts-inspector](https://github.com/andreadev-it/stacking-contexts-inspector) - DOM traversal techniques
|
|
453
463
|
- [dom-to-semantic-markdown](https://github.com/romansky/dom-to-semantic-markdown) - Content scoring algorithms
|
|
454
464
|
- [z-context](https://github.com/gwwar/z-context) - Selector generation approaches
|
|
@@ -485,10 +495,10 @@ For AI agents, use the bundled MCP server which returns XML-wrapped Markdown ins
|
|
|
485
495
|
- Region: `<page ...>\n <section><![CDATA[ ...markdown... ]]></section>\n</page>`
|
|
486
496
|
- Content: `<page ...>\n <content><![CDATA[ ...markdown... ]]></content>\n</page>`
|
|
487
497
|
- Golden path sequence:
|
|
488
|
-
1
|
|
489
|
-
2
|
|
490
|
-
3
|
|
491
|
-
4
|
|
498
|
+
1. `dom_extract_structure` → get page outline and pick a target
|
|
499
|
+
2. `dom_extract_region` → get actionable selectors for that area
|
|
500
|
+
3. Write a script; if unstable, re-run with higher detail or limits
|
|
501
|
+
4. Optional: `dom_extract_content` for readable text context
|
|
492
502
|
|
|
493
503
|
### Running the server
|
|
494
504
|
|
|
@@ -534,6 +544,7 @@ pnpm --filter @mcp-b/smart-dom-reader test:local
|
|
|
534
544
|
```
|
|
535
545
|
|
|
536
546
|
What it validates:
|
|
547
|
+
|
|
537
548
|
- Stable selectors (ID, data-testid, role+aria, name/id)
|
|
538
549
|
- Semantic extraction (headings/images/tables/lists)
|
|
539
550
|
- Shadow DOM detection
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/bundle-string.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Auto-generated bundle module for smart-dom-reader
|
|
4
|
+
* DO NOT EDIT - Generated by generate-bundle-module.mjs
|
|
5
|
+
*
|
|
6
|
+
* This module exports the bundled smart-dom-reader library as a string
|
|
7
|
+
* that can be injected into web pages for stateless DOM extraction.
|
|
8
|
+
*/
|
|
9
|
+
declare const SMART_DOM_READER_BUNDLE = "var SmartDOMReaderBundle = (function(exports) {\n\tObject.defineProperty(exports, Symbol.toStringTag, { value: \"Module\" });\n\t//#region \\0rolldown/runtime.js\n\tvar __defProp = Object.defineProperty;\n\tvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\n\tvar __getOwnPropNames = Object.getOwnPropertyNames;\n\tvar __hasOwnProp = Object.prototype.hasOwnProperty;\n\tvar __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);\n\tvar __exportAll = (all, no_symbols) => {\n\t\tlet target = {};\n\t\tfor (var name in all) __defProp(target, name, {\n\t\t\tget: all[name],\n\t\t\tenumerable: true\n\t\t});\n\t\tif (!no_symbols) __defProp(target, Symbol.toStringTag, { value: \"Module\" });\n\t\treturn target;\n\t};\n\tvar __copyProps = (to, from, except, desc) => {\n\t\tif (from && typeof from === \"object\" || typeof from === \"function\") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {\n\t\t\tkey = keys[i];\n\t\t\tif (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {\n\t\t\t\tget: ((k) => from[k]).bind(null, key),\n\t\t\t\tenumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable\n\t\t\t});\n\t\t}\n\t\treturn to;\n\t};\n\tvar __toCommonJS = (mod) => __hasOwnProp.call(mod, \"module.exports\") ? mod[\"module.exports\"] : __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\t//#endregion\n\t//#region src/content-detection.ts\n\tvar ContentDetection;\n\tvar init_content_detection = __esmMin((() => {\n\t\tContentDetection = class ContentDetection {\n\t\t\t/**\n\t\t\t* Find the main content area of a page\n\t\t\t* Inspired by dom-to-semantic-markdown's approach\n\t\t\t*/\n\t\t\tstatic findMainContent(doc) {\n\t\t\t\tconst mainElement = doc.querySelector(\"main, [role=\\\"main\\\"]\");\n\t\t\t\tif (mainElement) return mainElement;\n\t\t\t\tif (!doc.body) return doc.documentElement;\n\t\t\t\treturn ContentDetection.detectMainContent(doc.body);\n\t\t\t}\n\t\t\t/**\n\t\t\t* Detect main content using scoring algorithm\n\t\t\t*/\n\t\t\tstatic detectMainContent(rootElement) {\n\t\t\t\tconst candidates = [];\n\t\t\t\tContentDetection.collectCandidates(rootElement, candidates, 15);\n\t\t\t\tif (candidates.length === 0) return rootElement;\n\t\t\t\tcandidates.sort((a, b) => ContentDetection.calculateContentScore(b) - ContentDetection.calculateContentScore(a));\n\t\t\t\tlet bestCandidate = candidates[0];\n\t\t\t\tfor (let i = 1; i < candidates.length; i++) {\n\t\t\t\t\tconst candidate = candidates[i];\n\t\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\t}\n\t\t\t\treturn bestCandidate;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Collect content candidates\n\t\t\t*/\n\t\t\tstatic collectCandidates(element, candidates, minScore) {\n\t\t\t\tif (ContentDetection.calculateContentScore(element) >= minScore) candidates.push(element);\n\t\t\t\tArray.from(element.children).forEach((child) => {\n\t\t\t\t\tContentDetection.collectCandidates(child, candidates, minScore);\n\t\t\t\t});\n\t\t\t}\n\t\t\t/**\n\t\t\t* Calculate content score for an element\n\t\t\t*/\n\t\t\tstatic calculateContentScore(element) {\n\t\t\t\tlet score = 0;\n\t\t\t\tconst semanticClasses = [\n\t\t\t\t\t\"article\",\n\t\t\t\t\t\"content\",\n\t\t\t\t\t\"main-container\",\n\t\t\t\t\t\"main\",\n\t\t\t\t\t\"main-content\",\n\t\t\t\t\t\"post\",\n\t\t\t\t\t\"entry\"\n\t\t\t\t];\n\t\t\t\tconst semanticIds = [\n\t\t\t\t\t\"content\",\n\t\t\t\t\t\"main\",\n\t\t\t\t\t\"article\",\n\t\t\t\t\t\"post\",\n\t\t\t\t\t\"entry\"\n\t\t\t\t];\n\t\t\t\tsemanticClasses.forEach((cls) => {\n\t\t\t\t\tif (element.classList.contains(cls)) score += 10;\n\t\t\t\t});\n\t\t\t\tsemanticIds.forEach((id) => {\n\t\t\t\t\tif (element.id?.toLowerCase().includes(id)) score += 10;\n\t\t\t\t});\n\t\t\t\tconst tag = element.tagName.toLowerCase();\n\t\t\t\tif ([\n\t\t\t\t\t\"article\",\n\t\t\t\t\t\"main\",\n\t\t\t\t\t\"section\"\n\t\t\t\t].includes(tag)) score += 8;\n\t\t\t\tconst paragraphs = element.getElementsByTagName(\"p\").length;\n\t\t\t\tscore += Math.min(paragraphs * 2, 10);\n\t\t\t\tconst headings = element.querySelectorAll(\"h1, h2, h3\").length;\n\t\t\t\tscore += Math.min(headings * 3, 9);\n\t\t\t\tconst textLength = element.textContent?.trim().length || 0;\n\t\t\t\tif (textLength > 300) score += Math.min(Math.floor(textLength / 300) * 2, 10);\n\t\t\t\tconst linkDensity = ContentDetection.calculateLinkDensity(element);\n\t\t\t\tif (linkDensity < .3) score += 5;\n\t\t\t\telse if (linkDensity > .5) score -= 5;\n\t\t\t\tif (element.hasAttribute(\"data-main\") || element.hasAttribute(\"data-content\") || element.hasAttribute(\"itemprop\")) score += 8;\n\t\t\t\tconst role = element.getAttribute(\"role\");\n\t\t\t\tif (role === \"main\" || role === \"article\") score += 10;\n\t\t\t\tif (element.matches(\"aside, nav, header, footer, .sidebar, .navigation, .menu, .ad, .advertisement\")) score -= 10;\n\t\t\t\tif (element.getElementsByTagName(\"form\").length > 2) score -= 5;\n\t\t\t\treturn Math.max(0, score);\n\t\t\t}\n\t\t\t/**\n\t\t\t* Calculate link density in an element\n\t\t\t*/\n\t\t\tstatic calculateLinkDensity(element) {\n\t\t\t\tconst links = element.getElementsByTagName(\"a\");\n\t\t\t\tlet linkTextLength = 0;\n\t\t\t\tfor (const link of Array.from(links)) linkTextLength += link.textContent?.length || 0;\n\t\t\t\tconst totalTextLength = element.textContent?.length || 1;\n\t\t\t\treturn linkTextLength / totalTextLength;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Check if an element is likely navigation\n\t\t\t*/\n\t\t\tstatic isNavigation(element) {\n\t\t\t\tif (element.tagName.toLowerCase() === \"nav\" || element.getAttribute(\"role\") === \"navigation\") return true;\n\t\t\t\tconst navPatterns = [\n\t\t\t\t\t/nav/i,\n\t\t\t\t\t/menu/i,\n\t\t\t\t\t/sidebar/i,\n\t\t\t\t\t/toolbar/i\n\t\t\t\t];\n\t\t\t\tconst classesAndId = `${element.className} ${element.id}`.toLowerCase();\n\t\t\t\treturn navPatterns.some((pattern) => pattern.test(classesAndId));\n\t\t\t}\n\t\t\t/**\n\t\t\t* Check if element is likely supplementary content\n\t\t\t*/\n\t\t\tstatic isSupplementary(element) {\n\t\t\t\tif (element.tagName.toLowerCase() === \"aside\" || element.getAttribute(\"role\") === \"complementary\") return true;\n\t\t\t\tconst supplementaryPatterns = [\n\t\t\t\t\t/sidebar/i,\n\t\t\t\t\t/widget/i,\n\t\t\t\t\t/related/i,\n\t\t\t\t\t/advertisement/i,\n\t\t\t\t\t/social/i\n\t\t\t\t];\n\t\t\t\tconst classesAndId = `${element.className} ${element.id}`.toLowerCase();\n\t\t\t\treturn supplementaryPatterns.some((pattern) => pattern.test(classesAndId));\n\t\t\t}\n\t\t\t/**\n\t\t\t* Detect page landmarks\n\t\t\t*/\n\t\t\tstatic detectLandmarks(doc) {\n\t\t\t\tconst landmarks = {\n\t\t\t\t\tnavigation: [],\n\t\t\t\t\tmain: [],\n\t\t\t\t\tcomplementary: [],\n\t\t\t\t\tcontentinfo: [],\n\t\t\t\t\tbanner: [],\n\t\t\t\t\tsearch: [],\n\t\t\t\t\tform: [],\n\t\t\t\t\tregion: []\n\t\t\t\t};\n\t\t\t\tfor (const [landmark, selector] of Object.entries({\n\t\t\t\t\tnavigation: \"nav, [role=\\\"navigation\\\"]\",\n\t\t\t\t\tmain: \"main, [role=\\\"main\\\"]\",\n\t\t\t\t\tcomplementary: \"aside, [role=\\\"complementary\\\"]\",\n\t\t\t\t\tcontentinfo: \"footer, [role=\\\"contentinfo\\\"]\",\n\t\t\t\t\tbanner: \"header, [role=\\\"banner\\\"]\",\n\t\t\t\t\tsearch: \"[role=\\\"search\\\"]\",\n\t\t\t\t\tform: \"form[aria-label], form[aria-labelledby], [role=\\\"form\\\"]\",\n\t\t\t\t\tregion: \"section[aria-label], section[aria-labelledby], [role=\\\"region\\\"]\"\n\t\t\t\t})) {\n\t\t\t\t\tconst elements = doc.querySelectorAll(selector);\n\t\t\t\t\tlandmarks[landmark] = Array.from(elements);\n\t\t\t\t}\n\t\t\t\treturn landmarks;\n\t\t\t}\n\t\t};\n\t}));\n\t//#endregion\n\t//#region src/selectors.ts\n\tvar SelectorGenerator;\n\tvar init_selectors = __esmMin((() => {\n\t\tSelectorGenerator = class SelectorGenerator {\n\t\t\t/**\n\t\t\t* Generate multiple selector strategies for an element\n\t\t\t*/\n\t\t\tstatic generateSelectors(element) {\n\t\t\t\tconst doc = element.ownerDocument || document;\n\t\t\t\tconst candidates = [];\n\t\t\t\tif (element.id && SelectorGenerator.isUniqueId(element.id, doc)) candidates.push({\n\t\t\t\t\ttype: \"id\",\n\t\t\t\t\tvalue: `#${CSS.escape(element.id)}`,\n\t\t\t\t\tscore: 100\n\t\t\t\t});\n\t\t\t\tconst testId = SelectorGenerator.getDataTestId(element);\n\t\t\t\tif (testId) {\n\t\t\t\t\tconst v = `[data-testid=\"${CSS.escape(testId)}\"]`;\n\t\t\t\t\tcandidates.push({\n\t\t\t\t\t\ttype: \"data-testid\",\n\t\t\t\t\t\tvalue: v,\n\t\t\t\t\t\tscore: 90 + (SelectorGenerator.isUniqueSelectorSafe(v, doc) ? 5 : 0)\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst role = element.getAttribute(\"role\");\n\t\t\t\tconst aria = element.getAttribute(\"aria-label\");\n\t\t\t\tif (role && aria) {\n\t\t\t\t\tconst v = `[role=\"${CSS.escape(role)}\"][aria-label=\"${CSS.escape(aria)}\"]`;\n\t\t\t\t\tcandidates.push({\n\t\t\t\t\t\ttype: \"role-aria\",\n\t\t\t\t\t\tvalue: v,\n\t\t\t\t\t\tscore: 85 + (SelectorGenerator.isUniqueSelectorSafe(v, doc) ? 5 : 0)\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst nameAttr = element.getAttribute(\"name\");\n\t\t\t\tif (nameAttr) {\n\t\t\t\t\tconst v = `[name=\"${CSS.escape(nameAttr)}\"]`;\n\t\t\t\t\tcandidates.push({\n\t\t\t\t\t\ttype: \"name\",\n\t\t\t\t\t\tvalue: v,\n\t\t\t\t\t\tscore: 78 + (SelectorGenerator.isUniqueSelectorSafe(v, doc) ? 5 : 0)\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst pathCss = SelectorGenerator.generateCSSSelector(element, doc);\n\t\t\t\tconst structuralPenalty = (pathCss.match(/:nth-child\\(/g) || []).length * 10;\n\t\t\t\tconst classBonus = pathCss.includes(\".\") ? 8 : 0;\n\t\t\t\tconst pathScore = Math.max(0, 70 + classBonus - structuralPenalty);\n\t\t\t\tcandidates.push({\n\t\t\t\t\ttype: \"class-path\",\n\t\t\t\t\tvalue: pathCss,\n\t\t\t\t\tscore: pathScore\n\t\t\t\t});\n\t\t\t\tconst xpath = SelectorGenerator.generateXPath(element, doc);\n\t\t\t\tcandidates.push({\n\t\t\t\t\ttype: \"xpath\",\n\t\t\t\t\tvalue: xpath,\n\t\t\t\t\tscore: 40\n\t\t\t\t});\n\t\t\t\tconst textBased = SelectorGenerator.generateTextBasedSelector(element);\n\t\t\t\tif (textBased) candidates.push({\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tvalue: textBased,\n\t\t\t\t\tscore: 30\n\t\t\t\t});\n\t\t\t\tcandidates.sort((a, b) => b.score - a.score);\n\t\t\t\tconst selector = {\n\t\t\t\t\tcss: candidates.find((c) => c.type !== \"xpath\" && c.type !== \"text\")?.value || pathCss,\n\t\t\t\t\txpath,\n\t\t\t\t\tcandidates\n\t\t\t\t};\n\t\t\t\tif (textBased) selector.textBased = textBased;\n\t\t\t\tif (testId) selector.dataTestId = testId;\n\t\t\t\tif (aria) selector.ariaLabel = aria;\n\t\t\t\treturn selector;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Generate a unique CSS selector for an element\n\t\t\t*/\n\t\t\tstatic generateCSSSelector(element, doc) {\n\t\t\t\tif (element.id && SelectorGenerator.isUniqueId(element.id, doc)) return `#${CSS.escape(element.id)}`;\n\t\t\t\tconst testId = SelectorGenerator.getDataTestId(element);\n\t\t\t\tif (testId) return `[data-testid=\"${CSS.escape(testId)}\"]`;\n\t\t\t\tconst path = [];\n\t\t\t\tlet current = element;\n\t\t\t\twhile (current && current.nodeType === Node.ELEMENT_NODE) {\n\t\t\t\t\tlet selector = current.nodeName.toLowerCase();\n\t\t\t\t\tif (current.id && SelectorGenerator.isUniqueId(current.id, doc)) {\n\t\t\t\t\t\tselector = `#${CSS.escape(current.id)}`;\n\t\t\t\t\t\tpath.unshift(selector);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tconst classes = SelectorGenerator.getMeaningfulClasses(current);\n\t\t\t\t\tif (classes.length > 0) selector += `.${classes.map((c) => CSS.escape(c)).join(\".\")}`;\n\t\t\t\t\tconst siblings = current.parentElement?.children;\n\t\t\t\t\tif (siblings && siblings.length > 1) {\n\t\t\t\t\t\tconst index = Array.from(siblings).indexOf(current);\n\t\t\t\t\t\tif (index > 0 || !SelectorGenerator.isUniqueSelector(selector, current.parentElement)) selector += `:nth-child(${index + 1})`;\n\t\t\t\t\t}\n\t\t\t\t\tpath.unshift(selector);\n\t\t\t\t\tcurrent = current.parentElement;\n\t\t\t\t}\n\t\t\t\treturn SelectorGenerator.optimizePath(path, element, doc);\n\t\t\t}\n\t\t\t/**\n\t\t\t* Generate XPath for an element\n\t\t\t*/\n\t\t\tstatic generateXPath(element, doc) {\n\t\t\t\tif (element.id && SelectorGenerator.isUniqueId(element.id, doc)) return `//*[@id=\"${element.id}\"]`;\n\t\t\t\tconst path = [];\n\t\t\t\tlet current = element;\n\t\t\t\twhile (current && current.nodeType === Node.ELEMENT_NODE) {\n\t\t\t\t\tconst tagName = current.nodeName.toLowerCase();\n\t\t\t\t\tif (current.id && SelectorGenerator.isUniqueId(current.id, doc)) {\n\t\t\t\t\t\tpath.unshift(`//*[@id=\"${current.id}\"]`);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tlet xpath = tagName;\n\t\t\t\t\tconst siblings = current.parentElement?.children;\n\t\t\t\t\tif (siblings) {\n\t\t\t\t\t\tconst sameTagSiblings = Array.from(siblings).filter((s) => s.nodeName.toLowerCase() === tagName);\n\t\t\t\t\t\tif (sameTagSiblings.length > 1) {\n\t\t\t\t\t\t\tconst index = sameTagSiblings.indexOf(current) + 1;\n\t\t\t\t\t\t\txpath += `[${index}]`;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpath.unshift(xpath);\n\t\t\t\t\tcurrent = current.parentElement;\n\t\t\t\t}\n\t\t\t\treturn `//${path.join(\"/\")}`;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Generate a text-based selector for buttons and links\n\t\t\t*/\n\t\t\tstatic generateTextBasedSelector(element) {\n\t\t\t\tconst text = element.textContent?.trim();\n\t\t\t\tif (!text || text.length > 50) return void 0;\n\t\t\t\tconst tag = element.nodeName.toLowerCase();\n\t\t\t\tif ([\n\t\t\t\t\t\"button\",\n\t\t\t\t\t\"a\",\n\t\t\t\t\t\"label\"\n\t\t\t\t].includes(tag)) return `${tag}:contains(\"${text.replace(/['\"\\\\]/g, \"\\\\$&\")}\")`;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Get data-testid or similar attributes\n\t\t\t*/\n\t\t\tstatic getDataTestId(element) {\n\t\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\t}\n\t\t\t/**\n\t\t\t* Check if an ID is unique in the document\n\t\t\t*/\n\t\t\tstatic isUniqueId(id, doc) {\n\t\t\t\treturn doc.querySelectorAll(`#${CSS.escape(id)}`).length === 1;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Check if a selector is unique within a container\n\t\t\t*/\n\t\t\tstatic isUniqueSelector(selector, container) {\n\t\t\t\ttry {\n\t\t\t\t\treturn container.querySelectorAll(selector).length === 1;\n\t\t\t\t} catch {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstatic isUniqueSelectorSafe(selector, doc) {\n\t\t\t\ttry {\n\t\t\t\t\treturn doc.querySelectorAll(selector).length === 1;\n\t\t\t\t} catch {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\t/**\n\t\t\t* Get meaningful classes (filtering out utility classes)\n\t\t\t*/\n\t\t\tstatic getMeaningfulClasses(element) {\n\t\t\t\tconst classes = Array.from(element.classList);\n\t\t\t\tconst utilityPatterns = [\n\t\t\t\t\t/^(p|m|w|h|text|bg|border|flex|grid|col|row)-/,\n\t\t\t\t\t/^(xs|sm|md|lg|xl|2xl):/,\n\t\t\t\t\t/^(hover|focus|active|disabled|checked):/,\n\t\t\t\t\t/^js-/,\n\t\t\t\t\t/^is-/,\n\t\t\t\t\t/^has-/\n\t\t\t\t];\n\t\t\t\treturn classes.filter((cls) => {\n\t\t\t\t\tif (cls.length < 3) return false;\n\t\t\t\t\treturn !utilityPatterns.some((pattern) => pattern.test(cls));\n\t\t\t\t}).slice(0, 2);\n\t\t\t}\n\t\t\t/**\n\t\t\t* Optimize the selector path by removing unnecessary parts\n\t\t\t*/\n\t\t\tstatic optimizePath(path, element, doc) {\n\t\t\t\tfor (let i = 0; i < path.length - 1; i++) {\n\t\t\t\t\tconst shortPath = path.slice(i).join(\" > \");\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst matches = doc.querySelectorAll(shortPath);\n\t\t\t\t\t\tif (matches.length === 1 && matches[0] === element) return shortPath;\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t\treturn path.join(\" > \");\n\t\t\t}\n\t\t\t/**\n\t\t\t* Get a human-readable path description\n\t\t\t*/\n\t\t\tstatic getContextPath(element) {\n\t\t\t\tconst path = [];\n\t\t\t\tlet current = element;\n\t\t\t\tlet depth = 0;\n\t\t\t\tconst maxDepth = 5;\n\t\t\t\twhile (current && current !== element.ownerDocument?.body && depth < maxDepth) {\n\t\t\t\t\tconst tag = current.nodeName.toLowerCase();\n\t\t\t\t\tlet descriptor = tag;\n\t\t\t\t\tif (current.id) descriptor = `${tag}#${current.id}`;\n\t\t\t\t\telse if (current.className && typeof current.className === \"string\") {\n\t\t\t\t\t\tconst firstClass = current.className.split(\" \")[0];\n\t\t\t\t\t\tif (firstClass) descriptor = `${tag}.${firstClass}`;\n\t\t\t\t\t}\n\t\t\t\t\tconst role = current.getAttribute(\"role\");\n\t\t\t\t\tif (role) descriptor += `[role=\"${role}\"]`;\n\t\t\t\t\tpath.unshift(descriptor);\n\t\t\t\t\tcurrent = current.parentElement;\n\t\t\t\t\tdepth++;\n\t\t\t\t}\n\t\t\t\treturn path;\n\t\t\t}\n\t\t};\n\t}));\n\t//#endregion\n\t//#region src/traversal.ts\n\tvar DOMTraversal;\n\tvar init_traversal = __esmMin((() => {\n\t\tinit_selectors();\n\t\tDOMTraversal = class DOMTraversal {\n\t\t\tstatic INTERACTIVE_SELECTORS = [\n\t\t\t\t\"button\",\n\t\t\t\t\"a[href]\",\n\t\t\t\t\"input:not([type=\\\"hidden\\\"])\",\n\t\t\t\t\"textarea\",\n\t\t\t\t\"select\",\n\t\t\t\t\"[role=\\\"button\\\"]\",\n\t\t\t\t\"[onclick]\",\n\t\t\t\t\"[contenteditable=\\\"true\\\"]\",\n\t\t\t\t\"summary\",\n\t\t\t\t\"[tabindex]:not([tabindex=\\\"-1\\\"])\"\n\t\t\t];\n\t\t\tstatic SEMANTIC_SELECTORS = [\n\t\t\t\t\"h1\",\n\t\t\t\t\"h2\",\n\t\t\t\t\"h3\",\n\t\t\t\t\"h4\",\n\t\t\t\t\"h5\",\n\t\t\t\t\"h6\",\n\t\t\t\t\"article\",\n\t\t\t\t\"section\",\n\t\t\t\t\"nav\",\n\t\t\t\t\"aside\",\n\t\t\t\t\"main\",\n\t\t\t\t\"header\",\n\t\t\t\t\"footer\",\n\t\t\t\t\"form\",\n\t\t\t\t\"table\",\n\t\t\t\t\"ul\",\n\t\t\t\t\"ol\",\n\t\t\t\t\"img[alt]\",\n\t\t\t\t\"figure\",\n\t\t\t\t\"video\",\n\t\t\t\t\"audio\",\n\t\t\t\t\"[role=\\\"navigation\\\"]\",\n\t\t\t\t\"[role=\\\"main\\\"]\",\n\t\t\t\t\"[role=\\\"complementary\\\"]\",\n\t\t\t\t\"[role=\\\"contentinfo\\\"]\"\n\t\t\t];\n\t\t\t/**\n\t\t\t* Check if element is visible\n\t\t\t*/\n\t\t\tstatic isVisible(element, computedStyle) {\n\t\t\t\tconst rect = element.getBoundingClientRect();\n\t\t\t\tconst style = computedStyle || element.ownerDocument?.defaultView?.getComputedStyle(element);\n\t\t\t\tif (!style) return false;\n\t\t\t\treturn !!(rect.width > 0 && rect.height > 0 && style.display !== \"none\" && style.visibility !== \"hidden\" && style.opacity !== \"0\" && element.offsetParent !== null);\n\t\t\t}\n\t\t\t/**\n\t\t\t* Check if element is in viewport\n\t\t\t*/\n\t\t\tstatic isInViewport(element, viewport) {\n\t\t\t\tconst rect = element.getBoundingClientRect();\n\t\t\t\tconst view = viewport || {\n\t\t\t\t\twidth: element.ownerDocument?.defaultView?.innerWidth || 0,\n\t\t\t\t\theight: element.ownerDocument?.defaultView?.innerHeight || 0\n\t\t\t\t};\n\t\t\t\treturn rect.top < view.height && rect.bottom > 0 && rect.left < view.width && rect.right > 0;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Check if element passes filter criteria\n\t\t\t*/\n\t\t\tstatic passesFilter(element, filter) {\n\t\t\t\tif (!filter) return true;\n\t\t\t\tconst htmlElement = element;\n\t\t\t\tif (filter.excludeSelectors?.length) {\n\t\t\t\t\tfor (const selector of filter.excludeSelectors) if (element.matches(selector)) return false;\n\t\t\t\t}\n\t\t\t\tif (filter.includeSelectors?.length) {\n\t\t\t\t\tlet matches = false;\n\t\t\t\t\tfor (const selector of filter.includeSelectors) if (element.matches(selector)) {\n\t\t\t\t\t\tmatches = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (!matches) return false;\n\t\t\t\t}\n\t\t\t\tif (filter.tags?.length && !filter.tags.includes(element.tagName.toLowerCase())) return false;\n\t\t\t\tconst textContent = htmlElement.textContent?.toLowerCase() || \"\";\n\t\t\t\tif (filter.textContains?.length) {\n\t\t\t\t\tlet hasText = false;\n\t\t\t\t\tfor (const text of filter.textContains) if (textContent.includes(text.toLowerCase())) {\n\t\t\t\t\t\thasText = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (!hasText) return false;\n\t\t\t\t}\n\t\t\t\tif (filter.textMatches?.length) {\n\t\t\t\t\tlet matches = false;\n\t\t\t\t\tfor (const pattern of filter.textMatches) if (pattern.test(textContent)) {\n\t\t\t\t\t\tmatches = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (!matches) return false;\n\t\t\t\t}\n\t\t\t\tif (filter.hasAttributes?.length) {\n\t\t\t\t\tfor (const attr of filter.hasAttributes) if (!element.hasAttribute(attr)) return false;\n\t\t\t\t}\n\t\t\t\tif (filter.attributeValues) for (const [attr, value] of Object.entries(filter.attributeValues)) {\n\t\t\t\t\tconst attrValue = element.getAttribute(attr);\n\t\t\t\t\tif (!attrValue) return false;\n\t\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\t\tif (attrValue !== value) return false;\n\t\t\t\t\t} else if (value instanceof RegExp) {\n\t\t\t\t\t\tif (!value.test(attrValue)) return false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (filter.withinSelectors?.length) {\n\t\t\t\t\tlet isWithin = false;\n\t\t\t\t\tfor (const selector of filter.withinSelectors) if (element.closest(selector)) {\n\t\t\t\t\t\tisWithin = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (!isWithin) return false;\n\t\t\t\t}\n\t\t\t\tif (filter.interactionTypes?.length) {\n\t\t\t\t\tconst interaction = DOMTraversal.getInteractionInfo(element);\n\t\t\t\t\tlet hasInteraction = false;\n\t\t\t\t\tfor (const type of filter.interactionTypes) if (interaction[type]) {\n\t\t\t\t\t\thasInteraction = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (!hasInteraction) return false;\n\t\t\t\t}\n\t\t\t\tif (filter.nearText) {\n\t\t\t\t\tconst parent = element.parentElement;\n\t\t\t\t\tif (!parent || !parent.textContent?.toLowerCase().includes(filter.nearText.toLowerCase())) return false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Extract element information\n\t\t\t*/\n\t\t\tstatic extractElement(element, options, depth = 0) {\n\t\t\t\tif (options.maxDepth && depth > options.maxDepth) return null;\n\t\t\t\tif (!options.includeHidden && !DOMTraversal.isVisible(element)) return null;\n\t\t\t\tif (options.viewportOnly && !DOMTraversal.isInViewport(element)) return null;\n\t\t\t\tif (!DOMTraversal.passesFilter(element, options.filter)) return null;\n\t\t\t\tconst htmlElement = element;\n\t\t\t\tconst extracted = {\n\t\t\t\t\ttag: element.tagName.toLowerCase(),\n\t\t\t\t\ttext: DOMTraversal.getElementText(element, options),\n\t\t\t\t\tselector: SelectorGenerator.generateSelectors(element),\n\t\t\t\t\tattributes: DOMTraversal.getRelevantAttributes(element, options),\n\t\t\t\t\tcontext: DOMTraversal.getElementContext(element),\n\t\t\t\t\tinteraction: DOMTraversal.getInteractionInfo(element)\n\t\t\t\t};\n\t\t\t\tif (options.mode === \"full\" && DOMTraversal.isSemanticContainer(element)) {\n\t\t\t\t\tconst children = [];\n\t\t\t\t\tif (options.includeShadowDOM && htmlElement.shadowRoot) {\n\t\t\t\t\t\tconst shadowChildren = DOMTraversal.extractChildren(htmlElement.shadowRoot, options, depth + 1);\n\t\t\t\t\t\tchildren.push(...shadowChildren);\n\t\t\t\t\t}\n\t\t\t\t\tconst regularChildren = DOMTraversal.extractChildren(element, options, depth + 1);\n\t\t\t\t\tchildren.push(...regularChildren);\n\t\t\t\t\tif (children.length > 0) extracted.children = children;\n\t\t\t\t}\n\t\t\t\treturn extracted;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Extract children elements\n\t\t\t*/\n\t\t\tstatic extractChildren(container, options, depth) {\n\t\t\t\tconst children = [];\n\t\t\t\tconst elements = container.querySelectorAll(\"*\");\n\t\t\t\tfor (const child of Array.from(elements)) {\n\t\t\t\t\tif (DOMTraversal.hasExtractedAncestor(child, elements)) continue;\n\t\t\t\t\tconst extracted = DOMTraversal.extractElement(child, options, depth);\n\t\t\t\t\tif (extracted) children.push(extracted);\n\t\t\t\t}\n\t\t\t\treturn children;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Check if element has an ancestor that was already extracted\n\t\t\t*/\n\t\t\tstatic hasExtractedAncestor(element, extractedElements) {\n\t\t\t\tlet parent = element.parentElement;\n\t\t\t\twhile (parent) {\n\t\t\t\t\tif (Array.from(extractedElements).includes(parent)) return true;\n\t\t\t\t\tparent = parent.parentElement;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Get relevant attributes for an element\n\t\t\t*/\n\t\t\tstatic getRelevantAttributes(element, options) {\n\t\t\t\tconst relevant = [\n\t\t\t\t\t\"id\",\n\t\t\t\t\t\"class\",\n\t\t\t\t\t\"name\",\n\t\t\t\t\t\"type\",\n\t\t\t\t\t\"value\",\n\t\t\t\t\t\"placeholder\",\n\t\t\t\t\t\"href\",\n\t\t\t\t\t\"src\",\n\t\t\t\t\t\"alt\",\n\t\t\t\t\t\"title\",\n\t\t\t\t\t\"action\",\n\t\t\t\t\t\"method\",\n\t\t\t\t\t\"aria-label\",\n\t\t\t\t\t\"aria-describedby\",\n\t\t\t\t\t\"aria-controls\",\n\t\t\t\t\t\"role\",\n\t\t\t\t\t\"disabled\",\n\t\t\t\t\t\"readonly\",\n\t\t\t\t\t\"required\",\n\t\t\t\t\t\"checked\",\n\t\t\t\t\t\"min\",\n\t\t\t\t\t\"max\",\n\t\t\t\t\t\"pattern\",\n\t\t\t\t\t\"step\",\n\t\t\t\t\t\"autocomplete\",\n\t\t\t\t\t\"data-testid\",\n\t\t\t\t\t\"data-test\",\n\t\t\t\t\t\"data-cy\"\n\t\t\t\t];\n\t\t\t\tconst attributes = {};\n\t\t\t\tconst attrTruncate = options.attributeTruncateLength ?? 100;\n\t\t\t\tconst dataAttrTruncate = options.dataAttributeTruncateLength ?? 50;\n\t\t\t\tfor (const attr of relevant) {\n\t\t\t\t\tconst value = element.getAttribute(attr);\n\t\t\t\t\tif (value) attributes[attr] = value.length > attrTruncate ? `${value.substring(0, attrTruncate)}...` : value;\n\t\t\t\t}\n\t\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\t\treturn attributes;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Get element context information\n\t\t\t*/\n\t\t\tstatic getElementContext(element) {\n\t\t\t\tconst context = { parentChain: SelectorGenerator.getContextPath(element) };\n\t\t\t\tconst form = element.closest(\"form\");\n\t\t\t\tif (form) context.nearestForm = SelectorGenerator.generateSelectors(form).css;\n\t\t\t\tconst section = element.closest(\"section, [role=\\\"region\\\"]\");\n\t\t\t\tif (section) context.nearestSection = SelectorGenerator.generateSelectors(section).css;\n\t\t\t\tconst main = element.closest(\"main, [role=\\\"main\\\"]\");\n\t\t\t\tif (main) context.nearestMain = SelectorGenerator.generateSelectors(main).css;\n\t\t\t\tconst nav = element.closest(\"nav, [role=\\\"navigation\\\"]\");\n\t\t\t\tif (nav) context.nearestNav = SelectorGenerator.generateSelectors(nav).css;\n\t\t\t\treturn context;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Get interaction information for an element (compact format)\n\t\t\t*/\n\t\t\tstatic getInteractionInfo(element) {\n\t\t\t\tconst htmlElement = element;\n\t\t\t\tconst interaction = {};\n\t\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\t\tif (!!(htmlElement.onchange || element.getAttribute(\"onchange\") || element.matches(\"input, select, textarea\"))) interaction.change = true;\n\t\t\t\tif (!!(htmlElement.onsubmit || element.getAttribute(\"onsubmit\") || element.matches(\"form\"))) interaction.submit = true;\n\t\t\t\tif (element.matches(\"a[href], button[type=\\\"submit\\\"]\")) interaction.nav = true;\n\t\t\t\tif (htmlElement.hasAttribute(\"disabled\") || htmlElement.getAttribute(\"aria-disabled\") === \"true\") interaction.disabled = true;\n\t\t\t\tif (!DOMTraversal.isVisible(element)) interaction.hidden = true;\n\t\t\t\tconst ariaRole = element.getAttribute(\"role\");\n\t\t\t\tif (ariaRole) interaction.role = ariaRole;\n\t\t\t\tif (element.matches(\"input, textarea, select, button\")) {\n\t\t\t\t\tconst form = element.form || element.closest(\"form\");\n\t\t\t\t\tif (form) interaction.form = SelectorGenerator.generateSelectors(form).css;\n\t\t\t\t}\n\t\t\t\treturn interaction;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Get text content of an element (limited length)\n\t\t\t*/\n\t\t\tstatic getElementText(element, options) {\n\t\t\t\tif (element.matches(\"input, textarea\")) {\n\t\t\t\t\tconst input = element;\n\t\t\t\t\treturn input.value || input.placeholder || \"\";\n\t\t\t\t}\n\t\t\t\tif (element.matches(\"img\")) return element.alt || \"\";\n\t\t\t\tconst text = element.textContent?.trim() || \"\";\n\t\t\t\tconst maxLength = options?.textTruncateLength;\n\t\t\t\tif (maxLength && text.length > maxLength) return `${text.substring(0, maxLength)}...`;\n\t\t\t\treturn text;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Check if element is a semantic container\n\t\t\t*/\n\t\t\tstatic isSemanticContainer(element) {\n\t\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\t}\n\t\t\t/**\n\t\t\t* Get interactive elements\n\t\t\t*/\n\t\t\tstatic getInteractiveElements(container, options) {\n\t\t\t\tconst elements = [];\n\t\t\t\tconst selector = DOMTraversal.INTERACTIVE_SELECTORS.join(\", \");\n\t\t\t\tconst found = container.querySelectorAll(selector);\n\t\t\t\tfor (const element of Array.from(found)) {\n\t\t\t\t\tconst extracted = DOMTraversal.extractElement(element, options);\n\t\t\t\t\tif (extracted) elements.push(extracted);\n\t\t\t\t}\n\t\t\t\tif (options.customSelectors) for (const customSelector of options.customSelectors) try {\n\t\t\t\t\tconst customFound = container.querySelectorAll(customSelector);\n\t\t\t\t\tfor (const element of Array.from(customFound)) {\n\t\t\t\t\t\tconst extracted = DOMTraversal.extractElement(element, options);\n\t\t\t\t\t\tif (extracted) elements.push(extracted);\n\t\t\t\t\t}\n\t\t\t\t} catch (_e) {\n\t\t\t\t\tconsole.warn(`Invalid custom selector: ${customSelector}`);\n\t\t\t\t}\n\t\t\t\treturn elements;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Get semantic elements (for full mode)\n\t\t\t*/\n\t\t\tstatic getSemanticElements(container, options) {\n\t\t\t\tconst elements = [];\n\t\t\t\tconst selector = DOMTraversal.SEMANTIC_SELECTORS.join(\", \");\n\t\t\t\tconst found = container.querySelectorAll(selector);\n\t\t\t\tfor (const element of Array.from(found)) {\n\t\t\t\t\tconst extracted = DOMTraversal.extractElement(element, options);\n\t\t\t\t\tif (extracted) elements.push(extracted);\n\t\t\t\t}\n\t\t\t\treturn elements;\n\t\t\t}\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()} \u2026 ${t.slice(start, end).trim()}\u2026`;\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}\u2026`;\n\t}\n\tfunction bestSelector(el) {\n\t\treturn el.selector?.css || \"\";\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 \"\uD83E\uDDED\";\n\t\t\tcase \"navigation\": return \"\uD83D\uDCD1\";\n\t\t\tcase \"main\": return \"\uD83D\uDCC4\";\n\t\t\tcase \"sections\": return \"\uD83D\uDDC2\uFE0F\";\n\t\t\tcase \"sidebar\": return \"\uD83D\uDCDA\";\n\t\t\tcase \"footer\": return \"\uD83D\uDD3B\";\n\t\t\tcase \"modals\": return \"\uD83D\uDCAC\";\n\t\t\tdefault: return \"\uD83D\uDD39\";\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 = bestSelector(el);\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)\"} \u2192 \\`${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 ?? \"-\"} \u2192 \\`${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(`\u201C${truncate(region.textPreview, 80)}\u201D`);\n\t\tconst statsLine = stats.length ? ` \u2014 ${stats.join(\", \")}` : \"\";\n\t\treturn `${icon} ${label} \u2192 \\`${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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n\t}\n\tfunction capitalize(s) {\n\t\treturn s.charAt(0).toUpperCase() + s.slice(1);\n\t}\n\tvar MarkdownFormatter;\n\tvar init_markdown_formatter = __esmMin((() => {\n\t\tMarkdownFormatter = class {\n\t\t\tstatic structure(overview, _opts = {}, meta) {\n\t\t\t\tconst lines = [];\n\t\t\t\tlines.push(\"# Page Outline\");\n\t\t\t\tif (meta?.title || meta?.url) {\n\t\t\t\t\tlines.push(`Title: ${meta?.title ?? \"\"}`.trim());\n\t\t\t\t\tlines.push(`URL: ${meta?.url ?? \"\"}`.trim());\n\t\t\t\t}\n\t\t\t\tlines.push(\"\");\n\t\t\t\tconst regions = overview.regions;\n\t\t\t\tconst entries = [\n\t\t\t\t\t[\"header\", regions.header],\n\t\t\t\t\t[\"navigation\", regions.navigation],\n\t\t\t\t\t[\"main\", regions.main],\n\t\t\t\t\t[\"sections\", regions.sections],\n\t\t\t\t\t[\"sidebar\", regions.sidebar],\n\t\t\t\t\t[\"footer\", regions.footer],\n\t\t\t\t\t[\"modals\", regions.modals]\n\t\t\t\t];\n\t\t\t\tfor (const [key, value] of entries) {\n\t\t\t\t\tif (!value) continue;\n\t\t\t\t\tconst icon = iconForRegion(key);\n\t\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\t\tif (!value.length) continue;\n\t\t\t\t\t\tlines.push(`## ${icon} ${capitalize(key)}`);\n\t\t\t\t\t\tfor (const region of value) lines.push(renderRegionInfo(region));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlines.push(`## ${icon} ${capitalize(key)}`);\n\t\t\t\t\t\tlines.push(renderRegionInfo(value));\n\t\t\t\t\t}\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t}\n\t\t\t\tif (overview.suggestions?.length) {\n\t\t\t\t\tlines.push(\"## Suggestions\");\n\t\t\t\t\tfor (const s of overview.suggestions) lines.push(`- ${s}`);\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t}\n\t\t\t\tlines.push(\"Next: choose a region (by selector or [sectionId]) and call dom_extract_region for actionable details.\");\n\t\t\t\treturn wrapXml(lines.join(\"\\n\"), meta, \"outline\");\n\t\t\t}\n\t\t\tstatic region(result, opts = {}, meta) {\n\t\t\t\tconst lines = [];\n\t\t\t\tlines.push(\"# Region Details\");\n\t\t\t\tif (meta?.title || meta?.url) {\n\t\t\t\t\tlines.push(`Title: ${meta?.title ?? \"\"}`.trim());\n\t\t\t\t\tlines.push(`URL: ${meta?.url ?? \"\"}`.trim());\n\t\t\t\t}\n\t\t\t\tlines.push(\"\");\n\t\t\t\tconst inter = result.interactive;\n\t\t\t\tif (result.page) {\n\t\t\t\t\tconst ps = [\n\t\t\t\t\t\tresult.page.hasErrors ? \"errors: yes\" : \"errors: no\",\n\t\t\t\t\t\tresult.page.isLoading ? \"loading: yes\" : \"loading: no\",\n\t\t\t\t\t\tresult.page.hasModals ? \"modals: yes\" : \"modals: no\"\n\t\t\t\t\t];\n\t\t\t\t\tlines.push(`Page state: ${ps.join(\", \")}`);\n\t\t\t\t}\n\t\t\t\tconst summary = [];\n\t\t\t\tconst count = (arr) => arr ? arr.length : 0;\n\t\t\t\tsummary.push(`${count(inter.buttons)} buttons`);\n\t\t\t\tsummary.push(`${count(inter.links)} links`);\n\t\t\t\tsummary.push(`${count(inter.inputs)} inputs`);\n\t\t\t\tif (inter.forms?.length) summary.push(`${count(inter.forms)} forms`);\n\t\t\t\tlines.push(`Summary: ${summary.join(\", \")}`);\n\t\t\t\tlines.push(selectorQualitySummary(inter));\n\t\t\t\tlines.push(\"\");\n\t\t\t\tlines.push(renderInteractive(inter, opts));\n\t\t\t\tlines.push(\"\");\n\t\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\t\treturn wrapXml(lines.join(\"\\n\"), meta, \"section\");\n\t\t\t}\n\t\t\tstatic content(content, opts = {}, meta) {\n\t\t\t\tconst lines = [];\n\t\t\t\tlines.push(\"# Content\");\n\t\t\t\tlines.push(`Selector: \\`${content.selector}\\``);\n\t\t\t\tlines.push(\"\");\n\t\t\t\tif (content.text.headings?.length) {\n\t\t\t\t\tlines.push(\"Headings:\");\n\t\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\t\tlines.push(\"\");\n\t\t\t\t}\n\t\t\t\tif (content.text.paragraphs?.length) {\n\t\t\t\t\tconst limit = typeof opts.maxElements === \"number\" ? opts.maxElements : content.text.paragraphs.length;\n\t\t\t\t\tlines.push(\"Paragraphs:\");\n\t\t\t\t\tfor (const p of content.text.paragraphs.slice(0, limit)) lines.push(`- ${truncate(p, opts.maxTextLength ?? 200)}`);\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t}\n\t\t\t\tif (content.text.lists?.length) {\n\t\t\t\t\tlines.push(\"Lists:\");\n\t\t\t\t\tfor (const list of content.text.lists) {\n\t\t\t\t\t\tlines.push(`- ${list.type.toUpperCase()}:`);\n\t\t\t\t\t\tconst limit = typeof opts.maxElements === \"number\" ? opts.maxElements : list.items.length;\n\t\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\t}\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t}\n\t\t\t\tif (content.tables?.length) {\n\t\t\t\t\tlines.push(\"Tables:\");\n\t\t\t\t\tfor (const t of content.tables) {\n\t\t\t\t\t\tlines.push(`- Headers: ${t.headers.join(\" | \")}`);\n\t\t\t\t\t\tconst limit = typeof opts.maxElements === \"number\" ? opts.maxElements : t.rows.length;\n\t\t\t\t\t\tfor (const row of t.rows.slice(0, limit)) lines.push(` - ${row.join(\" | \")}`);\n\t\t\t\t\t}\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t}\n\t\t\t\tif (content.media?.length) {\n\t\t\t\t\tlines.push(\"Media:\");\n\t\t\t\t\tconst limit = typeof opts.maxElements === \"number\" ? opts.maxElements : content.media.length;\n\t\t\t\t\tfor (const m of content.media.slice(0, limit)) lines.push(`- ${m.type.toUpperCase()}: ${m.alt ?? \"\"} ${m.src ? `\u2192 ${m.src}` : \"\"}`.trim());\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t}\n\t\t\t\tlines.push(\"Next: if text is insufficient for targeting, call dom_extract_region for interactive selectors.\");\n\t\t\t\treturn wrapXml(lines.join(\"\\n\"), meta, \"content\");\n\t\t\t}\n\t\t};\n\t}));\n\t//#endregion\n\t//#region src/progressive.ts\n\tfunction resolveSmartDomReader() {\n\t\tif (typeof window !== \"undefined\") {\n\t\t\tconst globalWindow = window;\n\t\t\tconst direct = globalWindow.SmartDOMReader;\n\t\t\tif (typeof direct === \"function\") return direct;\n\t\t\tconst namespace = globalWindow.SmartDOMReaderNamespace;\n\t\t\tif (namespace && typeof namespace.SmartDOMReader === \"function\") return namespace.SmartDOMReader;\n\t\t}\n\t\ttry {\n\t\t\tif (typeof require === \"function\") {\n\t\t\t\tconst moduleExports = (init_src(), __toCommonJS(src_exports));\n\t\t\t\tif (moduleExports && typeof moduleExports.SmartDOMReader === \"function\") return moduleExports.SmartDOMReader;\n\t\t\t\tif (moduleExports && typeof moduleExports.default === \"function\") return moduleExports.default;\n\t\t\t}\n\t\t} catch {}\n\t}\n\tvar ProgressiveExtractor;\n\tvar init_progressive = __esmMin((() => {\n\t\tinit_content_detection();\n\t\tinit_selectors();\n\t\tinit_traversal();\n\t\tProgressiveExtractor = class ProgressiveExtractor {\n\t\t\t/**\n\t\t\t* Step 1: Extract high-level structural overview\n\t\t\t* This provides a \"map\" of the page for the AI to understand structure\n\t\t\t*/\n\t\t\tstatic extractStructure(root) {\n\t\t\t\tconst regions = {};\n\t\t\t\tconst header = root.querySelector(\"header, [role=\\\"banner\\\"], .header, #header\");\n\t\t\t\tif (header) regions.header = ProgressiveExtractor.analyzeRegion(header);\n\t\t\t\tconst navs = root.querySelectorAll(\"nav, [role=\\\"navigation\\\"], .nav, .navigation\");\n\t\t\t\tif (navs.length > 0) regions.navigation = Array.from(navs).map((nav) => ProgressiveExtractor.analyzeRegion(nav));\n\t\t\t\tif (root instanceof Document) {\n\t\t\t\t\tconst main = ContentDetection.findMainContent(root);\n\t\t\t\t\tif (main) {\n\t\t\t\t\t\tregions.main = ProgressiveExtractor.analyzeRegion(main);\n\t\t\t\t\t\tconst sections = main.querySelectorAll(\"section, article, [role=\\\"region\\\"]\");\n\t\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\t}\n\t\t\t\t} else {\n\t\t\t\t\tregions.main = ProgressiveExtractor.analyzeRegion(root);\n\t\t\t\t\tconst sections = root.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\tconst sidebars = root.querySelectorAll(\"aside, [role=\\\"complementary\\\"], .sidebar, #sidebar\");\n\t\t\t\tif (sidebars.length > 0) regions.sidebar = Array.from(sidebars).map((sidebar) => ProgressiveExtractor.analyzeRegion(sidebar));\n\t\t\t\tconst footer = root.querySelector(\"footer, [role=\\\"contentinfo\\\"], .footer, #footer\");\n\t\t\t\tif (footer) regions.footer = ProgressiveExtractor.analyzeRegion(footer);\n\t\t\t\tconst modals = root.querySelectorAll(\"[role=\\\"dialog\\\"], .modal, .popup, .overlay\");\n\t\t\t\tconst visibleModals = Array.from(modals).filter((modal) => DOMTraversal.isVisible(modal));\n\t\t\t\tif (visibleModals.length > 0) regions.modals = visibleModals.map((modal) => ProgressiveExtractor.analyzeRegion(modal));\n\t\t\t\tconst forms = ProgressiveExtractor.extractFormOverview(root);\n\t\t\t\tconst summary = ProgressiveExtractor.calculateSummary(root, regions, forms);\n\t\t\t\treturn {\n\t\t\t\t\tregions,\n\t\t\t\t\tforms,\n\t\t\t\t\tsummary,\n\t\t\t\t\tsuggestions: ProgressiveExtractor.generateSuggestions(regions, summary)\n\t\t\t\t};\n\t\t\t}\n\t\t\t/**\n\t\t\t* Step 2: Extract detailed information from a specific region\n\t\t\t*/\n\t\t\tstatic extractRegion(selector, doc, options = {}, smartDomReaderCtor) {\n\t\t\t\tconst element = doc.querySelector(selector);\n\t\t\t\tif (!element) return null;\n\t\t\t\tconst SmartDOMReaderCtor = smartDomReaderCtor ?? resolveSmartDomReader();\n\t\t\t\tif (!SmartDOMReaderCtor) throw new Error(\"SmartDOMReader is unavailable. Ensure the Smart DOM Reader module is loaded before calling extractRegion.\");\n\t\t\t\treturn new SmartDOMReaderCtor(options).extract(element, options);\n\t\t\t}\n\t\t\t/**\n\t\t\t* Step 3: Extract readable content from a region\n\t\t\t*/\n\t\t\tstatic extractContent(selector, doc, options = {}) {\n\t\t\t\tconst element = doc.querySelector(selector);\n\t\t\t\tif (!element) return null;\n\t\t\t\tconst result = {\n\t\t\t\t\tselector,\n\t\t\t\t\ttext: {},\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\twordCount: 0,\n\t\t\t\t\t\thasInteractive: false\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tif (options.includeHeadings !== false) {\n\t\t\t\t\tconst headings = element.querySelectorAll(\"h1, h2, h3, h4, h5, h6\");\n\t\t\t\t\tresult.text.headings = Array.from(headings).map((h) => ({\n\t\t\t\t\t\tlevel: Number.parseInt(h.tagName[1], 10),\n\t\t\t\t\t\ttext: ProgressiveExtractor.getTextContent(h, options.maxTextLength)\n\t\t\t\t\t}));\n\t\t\t\t}\n\t\t\t\tconst paragraphs = element.querySelectorAll(\"p\");\n\t\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\t\tif (options.includeLists !== false) {\n\t\t\t\t\tconst lists = element.querySelectorAll(\"ul, ol\");\n\t\t\t\t\tresult.text.lists = Array.from(lists).map((list) => ({\n\t\t\t\t\t\ttype: list.tagName.toLowerCase(),\n\t\t\t\t\t\titems: Array.from(list.querySelectorAll(\"li\")).map((li) => ProgressiveExtractor.getTextContent(li, options.maxTextLength))\n\t\t\t\t\t}));\n\t\t\t\t}\n\t\t\t\tif (options.includeTables !== false) {\n\t\t\t\t\tconst tables = element.querySelectorAll(\"table\");\n\t\t\t\t\tresult.tables = Array.from(tables).map((table) => {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\theaders: Array.from(table.querySelectorAll(\"th\")).map((th) => ProgressiveExtractor.getTextContent(th)),\n\t\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\t};\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (options.includeMedia !== false) {\n\t\t\t\t\tconst images = element.querySelectorAll(\"img\");\n\t\t\t\t\tconst videos = element.querySelectorAll(\"video\");\n\t\t\t\t\tconst audios = element.querySelectorAll(\"audio\");\n\t\t\t\t\tresult.media = [\n\t\t\t\t\t\t...Array.from(images).map((img) => {\n\t\t\t\t\t\t\tconst item = { type: \"img\" };\n\t\t\t\t\t\t\tconst alt = img.getAttribute(\"alt\");\n\t\t\t\t\t\t\tconst src = img.getAttribute(\"src\");\n\t\t\t\t\t\t\tif (alt) item.alt = alt;\n\t\t\t\t\t\t\tif (src) item.src = src;\n\t\t\t\t\t\t\treturn item;\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t...Array.from(videos).map((video) => {\n\t\t\t\t\t\t\tconst item = { type: \"video\" };\n\t\t\t\t\t\t\tconst src = video.getAttribute(\"src\");\n\t\t\t\t\t\t\tif (src) item.src = src;\n\t\t\t\t\t\t\treturn item;\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t...Array.from(audios).map((audio) => {\n\t\t\t\t\t\t\tconst item = { type: \"audio\" };\n\t\t\t\t\t\t\tconst src = audio.getAttribute(\"src\");\n\t\t\t\t\t\t\tif (src) item.src = src;\n\t\t\t\t\t\t\treturn item;\n\t\t\t\t\t\t})\n\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\tconst allText = element.textContent || \"\";\n\t\t\t\tresult.metadata.wordCount = allText.trim().split(/\\s+/).length;\n\t\t\t\tresult.metadata.hasInteractive = element.querySelectorAll(\"button, a, input, textarea, select\").length > 0;\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Analyze a region and extract summary information\n\t\t\t*/\n\t\t\tstatic analyzeRegion(element) {\n\t\t\t\tconst selector = SelectorGenerator.generateSelectors(element).css;\n\t\t\t\tconst buttons = element.querySelectorAll(\"button, [role=\\\"button\\\"]\");\n\t\t\t\tconst links = element.querySelectorAll(\"a[href]\");\n\t\t\t\tconst inputs = element.querySelectorAll(\"input, textarea, select\");\n\t\t\t\tconst forms = element.querySelectorAll(\"form\");\n\t\t\t\tconst lists = element.querySelectorAll(\"ul, ol\");\n\t\t\t\tconst tables = element.querySelectorAll(\"table\");\n\t\t\t\tconst media = element.querySelectorAll(\"img, video, audio\");\n\t\t\t\tconst interactiveCount = buttons.length + links.length + inputs.length;\n\t\t\t\tlet label;\n\t\t\t\tconst ariaLabel = element.getAttribute(\"aria-label\");\n\t\t\t\tif (ariaLabel) label = ariaLabel;\n\t\t\t\telse if (element.getAttribute(\"aria-labelledby\")) {\n\t\t\t\t\tconst labelId = element.getAttribute(\"aria-labelledby\");\n\t\t\t\t\tif (labelId) {\n\t\t\t\t\t\tconst labelElement = element.ownerDocument?.getElementById(labelId);\n\t\t\t\t\t\tif (labelElement) label = labelElement.textContent?.trim();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst heading = element.querySelector(\"h1, h2, h3\");\n\t\t\t\t\tif (heading) label = heading.textContent?.trim();\n\t\t\t\t}\n\t\t\t\tconst textContent = element.textContent?.trim() || \"\";\n\t\t\t\tconst textPreview = textContent.length > 50 ? `${textContent.substring(0, 50)}...` : textContent;\n\t\t\t\tconst regionInfo = {\n\t\t\t\t\tselector,\n\t\t\t\t\tinteractiveCount,\n\t\t\t\t\thasForm: forms.length > 0,\n\t\t\t\t\thasList: lists.length > 0,\n\t\t\t\t\thasTable: tables.length > 0,\n\t\t\t\t\thasMedia: media.length > 0\n\t\t\t\t};\n\t\t\t\tif (label) regionInfo.label = label;\n\t\t\t\tconst role = element.getAttribute(\"role\");\n\t\t\t\tif (role) regionInfo.role = role;\n\t\t\t\tif (buttons.length > 0) regionInfo.buttonCount = buttons.length;\n\t\t\t\tif (links.length > 0) regionInfo.linkCount = links.length;\n\t\t\t\tif (inputs.length > 0) regionInfo.inputCount = inputs.length;\n\t\t\t\tif (textPreview.length > 0) regionInfo.textPreview = textPreview;\n\t\t\t\treturn regionInfo;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Extract overview of forms on the page\n\t\t\t*/\n\t\t\tstatic extractFormOverview(root) {\n\t\t\t\tconst forms = root.querySelectorAll(\"form\");\n\t\t\t\treturn Array.from(forms).map((form) => {\n\t\t\t\t\tconst inputs = form.querySelectorAll(\"input, textarea, select\");\n\t\t\t\t\tconst selector = SelectorGenerator.generateSelectors(form).css;\n\t\t\t\t\tlet location = \"unknown\";\n\t\t\t\t\tif (form.closest(\"header, [role=\\\"banner\\\"]\")) location = \"header\";\n\t\t\t\t\telse if (form.closest(\"nav, [role=\\\"navigation\\\"]\")) location = \"navigation\";\n\t\t\t\t\telse if (form.closest(\"main, [role=\\\"main\\\"]\")) location = \"main\";\n\t\t\t\t\telse if (form.closest(\"aside, [role=\\\"complementary\\\"]\")) location = \"sidebar\";\n\t\t\t\t\telse if (form.closest(\"footer, [role=\\\"contentinfo\\\"]\")) location = \"footer\";\n\t\t\t\t\tlet purpose;\n\t\t\t\t\tconst formId = form.getAttribute(\"id\")?.toLowerCase();\n\t\t\t\t\tconst formClass = form.getAttribute(\"class\")?.toLowerCase();\n\t\t\t\t\tconst formAction = form.getAttribute(\"action\")?.toLowerCase();\n\t\t\t\t\tconst hasEmail = form.querySelector(\"input[type=\\\"email\\\"]\");\n\t\t\t\t\tconst hasPassword = form.querySelector(\"input[type=\\\"password\\\"]\");\n\t\t\t\t\tif (form.querySelector(\"input[type=\\\"search\\\"]\") || formId?.includes(\"search\") || formClass?.includes(\"search\")) purpose = \"search\";\n\t\t\t\t\telse if (hasPassword && hasEmail) purpose = \"login\";\n\t\t\t\t\telse if (hasPassword) purpose = \"authentication\";\n\t\t\t\t\telse if (formId?.includes(\"contact\") || formClass?.includes(\"contact\")) purpose = \"contact\";\n\t\t\t\t\telse if (formId?.includes(\"subscribe\") || formClass?.includes(\"subscribe\")) purpose = \"subscription\";\n\t\t\t\t\telse if (formAction?.includes(\"checkout\") || formClass?.includes(\"checkout\")) purpose = \"checkout\";\n\t\t\t\t\tconst formOverview = {\n\t\t\t\t\t\tselector,\n\t\t\t\t\t\tlocation,\n\t\t\t\t\t\tinputCount: inputs.length\n\t\t\t\t\t};\n\t\t\t\t\tif (purpose) formOverview.purpose = purpose;\n\t\t\t\t\treturn formOverview;\n\t\t\t\t});\n\t\t\t}\n\t\t\t/**\n\t\t\t* Calculate summary statistics\n\t\t\t*/\n\t\t\tstatic calculateSummary(root, regions, forms) {\n\t\t\t\tconst allInteractive = root.querySelectorAll(\"button, a[href], input, textarea, select\");\n\t\t\t\tconst allSections = root.querySelectorAll(\"section, article, [role=\\\"region\\\"]\");\n\t\t\t\tconst hasModals = (regions.modals?.length || 0) > 0;\n\t\t\t\tconst hasErrors = [\n\t\t\t\t\t\".error\",\n\t\t\t\t\t\".alert-danger\",\n\t\t\t\t\t\"[role=\\\"alert\\\"]\"\n\t\t\t\t].some((sel) => {\n\t\t\t\t\tconst element = root.querySelector(sel);\n\t\t\t\t\treturn element ? DOMTraversal.isVisible(element) : false;\n\t\t\t\t});\n\t\t\t\tconst isLoading = [\n\t\t\t\t\t\".loading\",\n\t\t\t\t\t\".spinner\",\n\t\t\t\t\t\"[aria-busy=\\\"true\\\"]\"\n\t\t\t\t].some((sel) => {\n\t\t\t\t\tconst element = root.querySelector(sel);\n\t\t\t\t\treturn element ? DOMTraversal.isVisible(element) : false;\n\t\t\t\t});\n\t\t\t\tconst summary = {\n\t\t\t\t\ttotalInteractive: allInteractive.length,\n\t\t\t\t\ttotalForms: forms.length,\n\t\t\t\t\ttotalSections: allSections.length,\n\t\t\t\t\thasModals,\n\t\t\t\t\thasErrors,\n\t\t\t\t\tisLoading\n\t\t\t\t};\n\t\t\t\tconst mainContentSelector = regions.main?.selector;\n\t\t\t\tif (mainContentSelector) summary.mainContentSelector = mainContentSelector;\n\t\t\t\treturn summary;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Generate AI-friendly suggestions\n\t\t\t*/\n\t\t\tstatic generateSuggestions(regions, summary) {\n\t\t\t\tconst suggestions = [];\n\t\t\t\tif (summary.hasErrors) suggestions.push(\"Page has error indicators - check error messages before interacting\");\n\t\t\t\tif (summary.isLoading) suggestions.push(\"Page appears to be loading - wait or check loading state\");\n\t\t\t\tif (summary.hasModals) suggestions.push(\"Modal/dialog is open - may need to interact with or close it first\");\n\t\t\t\tif (regions.main && regions.main.interactiveCount > 10) suggestions.push(`Main content has ${regions.main.interactiveCount} interactive elements - consider filtering`);\n\t\t\t\tif (summary.totalForms > 0) suggestions.push(`Found ${summary.totalForms} form(s) on the page`);\n\t\t\t\tif (!regions.main) suggestions.push(\"No clear main content area detected - may need to explore regions\");\n\t\t\t\treturn suggestions;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Get text content with optional truncation\n\t\t\t*/\n\t\t\tstatic getTextContent(element, maxLength) {\n\t\t\t\tconst text = element.textContent?.trim() || \"\";\n\t\t\t\tif (maxLength && text.length > maxLength) return `${text.substring(0, maxLength)}...`;\n\t\t\t\treturn text;\n\t\t\t}\n\t\t};\n\t}));\n\t//#endregion\n\t//#region src/types.ts\n\tvar init_types = __esmMin((() => {}));\n\t//#endregion\n\t//#region src/index.ts\n\tvar src_exports = /* @__PURE__ */ __exportAll({\n\t\tContentDetection: () => ContentDetection,\n\t\tMarkdownFormatter: () => MarkdownFormatter,\n\t\tProgressiveExtractor: () => ProgressiveExtractor,\n\t\tSelectorGenerator: () => SelectorGenerator,\n\t\tSmartDOMReader: () => SmartDOMReader,\n\t\tdefault: () => SmartDOMReader\n\t});\n\tvar SmartDOMReader;\n\tvar init_src = __esmMin((() => {\n\t\tinit_content_detection();\n\t\tinit_selectors();\n\t\tinit_traversal();\n\t\tinit_markdown_formatter();\n\t\tinit_progressive();\n\t\tinit_types();\n\t\tSmartDOMReader = class SmartDOMReader {\n\t\t\toptions;\n\t\t\tconstructor(options = {}) {\n\t\t\t\tthis.options = {\n\t\t\t\t\tmode: options.mode || \"interactive\",\n\t\t\t\t\tmaxDepth: options.maxDepth || 5,\n\t\t\t\t\tincludeHidden: options.includeHidden || false,\n\t\t\t\t\tincludeShadowDOM: options.includeShadowDOM ?? true,\n\t\t\t\t\tincludeIframes: options.includeIframes || false,\n\t\t\t\t\tviewportOnly: options.viewportOnly || false,\n\t\t\t\t\tmainContentOnly: options.mainContentOnly || false,\n\t\t\t\t\tcustomSelectors: options.customSelectors || [],\n\t\t\t\t\t...options.attributeTruncateLength !== void 0 && { attributeTruncateLength: options.attributeTruncateLength },\n\t\t\t\t\t...options.dataAttributeTruncateLength !== void 0 && { dataAttributeTruncateLength: options.dataAttributeTruncateLength },\n\t\t\t\t\t...options.textTruncateLength !== void 0 && { textTruncateLength: options.textTruncateLength },\n\t\t\t\t\t...options.filter !== void 0 && { filter: options.filter }\n\t\t\t\t};\n\t\t\t}\n\t\t\t/**\n\t\t\t* Main extraction method - extracts all data in one pass\n\t\t\t* @param rootElement The document or element to extract from\n\t\t\t* @param runtimeOptions Options to override constructor options\n\t\t\t*/\n\t\t\textract(rootElement = document, runtimeOptions) {\n\t\t\t\tconst startTime = Date.now();\n\t\t\t\tconst doc = rootElement instanceof Document ? rootElement : rootElement.ownerDocument;\n\t\t\t\tconst options = {\n\t\t\t\t\t...this.options,\n\t\t\t\t\t...runtimeOptions\n\t\t\t\t};\n\t\t\t\tlet container = rootElement instanceof Document ? doc : rootElement;\n\t\t\t\tif (options.mainContentOnly && rootElement instanceof Document) container = ContentDetection.findMainContent(doc);\n\t\t\t\tconst pageState = this.extractPageState(doc);\n\t\t\t\tconst landmarks = this.extractLandmarks(doc);\n\t\t\t\tconst interactive = this.extractInteractiveElements(container, options);\n\t\t\t\tconst result = {\n\t\t\t\t\tmode: options.mode,\n\t\t\t\t\ttimestamp: startTime,\n\t\t\t\t\tpage: pageState,\n\t\t\t\t\tlandmarks,\n\t\t\t\t\tinteractive\n\t\t\t\t};\n\t\t\t\tif (options.mode === \"full\") {\n\t\t\t\t\tconst semantic = this.extractSemanticElements(container, options);\n\t\t\t\t\tconst metadata = this.extractMetadata(doc, container, options);\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...result,\n\t\t\t\t\t\tsemantic,\n\t\t\t\t\t\tmetadata\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Extract page state information\n\t\t\t*/\n\t\t\textractPageState(doc) {\n\t\t\t\tconst hasFocus = this.getFocusedElement(doc);\n\t\t\t\treturn {\n\t\t\t\t\turl: doc.location?.href || \"\",\n\t\t\t\t\ttitle: doc.title || \"\",\n\t\t\t\t\thasErrors: this.detectErrors(doc),\n\t\t\t\t\tisLoading: this.detectLoading(doc),\n\t\t\t\t\thasModals: this.detectModals(doc),\n\t\t\t\t\t...hasFocus !== void 0 && { hasFocus }\n\t\t\t\t};\n\t\t\t}\n\t\t\t/**\n\t\t\t* Extract page landmarks\n\t\t\t*/\n\t\t\textractLandmarks(doc) {\n\t\t\t\tconst detected = ContentDetection.detectLandmarks(doc);\n\t\t\t\treturn {\n\t\t\t\t\tnavigation: this.elementsToSelectors(detected.navigation || []),\n\t\t\t\t\tmain: this.elementsToSelectors(detected.main || []),\n\t\t\t\t\tforms: this.elementsToSelectors(detected.form || []),\n\t\t\t\t\theaders: this.elementsToSelectors(detected.banner || []),\n\t\t\t\t\tfooters: this.elementsToSelectors(detected.contentinfo || []),\n\t\t\t\t\tarticles: this.elementsToSelectors(detected.region || []),\n\t\t\t\t\tsections: this.elementsToSelectors(detected.region || [])\n\t\t\t\t};\n\t\t\t}\n\t\t\t/**\n\t\t\t* Convert elements to selector strings\n\t\t\t*/\n\t\t\telementsToSelectors(elements) {\n\t\t\t\treturn elements.map((el) => SelectorGenerator.generateSelectors(el).css);\n\t\t\t}\n\t\t\t/**\n\t\t\t* Extract interactive elements\n\t\t\t*/\n\t\t\textractInteractiveElements(container, options) {\n\t\t\t\tconst buttons = [];\n\t\t\t\tconst links = [];\n\t\t\t\tconst inputs = [];\n\t\t\t\tconst clickable = [];\n\t\t\t\tcontainer.querySelectorAll(\"button, [role=\\\"button\\\"], input[type=\\\"button\\\"], input[type=\\\"submit\\\"]\").forEach((el) => {\n\t\t\t\t\tif (this.shouldIncludeElement(el, options)) {\n\t\t\t\t\t\tconst extracted = DOMTraversal.extractElement(el, options);\n\t\t\t\t\t\tif (extracted) buttons.push(extracted);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tcontainer.querySelectorAll(\"a[href]\").forEach((el) => {\n\t\t\t\t\tif (this.shouldIncludeElement(el, options)) {\n\t\t\t\t\t\tconst extracted = DOMTraversal.extractElement(el, options);\n\t\t\t\t\t\tif (extracted) links.push(extracted);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tcontainer.querySelectorAll(\"input:not([type=\\\"button\\\"]):not([type=\\\"submit\\\"]), textarea, select\").forEach((el) => {\n\t\t\t\t\tif (this.shouldIncludeElement(el, options)) {\n\t\t\t\t\t\tconst extracted = DOMTraversal.extractElement(el, options);\n\t\t\t\t\t\tif (extracted) inputs.push(extracted);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (options.customSelectors) options.customSelectors.forEach((selector) => {\n\t\t\t\t\tcontainer.querySelectorAll(selector).forEach((el) => {\n\t\t\t\t\t\tif (this.shouldIncludeElement(el, options)) {\n\t\t\t\t\t\t\tconst extracted = DOMTraversal.extractElement(el, options);\n\t\t\t\t\t\t\tif (extracted) clickable.push(extracted);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tbuttons,\n\t\t\t\t\tlinks,\n\t\t\t\t\tinputs,\n\t\t\t\t\tforms: this.extractForms(container, options),\n\t\t\t\t\tclickable\n\t\t\t\t};\n\t\t\t}\n\t\t\t/**\n\t\t\t* Extract form information\n\t\t\t*/\n\t\t\textractForms(container, options) {\n\t\t\t\tconst forms = [];\n\t\t\t\tcontainer.querySelectorAll(\"form\").forEach((form) => {\n\t\t\t\t\tif (!this.shouldIncludeElement(form, options)) return;\n\t\t\t\t\tconst formInputs = [];\n\t\t\t\t\tconst formButtons = [];\n\t\t\t\t\tform.querySelectorAll(\"input:not([type=\\\"button\\\"]):not([type=\\\"submit\\\"]), textarea, select\").forEach((input) => {\n\t\t\t\t\t\tconst extracted = DOMTraversal.extractElement(input, options);\n\t\t\t\t\t\tif (extracted) formInputs.push(extracted);\n\t\t\t\t\t});\n\t\t\t\t\tform.querySelectorAll(\"button, input[type=\\\"button\\\"], input[type=\\\"submit\\\"]\").forEach((button) => {\n\t\t\t\t\t\tconst extracted = DOMTraversal.extractElement(button, options);\n\t\t\t\t\t\tif (extracted) formButtons.push(extracted);\n\t\t\t\t\t});\n\t\t\t\t\tconst action = form.getAttribute(\"action\");\n\t\t\t\t\tconst method = form.getAttribute(\"method\");\n\t\t\t\t\tconst formInfo = {\n\t\t\t\t\t\tselector: SelectorGenerator.generateSelectors(form).css,\n\t\t\t\t\t\tinputs: formInputs,\n\t\t\t\t\t\tbuttons: formButtons\n\t\t\t\t\t};\n\t\t\t\t\tif (action) formInfo.action = action;\n\t\t\t\t\tif (method) formInfo.method = method;\n\t\t\t\t\tforms.push(formInfo);\n\t\t\t\t});\n\t\t\t\treturn forms;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Extract semantic elements (full mode only)\n\t\t\t*/\n\t\t\textractSemanticElements(container, options) {\n\t\t\t\tconst headings = [];\n\t\t\t\tconst images = [];\n\t\t\t\tconst tables = [];\n\t\t\t\tconst lists = [];\n\t\t\t\tconst articles = [];\n\t\t\t\tcontainer.querySelectorAll(\"h1, h2, h3, h4, h5, h6\").forEach((el) => {\n\t\t\t\t\tif (this.shouldIncludeElement(el, options)) {\n\t\t\t\t\t\tconst extracted = DOMTraversal.extractElement(el, options);\n\t\t\t\t\t\tif (extracted) headings.push(extracted);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tcontainer.querySelectorAll(\"img\").forEach((el) => {\n\t\t\t\t\tif (this.shouldIncludeElement(el, options)) {\n\t\t\t\t\t\tconst extracted = DOMTraversal.extractElement(el, options);\n\t\t\t\t\t\tif (extracted) images.push(extracted);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tcontainer.querySelectorAll(\"table\").forEach((el) => {\n\t\t\t\t\tif (this.shouldIncludeElement(el, options)) {\n\t\t\t\t\t\tconst extracted = DOMTraversal.extractElement(el, options);\n\t\t\t\t\t\tif (extracted) tables.push(extracted);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tcontainer.querySelectorAll(\"ul, ol\").forEach((el) => {\n\t\t\t\t\tif (this.shouldIncludeElement(el, options)) {\n\t\t\t\t\t\tconst extracted = DOMTraversal.extractElement(el, options);\n\t\t\t\t\t\tif (extracted) lists.push(extracted);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tcontainer.querySelectorAll(\"article, [role=\\\"article\\\"]\").forEach((el) => {\n\t\t\t\t\tif (this.shouldIncludeElement(el, options)) {\n\t\t\t\t\t\tconst extracted = DOMTraversal.extractElement(el, options);\n\t\t\t\t\t\tif (extracted) articles.push(extracted);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\theadings,\n\t\t\t\t\timages,\n\t\t\t\t\ttables,\n\t\t\t\t\tlists,\n\t\t\t\t\tarticles\n\t\t\t\t};\n\t\t\t}\n\t\t\t/**\n\t\t\t* Extract metadata\n\t\t\t*/\n\t\t\textractMetadata(doc, container, options) {\n\t\t\t\tconst allElements = container.querySelectorAll(\"*\");\n\t\t\t\tconst extractedElements = container.querySelectorAll(\"button, a, input, textarea, select, h1, h2, h3, h4, h5, h6, img, table, ul, ol, article\").length;\n\t\t\t\tconst metadata = {\n\t\t\t\t\ttotalElements: allElements.length,\n\t\t\t\t\textractedElements\n\t\t\t\t};\n\t\t\t\tif (options.mainContentOnly && container instanceof Element) metadata.mainContent = SelectorGenerator.generateSelectors(container).css;\n\t\t\t\tconst language = doc.documentElement.getAttribute(\"lang\");\n\t\t\t\tif (language) metadata.language = language;\n\t\t\t\treturn metadata;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Check if element should be included based on options\n\t\t\t*/\n\t\t\tshouldIncludeElement(element, options) {\n\t\t\t\tif (!options.includeHidden && !DOMTraversal.isVisible(element)) return false;\n\t\t\t\tif (options.viewportOnly && !DOMTraversal.isInViewport(element)) return false;\n\t\t\t\tif (options.filter && !DOMTraversal.passesFilter(element, options.filter)) return false;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Detect errors on the page\n\t\t\t*/\n\t\t\tdetectErrors(doc) {\n\t\t\t\treturn [\n\t\t\t\t\t\".error\",\n\t\t\t\t\t\".alert-danger\",\n\t\t\t\t\t\"[role=\\\"alert\\\"]\",\n\t\t\t\t\t\".error-message\"\n\t\t\t\t].some((sel) => {\n\t\t\t\t\tconst element = doc.querySelector(sel);\n\t\t\t\t\treturn element ? DOMTraversal.isVisible(element) : false;\n\t\t\t\t});\n\t\t\t}\n\t\t\t/**\n\t\t\t* Detect if page is loading\n\t\t\t*/\n\t\t\tdetectLoading(doc) {\n\t\t\t\treturn [\n\t\t\t\t\t\".loading\",\n\t\t\t\t\t\".spinner\",\n\t\t\t\t\t\"[aria-busy=\\\"true\\\"]\",\n\t\t\t\t\t\".loader\"\n\t\t\t\t].some((sel) => {\n\t\t\t\t\tconst element = doc.querySelector(sel);\n\t\t\t\t\treturn element ? DOMTraversal.isVisible(element) : false;\n\t\t\t\t});\n\t\t\t}\n\t\t\t/**\n\t\t\t* Detect modal dialogs\n\t\t\t*/\n\t\t\tdetectModals(doc) {\n\t\t\t\treturn [\n\t\t\t\t\t\"[role=\\\"dialog\\\"]\",\n\t\t\t\t\t\".modal\",\n\t\t\t\t\t\".popup\",\n\t\t\t\t\t\".overlay\"\n\t\t\t\t].some((sel) => {\n\t\t\t\t\tconst element = doc.querySelector(sel);\n\t\t\t\t\treturn element ? DOMTraversal.isVisible(element) : false;\n\t\t\t\t});\n\t\t\t}\n\t\t\t/**\n\t\t\t* Get currently focused element\n\t\t\t*/\n\t\t\tgetFocusedElement(doc) {\n\t\t\t\tconst focused = doc.activeElement;\n\t\t\t\tif (focused && focused !== doc.body) return SelectorGenerator.generateSelectors(focused).css;\n\t\t\t}\n\t\t\t/**\n\t\t\t* Quick extraction for interactive elements only\n\t\t\t* @param doc The document to extract from\n\t\t\t* @param options Extraction options\n\t\t\t*/\n\t\t\tstatic extractInteractive(doc, options = {}) {\n\t\t\t\treturn new SmartDOMReader({\n\t\t\t\t\t...options,\n\t\t\t\t\tmode: \"interactive\"\n\t\t\t\t}).extract(doc);\n\t\t\t}\n\t\t\t/**\n\t\t\t* Quick extraction for full content\n\t\t\t* @param doc The document to extract from\n\t\t\t* @param options Extraction options\n\t\t\t*/\n\t\t\tstatic extractFull(doc, options = {}) {\n\t\t\t\treturn new SmartDOMReader({\n\t\t\t\t\t...options,\n\t\t\t\t\tmode: \"full\"\n\t\t\t\t}).extract(doc);\n\t\t\t}\n\t\t\t/**\n\t\t\t* Extract from a specific element\n\t\t\t* @param element The element to extract from\n\t\t\t* @param mode The extraction mode\n\t\t\t* @param options Additional options\n\t\t\t*/\n\t\t\tstatic extractFromElement(element, mode = \"interactive\", options = {}) {\n\t\t\t\treturn new SmartDOMReader({\n\t\t\t\t\t...options,\n\t\t\t\t\tmode\n\t\t\t\t}).extract(element);\n\t\t\t}\n\t\t};\n\t}));\n\t//#endregion\n\t//#region src/bundle-entry.ts\n\tinit_src();\n\tinit_markdown_formatter();\n\tinit_progressive();\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\tlet doc = document;\n\t\t\t\t\tif (frameSelector) {\n\t\t\t\t\t\tconst iframe = document.querySelector(frameSelector);\n\t\t\t\t\t\tif (!iframe || !(iframe instanceof HTMLIFrameElement) || !iframe.contentDocument) return { error: `Cannot access iframe: ${frameSelector}` };\n\t\t\t\t\t\tdoc = iframe.contentDocument;\n\t\t\t\t\t}\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\tlet doc = document;\n\t\t\t\t\tif (frameSelector) {\n\t\t\t\t\t\tconst iframe = document.querySelector(frameSelector);\n\t\t\t\t\t\tif (!iframe || !(iframe instanceof HTMLIFrameElement) || !iframe.contentDocument) return { error: `Cannot access iframe: ${frameSelector}` };\n\t\t\t\t\t\tdoc = iframe.contentDocument;\n\t\t\t\t\t}\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, SmartDOMReader);\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\tlet doc = document;\n\t\t\t\t\tif (frameSelector) {\n\t\t\t\t\t\tconst iframe = document.querySelector(frameSelector);\n\t\t\t\t\t\tif (!iframe || !(iframe instanceof HTMLIFrameElement) || !iframe.contentDocument) return { error: `Cannot access iframe: ${frameSelector}` };\n\t\t\t\t\t\tdoc = iframe.contentDocument;\n\t\t\t\t\t}\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\tlet doc = document;\n\t\t\t\t\tif (frameSelector) {\n\t\t\t\t\t\tconst iframe = document.querySelector(frameSelector);\n\t\t\t\t\t\tif (!iframe || !(iframe instanceof HTMLIFrameElement) || !iframe.contentDocument) return { error: `Cannot access iframe: ${frameSelector}` };\n\t\t\t\t\t\tdoc = iframe.contentDocument;\n\t\t\t\t\t}\n\t\t\t\t\tconst extractResult = selector ? SmartDOMReader.extractFromElement(doc.querySelector(selector), \"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\tlet doc = document;\n\t\t\t\t\tif (frameSelector) {\n\t\t\t\t\t\tconst iframe = document.querySelector(frameSelector);\n\t\t\t\t\t\tif (!iframe || !(iframe instanceof HTMLIFrameElement) || !iframe.contentDocument) return { error: `Cannot access iframe: ${frameSelector}` };\n\t\t\t\t\t\tdoc = iframe.contentDocument;\n\t\t\t\t\t}\n\t\t\t\t\tconst extractResult = selector ? SmartDOMReader.extractFromElement(doc.querySelector(selector), \"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.SmartDOMReaderBundle = { executeExtraction };\n\texports.executeExtraction = executeExtraction;\n\treturn exports;\n})({});\n";
|
|
10
|
+
declare const SMART_DOM_READER_VERSION = "1.0.0";
|
|
11
|
+
//#endregion
|
|
12
|
+
export { SMART_DOM_READER_BUNDLE, SMART_DOM_READER_VERSION };
|
|
13
|
+
//# sourceMappingURL=bundle-string.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle-string.d.mts","names":[],"sources":["../src/bundle-string.ts"],"mappings":";;AAQA;;;;;AAEA;cAFa,uBAAA;AAAA,cAEA,wBAAA"}
|