@v1nvn/readability-mcp 0.14.0 → 0.14.1

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":"extract-BKl4PzEI.js","names":[],"sources":["../../package.json","../../src/config.ts","../../src/resources.ts","../../src/errors.ts","../../src/logger.ts","../../src/policy/markdown.ts","../../src/policy/chunk.ts","../../src/tools/schemas.ts","../../src/tools/output-schema.ts","../../src/pipeline/dom.ts","../../src/policy/math.ts","../../src/policy/resolver.ts","../../src/pipeline/normalize.ts","../../src/pipeline/readability.ts","../../src/policy/diagnostics.ts","../../src/policy/gating.ts","../../src/pipeline/urls.ts","../../src/policy/pagination.ts","../../src/tools/html-source.ts","../../src/policy/cell-text.ts","../../src/policy/tables.ts","../../src/policy/text.ts","../../src/policy/metadata.ts","../../src/output/format.ts","../../src/pipeline/sanitize.ts","../../src/policy/footnotes.ts","../../src/pipeline/turndown.ts","../../src/policy/fallback.ts","../../src/policy/images.ts","../../src/policy/truncate.ts","../../src/tools/extract.ts"],"sourcesContent":["{\n \"name\": \"@v1nvn/readability-mcp\",\n \"version\": \"0.14.0\",\n \"description\": \"MCP server that turns rendered (post-JS) HTML into clean Markdown + metadata via Readability, Turndown, and DOMPurify.\",\n \"type\": \"module\",\n \"main\": \"dist/index.js\",\n \"bin\": \"dist/index.js\",\n \"scripts\": {\n \"build\": \"vite build\",\n \"bench\": \"vite-node test/bench/run.ts\",\n \"dev\": \"vite-node src/dev.ts\",\n \"start\": \"node dist/index.js\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:update-goldens\": \"UPDATE_GOLDENS=1 vitest run\",\n \"coverage\": \"vitest run --coverage\"\n },\n \"keywords\": [\n \"mcp\",\n \"readability\",\n \"markdown\",\n \"turndown\",\n \"model-context-protocol\"\n ],\n \"author\": \"v1nvn\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/v1nvn/agentic.git\",\n \"directory\": \"packages/readability-mcp\"\n },\n \"files\": [\n \"dist\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"@mozilla/readability\": \"^0.6.0\",\n \"dompurify\": \"^3.4.12\",\n \"jsdom\": \"^29.1.1\",\n \"remark-gfm\": \"^4.0.1\",\n \"remark-parse\": \"^11.0.0\",\n \"turndown\": \"^7.2.4\",\n \"turndown-plugin-gfm\": \"^1.0.2\",\n \"unified\": \"^11.0.5\",\n \"yaml\": \"^2.9.0\",\n \"zod\": \"^4.4.3\"\n },\n \"devDependencies\": {\n \"@types/mdast\": \"^4.0.4\",\n \"@types/turndown\": \"^5.0.6\",\n \"@vitest/coverage-v8\": \"^4.1.10\",\n \"vite\": \"^8.1.4\",\n \"vite-node\": \"^6.0.0\",\n \"vitest\": \"^4.1.10\"\n }\n}\n","import pkg from '../package.json' with { type: 'json' };\n\nexport interface ServerConfig {\n readonly description: string;\n readonly instructions: string;\n readonly logLevel: LogLevel;\n readonly name: 'readability-mcp';\n readonly title: string;\n readonly version: string;\n}\n\nexport type LogLevel = 'debug' | 'error' | 'info' | 'silent' | 'warn';\n\nconst VALID_LEVELS: readonly LogLevel[] = [\n 'debug',\n 'info',\n 'warn',\n 'error',\n 'silent',\n];\n\nconst LEVEL_RANK: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n silent: Number.MAX_SAFE_INTEGER,\n};\n\nconst DEFAULT_LOG_LEVEL: LogLevel = 'info';\n\nfunction resolveLogLevel(env: NodeJS.ProcessEnv): LogLevel {\n const raw = env.READABILITY_MCP_LOG_LEVEL;\n if (raw && (VALID_LEVELS as readonly string[]).includes(raw)) {\n return raw as LogLevel;\n }\n return DEFAULT_LOG_LEVEL;\n}\n\nconst SERVER_TITLE = 'Readability MCP';\n\nconst SERVER_DESCRIPTION =\n 'Turn already-rendered (post-JavaScript) HTML into clean, LLM-friendly Markdown plus metadata, via Mozilla Readability, Turndown, and DOMPurify. Makes no outbound requests — input is the rendered HTML, read from a file path (localPath) so the page bytes never enter the model context.';\n\nconst SERVER_INSTRUCTIONS = `Eleven always-on tools, all fed a file path (localPath) holding already-rendered HTML (e.g. document.documentElement.outerHTML written to disk by a browser/devtools capture) — except \\`chunk_text\\`, which operates on already-extracted text. A sampling-gated \\`summarize\\` adds a twelfth when (and only when) the host advertises the MCP \\`sampling\\` capability. The server never fetches URLs.\n\n- extract: main tool. Runs Readability to pull the article and returns Markdown + metadata + diagnostics. Use by default for article-like pages. Pass the \\`chunk\\` option to also emit token-bounded chunks for RAG/embedding.\n- extract_links: return a structured list of anchor links ({text, href, rel, isExternal}) from the raw DOM — hrefs absolutized against baseUrl; pairs with chrome-devtools for crawl/navigation decisions.\n- extract_list: second engine for feed/index/search/HN-style pages Readability cannot turn into one article. Returns {items:[{title,url,snippet,score}], diagnostics} — the same-shape sibling-anchor cluster with the most items wins. Reports \\`detected:false\\` on article pages.\n- extract_grid: detect and extract a CSS-grid / div \"table\" — the div equivalent of \\`extract_tables\\` for SPAs that render data into repeating \\`<div>\\` rows. Auto-detects the largest same-shape sibling group of ≥3 rows (each ≥2 cells) or takes explicit \\`rowSelector\\` + \\`cellSelector\\`; renders through the same gfm/csv/json matrix renderer.\n- extract_metadata: return only the bibliographic metadata (title, byline, siteName, lang, publishedTime, excerpt, canonical, baseUrl) without running Readability — fast pre-check for crawlers/citation.\n- extract_section: return one section by CSS selector OR heading text. Selector mode is a straight pass-through to extract’s selectors.include; heading mode spans the matched heading to the next same-or-higher level (case-insensitive, first match wins).\n- extract_tables: extract every <table> on the page (page-wide walk → gfm/csv/json), reusing the same rowspan/colspan-aware matrix serializer as the \\`tables\\` option on \\`extract\\`. Captures tables outside the article body — nav, aside, boilerplate — that the \\`tables\\` option never sees.\n- explain: post-mortem for an extraction — surfaces Readability’s REAL per-candidate contentScore values, the chosen root, a removed-nodes breakdown, gating/pagination signals, and the pre-Readability HTML snapshot. Same normalize + Readability path as \\`extract\\`, no fallback cascade/Turndown.\n- html_to_markdown: convert an arbitrary HTML fragment to Markdown with NO Readability scoring (e.g. a snippet already isolated via devtools).\n- outline: cheap heading pre-check (h1-h6 with stable anchor ids) before paying for full extraction.\n- chunk_text: split already-extracted text into token-bounded chunks (each with index, tokenCount, and nearest preceding heading) for embedding/RAG.\n\nSampling-gated (listed only when the client advertises \\`sampling\\` on initialize):\n- summarize: delegate to the HOST’s model via \\`sampling/createMessage\\` — input {text, maxTokens?}, typically the output of \\`extract\\`/\\`html_to_markdown\\`. The server embeds no model and calls no provider directly; the host picks the model and may prompt the user first.\n\nRounding out the surface:\n- resources: \\`extract({cache:true})\\` caches results as addressable \\`readability://page/{hash}\\` Resources; \\`diagnostics.cache = {hit, normalizedHash, originalHash}\\`. Re-renders that differ only in nonce/CSP/generated-id collapse to the same key (normalized-hash keying).\n\nThe optional baseUrl is origin context only (absolutizes relative links); it is never fetched. Every tool returns MCP structured content (metadata, diagnostics) validated by an output schema, plus a readable payload in content[0].text. Failures surface as { isError: true } results, never thrown across the wire.`;\n\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n return {\n name: 'readability-mcp',\n version: pkg.version,\n title: SERVER_TITLE,\n description: SERVER_DESCRIPTION,\n instructions: SERVER_INSTRUCTIONS,\n logLevel: resolveLogLevel(env),\n };\n}\n\nexport function levelEnabled(\n config: ServerConfig,\n level: Exclude<LogLevel, 'silent'>,\n): boolean {\n return LEVEL_RANK[level] >= LEVEL_RANK[config.logLevel];\n}\n","// In-memory cache of extract results keyed by a volatility-normalized hash of\n// the HTML plus an args fingerprint, exposed to clients as addressable MCP\n// Resources (`readability://page/{hash}`). Re-renders that differ only in\n// nonce/CSP/generated ids collapse to the same key; the original hash rides\n// alongside each entry so a should-have-hit-but-didn't miss points at a\n// normalizer bug rather than a genuinely different page.\n\nimport { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { createHash } from 'node:crypto';\n\nimport type { ToolHandle } from './server.js';\nimport type { ExtractFromHtmlInput } from './tools/schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type {\n ListResourcesResult,\n ReadResourceResult,\n} from '@modelcontextprotocol/sdk/types.js';\n\nconst MAX_ENTRIES = 256;\nconst TTL_MS = 30 * 60 * 1000;\nconst RESOURCE_SCHEME = 'readability://page/';\n\ninterface CacheEntry {\n readonly argsFingerprint: string;\n readonly cacheKey: string;\n readonly contentText: string;\n readonly expiresAt: number;\n readonly normalizedHash: string;\n readonly originalHash: string;\n // Held opaquely: cloned on hit and returned as CallToolResult.structuredContent\n // without re-validation, so a wider element type than the strict output schema\n // (e.g. readonly trace arrays) is fine here.\n readonly structuredContent: unknown;\n}\n\ninterface CacheLookup {\n readonly entry: CacheEntry;\n readonly normalizedHash: string;\n readonly originalHash: string;\n}\n\nconst entries = new Map<string, CacheEntry>();\n\nexport function resetCache(): void {\n entries.clear();\n}\n\nfunction evictExpired(now: number): void {\n for (const [key, entry] of entries) {\n if (entry.expiresAt <= now) {\n entries.delete(key);\n }\n }\n}\n\n// Bounded LRU: Map preserves insertion order, so re-insert on access to surface\n// recently-used entries to the end and drop the oldest when over capacity.\nfunction touch(key: string, entry: CacheEntry): void {\n entries.delete(key);\n entries.set(key, entry);\n}\n\nfunction pruneToMax(): void {\n while (entries.size > MAX_ENTRIES) {\n const oldest = entries.keys().next();\n if (oldest.done) {\n break;\n }\n entries.delete(oldest.value);\n }\n}\n\n// --- Hashing ---------------------------------------------------------------\n\n// Conservative volatility normalization — over-stripping collides distinct\n// pages. Each rule targets a specific known-volatile source.\nfunction normalizeForHash(html: string): string {\n let s = html;\n // Non-JSON-LD scripts carry CSP nonces, build hashes, A/B test buckets —\n // same page, different bytes on every render — so strip them. Preserve\n // <script type=\"application/ld+json\">: structured metadata is content, so\n // a changed datePublished must bust the cache. Mirrors the extraction\n // normalizer in pipeline/normalize.ts.\n s = s.replace(\n /<script\\b(?![^>]*type\\s*=\\s*[\"']application\\/ld\\+json[\"'])[^>]*>[\\s\\S]*?<\\/script>/gi,\n '',\n );\n s = s.replace(\n /<script\\b(?![^>]*type\\s*=\\s*[\"']application\\/ld\\+json[\"'])[^>]*\\/?>/gi,\n '',\n );\n // CSP <meta http-equiv=\"Content-Security-Policy\"> carries the per-response\n // nonce directive — strip the whole tag.\n s = s.replace(\n /<meta\\b[^>]*http-equiv=[\"']?content-security-policy[\"']?[^>]*>/gi,\n '',\n );\n // Per-render nonce attributes on any tag (script, link, style, …).\n s = s.replace(/\\snonce\\s*=\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)/gi, '');\n // Build-tool generated attribute names: Vue `data-v-1a2b3c4d`,\n // CSS-modules `data-css-1a2b3c4d`, Svelte `data-svelte-1a2b3c4d`. The\n // value-part is optional — Vue's compiler emits the bare `data-v-<hash>`\n // form with no `=\"...\"`.\n s = s.replace(\n /\\sdata-(?:v|css|svelte|h)-[a-z0-9]{6,}(?:\\s*=\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+))?/gi,\n '',\n );\n // React useId / RSC / Next.js generated ids: id=\":R1:\", ':r1:',\n // __next_internal_action_…, react internal identifiers.\n s = s.replace(\n /\\sid\\s*=\\s*(\":[A-Za-z0-9_-]+:\"|':[A-Za-z0-9_-]+:'|__next_[A-Za-z0-9_-]+)/g,\n '',\n );\n s = s.replace(\n /\\sid\\s*=\\s*(\"react[A-Z]_[A-Za-z0-9_]+\"|'react[A-Z]_[A-Za-z0-9_]+')/gi,\n '',\n );\n // Whitespace runs and indentation differ by minifier/pretty-printer.\n s = s.replace(/\\s+/g, ' ');\n return s.trim();\n}\n\nfunction sha256(text: string): string {\n return createHash('sha256').update(text).digest('hex');\n}\n\nexport function normalizedHashOf(html: string): string {\n return sha256(normalizeForHash(html));\n}\n\nexport function originalHashOf(html: string): string {\n return sha256(html.trim());\n}\n\n// Output-affecting options participate in the fingerprint so the same page with\n// format:'html' vs format:'markdown' does not collide. `baseUrl` absolutizes\n// links in the output, so it must participate too.\nfunction buildArgsFingerprint(args: ExtractFromHtmlInput): string {\n const sel = args.selectors\n ? {\n exclude: args.selectors.exclude ?? [],\n include: args.selectors.include ?? '',\n }\n : null;\n const chunk = args.chunk\n ? {\n maxTokens: args.chunk.maxTokens,\n overlap: args.chunk.overlap,\n strategy: args.chunk.strategy,\n }\n : null;\n const fp = {\n chunk,\n cleanChrome: args.cleanChrome,\n codeBlockStyle: args.codeBlockStyle,\n debug: args.debug,\n extraction: args.extraction,\n format: args.format,\n gfm: args.gfm,\n headingStyle: args.headingStyle,\n imageInventory: args.imageInventory,\n images: args.images,\n keepClasses: args.keepClasses,\n maxChars: args.maxChars ?? null,\n maxNodes: args.maxNodes ?? null,\n metadataMode: args.metadataMode,\n minArticleLength: args.minArticleLength ?? null,\n readabilityOverrides: args.readabilityOverrides ?? null,\n sanitize: args.sanitize,\n selectors: sel,\n tables: args.tables ?? null,\n baseUrl: args.baseUrl ?? null,\n wordsPerMinute: args.wordsPerMinute,\n };\n return JSON.stringify(fp);\n}\n\nfunction computeHashes(html: string): {\n normalizedHash: string;\n originalHash: string;\n} {\n return {\n normalizedHash: normalizedHashOf(html),\n originalHash: originalHashOf(html),\n };\n}\n\nfunction combineKey(normalizedHash: string, argsFingerprint: string): string {\n return sha256(`${normalizedHash}:${argsFingerprint}`);\n}\n\n// --- Store API -------------------------------------------------------------\n\nexport function lookup(\n html: string,\n args: ExtractFromHtmlInput,\n): CacheLookup | undefined {\n const now = Date.now();\n evictExpired(now);\n const { normalizedHash, originalHash } = computeHashes(html);\n const cacheKey = combineKey(normalizedHash, buildArgsFingerprint(args));\n const entry = entries.get(cacheKey);\n if (!entry || entry.expiresAt <= now) {\n if (entry) {\n entries.delete(cacheKey);\n }\n return undefined;\n }\n touch(cacheKey, entry);\n return { entry, normalizedHash, originalHash };\n}\n\nexport function storeResult(\n html: string,\n args: ExtractFromHtmlInput,\n result: {\n contentText: string;\n structuredContent: unknown;\n },\n): { normalizedHash: string; originalHash: string } {\n const now = Date.now();\n evictExpired(now);\n const { normalizedHash, originalHash } = computeHashes(html);\n const argsFingerprint = buildArgsFingerprint(args);\n const cacheKey = combineKey(normalizedHash, argsFingerprint);\n const entry: CacheEntry = {\n argsFingerprint,\n cacheKey,\n contentText: result.contentText,\n expiresAt: now + TTL_MS,\n normalizedHash,\n originalHash,\n structuredContent: result.structuredContent,\n };\n entries.set(cacheKey, entry);\n pruneToMax();\n return { normalizedHash, originalHash };\n}\n\nexport function getEntryByHash(hash: string): CacheEntry | undefined {\n for (const entry of entries.values()) {\n if (entry.normalizedHash === hash || entry.cacheKey === hash) {\n return entry;\n }\n }\n return undefined;\n}\n\nexport function listEntries(): readonly CacheEntry[] {\n const now = Date.now();\n evictExpired(now);\n return [...entries.values()];\n}\n\n// --- Resource registration -------------------------------------------------\n\n// \"page-cache\" names the registered template; the readability:// URI scheme\n// identifies the cache namespace and {hash} is the combined cache key.\nconst PAGE_CACHE_TEMPLATE = new ResourceTemplate('readability://page/{hash}', {\n // Dynamic set: the cache comes and goes, so we enumerate current entries.\n list: (): ListResourcesResult => ({\n resources: listEntries().map(entry => ({\n description: `Cached extract for normalized hash ${entry.normalizedHash.slice(0, 12)}…`,\n mimeType: 'text/markdown',\n name: entry.normalizedHash,\n uri: `${RESOURCE_SCHEME}${entry.cacheKey}`,\n })),\n }),\n});\n\nexport function registerCacheResources(server: McpServer): ToolHandle {\n return server.registerResource(\n 'page-cache',\n PAGE_CACHE_TEMPLATE,\n {\n title: 'Cached page extractions',\n description:\n 'Addressable cache of `extract` results called with cache:true. Each entry is keyed by the volatility-normalized hash of the HTML plus the output options; the URI path segment is the combined cache key.',\n mimeType: 'text/markdown',\n },\n (uri, variables): ReadResourceResult => {\n const hash = String(variables.hash);\n const entry = getEntryByHash(hash);\n if (!entry) {\n return {\n contents: [\n {\n uri: uri.href,\n text: '',\n },\n ],\n };\n }\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'text/markdown',\n text: entry.contentText,\n },\n ],\n };\n },\n );\n}\n\nexport function registerResources(server: McpServer): ToolHandle[] {\n return [registerCacheResources(server)];\n}\n","import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nexport interface TextContent {\n readonly text: string;\n readonly type: 'text';\n}\n\nexport interface ErrorResult {\n // Mutable so the literal assigns to the SDK's CallToolResult (mutable content).\n readonly content: TextContent[];\n readonly isError: true;\n}\n\nexport class ExtractionError extends Error {\n public override readonly cause: unknown;\n\n public constructor(message: string, options: { cause?: unknown } = {}) {\n super(message);\n this.name = 'ExtractionError';\n this.cause = options.cause;\n }\n}\n\nfunction describeError(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n return String(err);\n}\n\nexport function toErrorResult(err: unknown): CallToolResult {\n const label = err instanceof ExtractionError ? err.name : 'Error';\n return {\n isError: true,\n content: [{ type: 'text', text: `${label}: ${describeError(err)}` }],\n };\n}\n","import { levelEnabled, loadConfig, type LogLevel } from './config.js';\n\ntype Writer = (message: string) => void;\n\nconst LEVEL_LABEL: Record<Exclude<LogLevel, 'silent'>, string> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n};\n\nfunction format(level: Exclude<LogLevel, 'silent'>, message: string): string {\n return `${LEVEL_LABEL[level]} ${message}`;\n}\n\nfunction emit(\n writer: Writer,\n level: Exclude<LogLevel, 'silent'>,\n message: string,\n): void {\n writer(format(level, message));\n}\n\nclass Logger {\n private readonly stderr: Writer;\n\n constructor(stderr: Writer = line => process.stderr.write(`${line}\\n`)) {\n this.stderr = stderr;\n }\n\n debug(message: string): void {\n if (levelEnabled(activeConfig, 'debug')) {\n emit(this.stderr, 'debug', message);\n }\n }\n\n error(message: string): void {\n if (levelEnabled(activeConfig, 'error')) {\n emit(this.stderr, 'error', message);\n }\n }\n\n info(message: string): void {\n if (levelEnabled(activeConfig, 'info')) {\n emit(this.stderr, 'info', message);\n }\n }\n\n warn(message: string): void {\n if (levelEnabled(activeConfig, 'warn')) {\n emit(this.stderr, 'warn', message);\n }\n }\n}\n\nconst activeConfig = loadConfig();\n\nexport const logger = new Logger();\n","import remarkGfm from 'remark-gfm';\nimport remarkParse from 'remark-parse';\nimport { unified } from 'unified';\n\nexport type MarkdownBlockKind = 'code' | 'heading' | 'other';\n\n// A top-level CommonMark block located by source offset. remark is used as a\n// boundary finder, never a serializer: callers slice the *original* string at\n// these offsets so truncate/chunk return byte-for-byte slices of the Turndown\n// output rather than a reflowed re-stringification.\nexport interface MarkdownBlock {\n readonly depth: number;\n readonly end: number;\n readonly kind: MarkdownBlockKind;\n readonly start: number;\n}\n\nconst processor = unified().use(remarkParse).use(remarkGfm);\n\nconst HEADING_MARKERS = /^#{1,6}\\s+/;\n\nexport function parseBlocks(source: string): MarkdownBlock[] {\n const tree = processor.parse(source);\n const blocks: MarkdownBlock[] = [];\n for (const node of tree.children) {\n const start = node.position?.start.offset;\n const end = node.position?.end.offset;\n if (start === undefined || end === undefined) {\n continue;\n }\n const kind: MarkdownBlockKind =\n node.type === 'code'\n ? 'code'\n : node.type === 'heading'\n ? 'heading'\n : 'other';\n const depth = node.type === 'heading' ? node.depth : 0;\n blocks.push({ depth, end, kind, start });\n }\n return blocks;\n}\n\nexport function headingText(raw: string): string {\n return raw.replace(HEADING_MARKERS, '').trim();\n}\n\n// Hard-cap a run to <= maxChars pieces: pack whole lines, then hard-split any\n// single line that alone exceeds the budget.\nexport function hardSplitLines(text: string, maxChars: number): string[] {\n const pieces: string[] = [];\n let buffer = '';\n function flush(): void {\n if (buffer) {\n pieces.push(buffer);\n buffer = '';\n }\n }\n for (const line of text.split('\\n')) {\n if (line.length > maxChars) {\n flush();\n for (let i = 0; i < line.length; i += maxChars) {\n pieces.push(line.slice(i, i + maxChars));\n }\n continue;\n }\n const candidate = buffer ? `${buffer}\\n${line}` : line;\n if (candidate.length > maxChars) {\n flush();\n buffer = line;\n } else {\n buffer = candidate;\n }\n }\n flush();\n return pieces;\n}\n","// Token-bounded chunking of extracted markdown for RAG/embedding. The\n// `chars/4` estimator mirrors policy/metadata.ts so a chunk's `tokenCount` is\n// directly comparable to `metadata.tokenEstimate`. This is the server-side\n// chunking path for large documents; transport-level streaming was rejected —\n// MCP tool-result progress isn't universally host-supported, and this option\n// already keeps a multi-MB page from returning as one payload.\n\nimport { hardSplitLines, headingText, parseBlocks } from './markdown.js';\n\nexport interface Chunk {\n readonly headingContext: string;\n readonly index: number;\n readonly text: string;\n readonly tokenCount: number;\n}\n\nexport type ChunkStrategy = 'char' | 'semantic';\n\nexport interface ChunkOptions {\n readonly maxTokens: number;\n readonly overlap: number;\n readonly strategy: ChunkStrategy;\n}\n\ninterface Block {\n readonly headingContext: string;\n readonly text: string;\n}\n\ninterface Unit {\n readonly headingContext: string;\n readonly text: string;\n}\n\n// Character offsets of a contributing unit within the in-progress chunk text;\n// used to recover the heading context at the start of an overlap tail.\ninterface Span {\n readonly end: number;\n readonly headingContext: string;\n readonly start: number;\n}\n\nconst HEADING_FIRST_LINE = /^#{1,6}\\s/;\n\nfunction splitBlocks(markdown: string): Block[] {\n const parts = markdown.split(/\\n{2,}/);\n const blocks: Block[] = [];\n let heading = '';\n for (const part of parts) {\n const text = part.trim();\n if (!text) {\n continue;\n }\n if (HEADING_FIRST_LINE.test(text)) {\n heading = headingText(text);\n }\n blocks.push({ headingContext: heading, text });\n }\n return blocks;\n}\n\n// Char strategy may break a code block — the semantic strategy avoids that.\nfunction splitOversizedBlock(block: Block, maxChars: number): Unit[] {\n return hardSplitLines(block.text, maxChars).map(text => ({\n headingContext: block.headingContext,\n text,\n }));\n}\n\nfunction toUnits(blocks: readonly Block[], maxChars: number): Unit[] {\n const units: Unit[] = [];\n for (const block of blocks) {\n if (block.text.length <= maxChars) {\n units.push(block);\n } else {\n units.push(...splitOversizedBlock(block, maxChars));\n }\n }\n return units;\n}\n\nfunction chunkMarkdownChar(\n markdown: string,\n options: Readonly<ChunkOptions>,\n): Chunk[] {\n if (!markdown) {\n return [];\n }\n const maxTokens = Math.max(1, Math.floor(options.maxTokens));\n const maxChars = maxTokens * 4;\n const overlapTokens = Math.max(0, Math.floor(options.overlap));\n // Overlap must be strictly less than maxChars — otherwise a chunk's tail\n // could consume the entire budget and we'd loop forever re-emitting it.\n const overlapChars = Math.min(overlapTokens * 4, maxChars - 1);\n\n const blocks = splitBlocks(markdown);\n if (blocks.length === 0) {\n return [];\n }\n const units = toUnits(blocks, maxChars);\n if (units.length === 0) {\n return [];\n }\n\n const chunks: Chunk[] = [];\n let i = 0;\n let overlapText = '';\n let overlapHeading = '';\n\n while (i < units.length) {\n const spans: Span[] = [];\n let chunkText = '';\n\n if (overlapText) {\n chunkText = overlapText;\n spans.push({\n end: overlapText.length,\n headingContext: overlapHeading,\n start: 0,\n });\n }\n\n // Force at least one new unit per chunk so the loop terminates. If carrying\n // the overlap would push the first new unit past maxChars, drop the overlap\n // for this chunk (the unit was pre-split to <= maxChars so it always fits alone).\n const firstUnit = units[i];\n const sepLen = chunkText ? 2 : 0;\n if (\n chunkText &&\n chunkText.length + sepLen + firstUnit.text.length > maxChars\n ) {\n chunkText = '';\n spans.length = 0;\n }\n\n {\n const sep = chunkText ? '\\n\\n' : '';\n const start = chunkText.length + sep.length;\n chunkText = chunkText + sep + firstUnit.text;\n spans.push({\n end: chunkText.length,\n headingContext: firstUnit.headingContext,\n start,\n });\n i += 1;\n }\n\n while (i < units.length) {\n const unit = units[i];\n const candidate = `${chunkText}\\n\\n${unit.text}`;\n if (candidate.length > maxChars) {\n break;\n }\n const start = chunkText.length + 2;\n chunkText = candidate;\n spans.push({\n end: chunkText.length,\n headingContext: unit.headingContext,\n start,\n });\n i += 1;\n }\n\n const text = chunkText.trim();\n if (text) {\n chunks.push({\n headingContext: spans[0]?.headingContext ?? '',\n index: chunks.length,\n text,\n tokenCount: Math.round(text.length / 4),\n });\n }\n\n if (overlapChars > 0 && i < units.length) {\n const overlapStart = Math.max(0, chunkText.length - overlapChars);\n const carrier =\n spans.find(\n span => overlapStart >= span.start && overlapStart < span.end,\n ) ?? spans.find(span => span.start >= overlapStart);\n overlapHeading = carrier?.headingContext ?? '';\n overlapText = chunkText.slice(overlapStart).replace(/^\\n+/, '');\n } else {\n overlapText = '';\n overlapHeading = '';\n }\n }\n\n return chunks;\n}\n\n// --- Semantic strategy ------------------------------------------------------\n//\n// Break on heading/section boundaries and never split a fenced code block. Each\n// top-level mdast node is one unit; a fenced code block is a single `code` node\n// so it stays atomic for free.\n\ntype SemanticUnitKind = 'code' | 'heading' | 'text';\n\ninterface SemanticUnit {\n readonly headingContext: string;\n readonly kind: SemanticUnitKind;\n readonly text: string;\n}\n\ninterface SemanticSection {\n readonly headingContext: string;\n readonly units: readonly SemanticUnit[];\n}\n\n// A group is the unit-list slice that becomes one chunk's body, plus the\n// heading-context of its first non-carrier unit (so prepending an overlap\n// carrier cannot steal the heading's context).\ninterface SemanticGroup {\n readonly headingContext: string;\n readonly units: readonly SemanticUnit[];\n}\n\nfunction parseSemanticUnits(markdown: string): SemanticUnit[] {\n const units: SemanticUnit[] = [];\n const stack: { level: number; text: string }[] = [];\n\n function context(): string {\n return stack.map(h => h.text).join(' > ');\n }\n\n for (const block of parseBlocks(markdown)) {\n const text = markdown.slice(block.start, block.end);\n if (block.kind === 'heading') {\n while (stack.length > 0 && stack[stack.length - 1].level >= block.depth) {\n stack.pop();\n }\n stack.push({ level: block.depth, text: headingText(text) });\n units.push({ headingContext: context(), kind: 'heading', text });\n continue;\n }\n units.push({\n headingContext: context(),\n kind: block.kind === 'code' ? 'code' : 'text',\n text,\n });\n }\n return units;\n}\n\n// A section = a heading unit plus its following non-heading units until the\n// next heading. Pre-first-heading content forms its own section with an empty\n// hierarchy path.\nfunction groupSections(units: readonly SemanticUnit[]): SemanticSection[] {\n const sections: SemanticSection[] = [];\n let current: SemanticUnit[] = [];\n let currentContext = '';\n\n function flush(): void {\n if (current.length > 0) {\n sections.push({ headingContext: currentContext, units: current });\n current = [];\n }\n }\n\n for (const unit of units) {\n if (unit.kind === 'heading') {\n flush();\n current = [unit];\n currentContext = unit.headingContext;\n } else {\n if (current.length === 0) {\n currentContext = unit.headingContext;\n }\n current.push(unit);\n }\n }\n flush();\n return sections;\n}\n\nfunction joinedLength(units: readonly SemanticUnit[]): number {\n if (units.length === 0) {\n return 0;\n }\n let total = units[0].text.length;\n for (let i = 1; i < units.length; i++) {\n total += 2 + units[i].text.length;\n }\n return total;\n}\n\n// Hard-split a text unit that alone exceeds the budget — line-aware first, then\n// hard-split any oversized line. Only ever applied to paragraphs; code blocks\n// are never passed through here.\nfunction splitOversizedTextUnit(\n unit: SemanticUnit,\n maxChars: number,\n): SemanticUnit[] {\n return hardSplitLines(unit.text, maxChars).map(text => ({\n headingContext: unit.headingContext,\n kind: 'text',\n text,\n }));\n}\n\n// Pack sections into chunk groups <= maxChars, preferring heading boundaries.\n// A section that fits is never split; an oversized section is split into\n// sub-chunks by body unit (paragraphs may split; code blocks never do).\nfunction buildBaseGroups(\n sections: readonly SemanticSection[],\n maxChars: number,\n): SemanticUnit[][] {\n const groups: SemanticUnit[][] = [];\n let current: SemanticUnit[] = [];\n let currentLen = 0;\n\n function emit(): void {\n if (current.length > 0) {\n groups.push(current);\n current = [];\n currentLen = 0;\n }\n }\n\n function append(unit: SemanticUnit): void {\n const sep = current.length > 0 ? 2 : 0;\n current.push(unit);\n currentLen += sep + unit.text.length;\n }\n\n for (const section of sections) {\n const sectionLen = joinedLength(section.units);\n\n const sep = current.length > 0 ? 2 : 0;\n if (currentLen + sep + sectionLen <= maxChars) {\n for (const unit of section.units) {\n append(unit);\n }\n continue;\n }\n\n // Doesn't fit merged — close the current group and retry the section alone\n // so it has first dibs on the next group (it may still merge with a later\n // section that fits behind it).\n emit();\n\n if (sectionLen <= maxChars) {\n for (const unit of section.units) {\n append(unit);\n }\n continue;\n }\n\n // Oversized section: pack body units under the heading; split oversized\n // paragraphs, emit oversized code blocks whole.\n const [headingUnit, ...body] = section.units;\n current.push(headingUnit);\n currentLen = headingUnit.text.length;\n for (const unit of body) {\n if (unit.kind === 'code' && unit.text.length > maxChars) {\n // Semantic never splits a code fence: a fence cut mid-way is\n // unrecoverable, so the block is emitted as its own chunk even though\n // it exceeds the token budget. Deliberate tradeoff vs. the char cap.\n emit();\n groups.push([unit]);\n continue;\n }\n const sepNow = current.length > 0 ? 2 : 0;\n if (\n unit.text.length <= maxChars &&\n currentLen + sepNow + unit.text.length <= maxChars\n ) {\n append(unit);\n continue;\n }\n const pieces =\n unit.text.length > maxChars\n ? splitOversizedTextUnit(unit, maxChars)\n : [unit];\n for (const piece of pieces) {\n const pieceSep = current.length > 0 ? 2 : 0;\n if (\n current.length > 0 &&\n currentLen + pieceSep + piece.text.length > maxChars\n ) {\n emit();\n }\n append(piece);\n }\n }\n // Leave the trailing sub-chunk in `current` so a following section can\n // merge into it if it fits.\n }\n emit();\n return groups;\n}\n\n// Carry the trailing text (non-code) units from the previous group as the\n// prefix of the next, up to overlapChars. Never carry into or out of a code\n// block: a trailing code run is skipped, then contiguous text units before it\n// are carried (stop at the first heading or code block encountered).\nfunction applySemanticOverlap(\n groups: readonly SemanticUnit[][],\n overlapChars: number,\n): SemanticGroup[] {\n const result: SemanticGroup[] = [];\n for (let i = 0; i < groups.length; i++) {\n const group = groups[i];\n const carrier: SemanticUnit[] = [];\n if (i > 0 && overlapChars > 0) {\n const prev = groups[i - 1];\n let end = prev.length;\n while (end > 0 && prev[end - 1].kind === 'code') {\n end -= 1;\n }\n let carrierLen = 0;\n for (let j = end - 1; j >= 0; j--) {\n const unit = prev[j];\n if (unit.kind !== 'text') {\n break;\n }\n const sep = carrier.length > 0 ? 2 : 0;\n if (carrierLen + sep + unit.text.length > overlapChars) {\n break;\n }\n carrier.unshift(unit);\n carrierLen += sep + unit.text.length;\n }\n }\n const units = carrier.length === 0 ? group : [...carrier, ...group];\n result.push({\n // The chunk's heading context follows its first non-carrier unit, so a\n // heading-leading chunk keeps its heading's path even with overlap.\n headingContext: group[0]?.headingContext ?? '',\n units,\n });\n }\n return result;\n}\n\nfunction chunkMarkdownSemantic(\n markdown: string,\n options: Readonly<ChunkOptions>,\n): Chunk[] {\n if (!markdown) {\n return [];\n }\n const maxTokens = Math.max(1, Math.floor(options.maxTokens));\n const maxChars = maxTokens * 4;\n const overlapTokens = Math.max(0, Math.floor(options.overlap));\n const overlapChars = Math.min(overlapTokens * 4, maxChars - 1);\n\n const units = parseSemanticUnits(markdown);\n if (units.length === 0) {\n return [];\n }\n const sections = groupSections(units);\n const baseGroups = buildBaseGroups(sections, maxChars);\n const groups = applySemanticOverlap(baseGroups, overlapChars);\n\n const chunks: Chunk[] = [];\n for (const group of groups) {\n if (group.units.length === 0) {\n continue;\n }\n const text = group.units\n .map(unit => unit.text)\n .join('\\n\\n')\n .trim();\n if (!text) {\n continue;\n }\n chunks.push({\n headingContext: group.headingContext,\n index: chunks.length,\n text,\n tokenCount: Math.round(text.length / 4),\n });\n }\n return chunks;\n}\n\nexport function chunkMarkdown(\n markdown: string,\n options: Readonly<ChunkOptions>,\n): Chunk[] {\n if (options.strategy === 'semantic') {\n return chunkMarkdownSemantic(markdown, options);\n }\n return chunkMarkdownChar(markdown, options);\n}\n","import { z } from 'zod';\n\nexport const formatSchema = z.enum(['html', 'json', 'markdown', 'text']);\nexport const metadataModeSchema = z.enum(['json', 'none', 'yaml']);\nexport const extractionSchema = z.enum([\n 'aggressive',\n 'balanced',\n 'conservative',\n]);\nexport const headingStyleSchema = z.enum(['atx', 'setext']);\nexport const codeBlockStyleSchema = z.enum(['fenced', 'indented']);\nexport const imageModeSchema = z.enum([\n 'drop',\n 'keep',\n 'reference',\n 'src-only',\n]);\nexport const tableFormatSchema = z.enum(['csv', 'gfm', 'json']);\n\nexport const localPathField = z\n .string()\n .describe(\n 'Absolute or relative path to a file holding the already-rendered (post-JavaScript) HTML to process, e.g. `document.documentElement.outerHTML` written to disk by a browser/devtools capture. Read by the server so the page bytes never enter the model context — only this path string does. Resolved relative to the server process working directory; the server makes no outbound requests.',\n );\n\nexport const selectorsSchema = z\n .object({\n include: z\n .string()\n .optional()\n .describe(\n 'CSS selector restricting extraction to a matching subtree (e.g. \"main\", \"article\", \".post\"). The first match replaces the document body before processing.',\n ),\n exclude: z\n .array(z.string())\n .optional()\n .describe(\n 'CSS selectors for boilerplate to remove before extraction (e.g. [\"nav\", \"footer\", \"[role=banner]\"]).',\n ),\n })\n .optional()\n .describe(\n 'Scope the extracted/converted content by CSS selector before processing.',\n );\n\nexport const readabilityOverridesSchema = z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\n 'Escape hatch: a record spread verbatim into the Readability options. Unstable and unvalidated; overrides the extraction/keepClasses/maxNodes/minArticleLength knobs.',\n );\n\nexport const chunkStrategySchema = z.enum(['char', 'semantic']);\n\nexport const chunkOptionsSchema = z\n .object({\n maxTokens: z\n .number()\n .int()\n .min(1)\n .describe(\n 'Per-chunk token budget. Each chunk.text is sized so Math.round(text.length/4) stays within this bound (hard cap; oversized blocks are split by line, then hard-split).',\n ),\n overlap: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Tokens to overlap between consecutive chunks (>=0). The trailing overlapChars of chunk N becomes the leading context of chunk N+1, preserving cross-chunk coherence at a cost of redundant tokens.',\n )\n .default(0),\n strategy: chunkStrategySchema\n .describe(\n \"Chunking strategy. 'semantic' (default) breaks on heading/section boundaries and never splits a fenced code block; 'char' greedily groups blank-line-separated blocks under a chars/4 token budget (may split a code block).\",\n )\n .default('semantic'),\n })\n .describe(\n 'Token-bounded chunking options for splitting the extracted markdown into RAG/embedding-ready slices.',\n );\n\nexport const turndownOptionsShape = {\n debug: z\n .boolean()\n .describe(\n 'Emit per-stage timings (normalize, readability, sanitize, turndown, metadata) under diagnostics.trace. Debug-only — leaves trace absent by default.',\n )\n .default(false),\n cleanChrome: z\n .boolean()\n .describe(\n 'Strip browser chrome (scrollbars, consent/cookie banners, fixed nav and overlays) before conversion. These elements poison Readability density scoring and clutter fragment output.',\n )\n .default(true),\n codeBlockStyle: codeBlockStyleSchema\n .describe(\n \"Markdown code-block style: 'fenced' (triple backticks) or 'indented' (four-space).\",\n )\n .default('fenced'),\n format: formatSchema\n .describe(\n \"Returned payload format: 'markdown' (default), 'html', 'text', or 'json' (emits {metadata, content, diagnostics}).\",\n )\n .default('markdown'),\n gfm: z\n .boolean()\n .describe(\n 'Enable GitHub-Flavored Markdown: tables, strikethrough, and task lists.',\n )\n .default(true),\n headingStyle: headingStyleSchema\n .describe(\n \"Markdown heading style: 'atx' (#) or 'setext' (underlining with = / -).\",\n )\n .default('atx'),\n images: imageModeSchema\n .describe(\n \"Image handling: 'keep' (inline ![alt](url)), 'drop', 'src-only' (bare URL text), or 'reference' (link-reference style).\",\n )\n .default('keep'),\n maxChars: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Truncate markdown/text output at a block boundary; never splits a fenced code block. Ignored for html/json formats.',\n )\n .optional(),\n metadataMode: metadataModeSchema\n .describe(\n \"Prepend a metadata block to the markdown/text payload: 'none' (default), 'yaml', or 'json'.\",\n )\n .default('none'),\n sanitize: z\n .boolean()\n .describe(\n 'Run DOMPurify over the extracted/fragment HTML before conversion (strips scripts, event handlers, and iframes).',\n )\n .default(true),\n tables: tableFormatSchema\n .describe(\n 'Render <table> elements via a rowspan/colspan-aware matrix: \"gfm\" (default native GFM table), \"csv\" (RFC-4180-ish code block), or \"json\" (array of row objects keyed by the header). When unset, tables pass through Turndown\\'s native rule unchanged.',\n )\n .optional(),\n // zod v4 renamed `z.string().url()` to `z.url()`.\n baseUrl: z\n .url()\n .describe(\n 'Base URL for absolutizing relative links and images. NEVER fetched — origin context only.',\n )\n .optional(),\n wordsPerMinute: z\n .number()\n .int()\n .min(1)\n .describe(\n 'Reading speed (words per minute) used to compute metadata.readingTimeMin.',\n )\n .default(200),\n} as const;\n\nexport const extractInputShape = {\n localPath: localPathField,\n ...turndownOptionsShape,\n\n cache: z\n .boolean()\n .describe(\n 'When true, cache the result keyed by a normalized hash of the HTML plus the output-affecting options; repeat calls with the same normalized HTML hit the cache and report diagnostics.cache (hit/miss + both hashes). The cache is in-memory and bounded; entries are also exposed as readability://page/{hash} resources.',\n )\n .default(false),\n extraction: extractionSchema\n .describe(\n \"Readability scoring aggressiveness: 'balanced' (default), 'aggressive', or 'conservative'. Maps to Readability's scorer knobs.\",\n )\n .default('balanced'),\n keepClasses: z\n .boolean()\n .describe(\n 'Retain all CSS classes on extracted nodes. Defaults false, which strips non-language classes.',\n )\n .default(false),\n maxNodes: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Hard cap on elements parsed (Readability maxElemsToParse). Safety/perf guard for very large documents.',\n )\n .optional(),\n minArticleLength: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Minimum article character length below which extraction falls back to the selector cascade (Readability charThreshold).',\n )\n .optional(),\n readabilityOverrides: readabilityOverridesSchema,\n selectors: selectorsSchema,\n chunk: chunkOptionsSchema\n .optional()\n .describe(\n 'Split the extracted markdown into token-bounded chunks (RAG/embedding-ready). When set, structuredContent.chunks is populated. Only applies to format:\"markdown\" | \"text\"; HTML/JSON payloads carry no markdown body to slice and leave chunks unset.',\n ),\n imageInventory: z\n .boolean()\n .describe(\n 'Emit structuredContent.images: a list of {src (absolute, resolved), alt, width?, height?, caption} for every <img> in the extracted article. Independent of the `images` option (which governs inline rendering). Placeholders are skipped.',\n )\n .default(false),\n} as const;\n\nexport const extractInputSchema = z.object(extractInputShape);\n\nexport const htmlToMarkdownInputShape = {\n localPath: localPathField,\n ...turndownOptionsShape,\n selectors: selectorsSchema,\n} as const;\n\nexport const htmlToMarkdownInputSchema = z.object(htmlToMarkdownInputShape);\n\nexport const outlineInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL, carried through to metadata.baseUrl and used to absolutize links. NEVER fetched — origin context only.',\n )\n .optional(),\n selectors: selectorsSchema,\n} as const;\n\nexport const outlineInputSchema = z.object(outlineInputShape);\n\nexport const extractMetadataInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL, carried through to metadata.baseUrl and used to absolutize links. NEVER fetched — origin context only.',\n )\n .optional(),\n} as const;\n\nexport const extractMetadataInputSchema = z.object(extractMetadataInputShape);\n\nexport const extractLinksInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL for absolutizing relative hrefs and computing isExternal. NEVER fetched — origin context only.',\n )\n .optional(),\n sameOriginOnly: z\n .boolean()\n .describe(\n 'Drop cross-origin links; keep same-origin, relative, and fragment links.',\n )\n .default(false),\n selectors: selectorsSchema,\n} as const;\n\nexport const extractLinksInputSchema = z.object(extractLinksInputShape);\n\nexport const chunkTextInputShape = {\n text: z\n .string()\n .describe(\n 'Already-extracted text to split (e.g. markdown from `extract` or any plain text). No HTML parsing or Readability scoring is applied — the input is chunked verbatim.',\n ),\n maxTokens: z\n .number()\n .int()\n .min(1)\n .describe(\n 'Per-chunk token budget. Each chunk.text is sized so Math.round(text.length/4) stays within this bound (hard cap; oversized blocks are split by line, then hard-split).',\n )\n .default(500),\n overlap: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Tokens to overlap between consecutive chunks (>=0). The trailing overlapChars of chunk N becomes the leading context of chunk N+1.',\n )\n .default(0),\n strategy: chunkStrategySchema\n .describe(\n \"Chunking strategy. 'semantic' (default) breaks on heading/section boundaries and never splits a fenced code block; 'char' greedily groups blank-line-separated blocks under a chars/4 token budget.\",\n )\n .default('semantic'),\n} as const;\n\nexport const chunkTextInputSchema = z.object(chunkTextInputShape);\n\nexport const extractSectionInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL for absolutizing relative links and images. NEVER fetched — origin context only.',\n )\n .optional(),\n selector: z\n .string()\n .describe(\n 'CSS selector scoping extraction to one subtree; passed straight through as selectors.include. Provide exactly one of selector/heading.',\n )\n .optional(),\n heading: z\n .string()\n .describe(\n 'Heading text selecting one section; the section spans from this heading to the next same-or-higher-level heading. Case-insensitive; first match wins. Provide exactly one of selector/heading.',\n )\n .optional(),\n} as const;\n\nexport const extractSectionInputSchema = z\n .object(extractSectionInputShape)\n .superRefine((value, ctx) => {\n const hasSelector = value.selector !== undefined;\n const hasHeading = value.heading !== undefined;\n if (hasSelector === hasHeading) {\n ctx.addIssue({\n code: 'custom',\n message:\n 'Provide exactly one of `selector` or `heading` (both set or both unset is invalid).',\n path: ['selector'],\n });\n }\n });\n\nexport const extractTablesInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL, carried through to metadata.baseUrl. NEVER fetched — origin context only.',\n )\n .optional(),\n format: tableFormatSchema\n .describe(\n 'Output format for every table: \"gfm\" (default — native GFM table with a delimiter row), \"csv\" (RFC-4180-ish, quoted fields), or \"json\" (array of row objects keyed by the header row).',\n )\n .default('gfm'),\n selectors: selectorsSchema,\n} as const;\n\nexport const extractTablesInputSchema = z.object(extractTablesInputShape);\n\nexport const extractListInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL for absolutizing item hrefs against. NEVER fetched — origin context only.',\n )\n .optional(),\n selectors: selectorsSchema,\n} as const;\n\nexport const extractListInputSchema = z.object(extractListInputShape);\n\nexport const extractGridInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL, carried through to metadata.baseUrl. NEVER fetched — origin context only.',\n )\n .optional(),\n format: tableFormatSchema\n .describe(\n 'Output format for the grid: \"gfm\" (default — native GFM table with a delimiter row), \"csv\" (RFC-4180-ish, quoted fields), or \"json\" (array of row objects keyed by the header row).',\n )\n .default('gfm'),\n selectors: selectorsSchema,\n rowSelector: z\n .string()\n .describe(\n 'CSS selector for repeating row containers. When set WITH cellSelector, selector mode is used (no auto-detection). Example: \\'[class*=\"estimate-row\"]\\'.',\n )\n .optional(),\n cellSelector: z\n .string()\n .describe(\n 'CSS selector for cells within each row (scoped to the row subtree). Required together with rowSelector for selector mode. Example: \\'[class*=\"cell\"]\\'.',\n )\n .optional(),\n} as const;\n\n// rowSelector and cellSelector are both-or-neither: both set routes through\n// selector mode, neither set routes through auto-detection. Exactly one set is\n// ambiguous (which mode?), so it is rejected up front rather than silently\n// falling back.\nexport const extractGridInputSchema = z\n .object(extractGridInputShape)\n .superRefine((value, ctx) => {\n const hasRow = value.rowSelector !== undefined;\n const hasCell = value.cellSelector !== undefined;\n if (hasRow !== hasCell) {\n ctx.addIssue({\n code: 'custom',\n message:\n 'Provide both `rowSelector` and `cellSelector` for selector mode, or neither for auto-detection (setting only one is invalid).',\n path: ['rowSelector'],\n });\n }\n });\n\nexport type ChunkTextInput = z.infer<typeof chunkTextInputSchema>;\nexport type ExtractGridInput = z.infer<typeof extractGridInputSchema>;\nexport type ExtractInput = z.infer<typeof extractInputSchema>;\nexport type ExtractLinksInput = z.infer<typeof extractLinksInputSchema>;\nexport type ExtractListInput = z.infer<typeof extractListInputSchema>;\nexport type ExtractMetadataInput = z.infer<typeof extractMetadataInputSchema>;\nexport type ExtractSectionInput = z.infer<typeof extractSectionInputSchema>;\nexport type ExtractTablesInput = z.infer<typeof extractTablesInputSchema>;\nexport type HtmlToMarkdownInput = z.infer<typeof htmlToMarkdownInputSchema>;\nexport type OutlineInput = z.infer<typeof outlineInputSchema>;\nexport type Selectors = z.infer<typeof selectorsSchema>;\n\n// A worker takes the same fields as its public schema minus `localPath`, plus\n// the already-resolved `html` string. The option fields are all optional here:\n// the public fn hands a fully-defaulted object (it parsed the schema), while\n// internal callers (the CLI, extract_section, tests) pass only what they care\n// about and the worker merges schema defaults for the rest. Tools whose schema\n// has no `.default()` fields have nothing to merge.\nexport type FromHtmlInput<T> = Partial<Omit<T, 'localPath'>> & { html: string };\n\nexport type ExtractFromHtmlInput = FromHtmlInput<ExtractInput>;\nexport type HtmlToMarkdownFromHtmlInput = FromHtmlInput<HtmlToMarkdownInput>;\nexport type OutlineFromHtmlInput = FromHtmlInput<OutlineInput>;\nexport type ExtractLinksFromHtmlInput = FromHtmlInput<ExtractLinksInput>;\nexport type ExtractListFromHtmlInput = FromHtmlInput<ExtractListInput>;\nexport type ExtractMetadataFromHtmlInput = FromHtmlInput<ExtractMetadataInput>;\nexport type ExtractSectionFromHtmlInput = FromHtmlInput<ExtractSectionInput>;\nexport type ExtractTablesFromHtmlInput = FromHtmlInput<ExtractTablesInput>;\nexport type ExtractGridFromHtmlInput = FromHtmlInput<ExtractGridInput>;\n","import { z } from 'zod';\n\nimport { tableFormatSchema } from './schemas.js';\n\n// Shared between `extract` and `extract_metadata` outputs so the metadata\n// cascade has one shape across tools. wordCount/readingTimeMin/tokenEstimate\n// are optional here — extract_metadata omits them; extract populates them.\nconst metadataObjectSchema = z\n .object({\n byline: z\n .string()\n .optional()\n .describe(\n 'Article author(s), resolved from JSON-LD, OpenGraph, <meta>, or Readability.',\n ),\n canonical: z\n .string()\n .optional()\n .describe(\n 'Declared canonical URL from <link rel=\"canonical\"> (or og:url as fallback). Distinct from baseUrl, which is the origin context passed in.',\n ),\n estimator: z\n .string()\n .optional()\n .describe(\n 'Name of the heuristic backing tokenEstimate (e.g. \"chars/4\").',\n ),\n excerpt: z\n .string()\n .optional()\n .describe('Short article summary produced by Readability.'),\n lang: z.string().optional().describe('Detected document language.'),\n publishedTime: z\n .string()\n .optional()\n .describe(\n 'Publication timestamp resolved from JSON-LD, <meta>, or <time> elements.',\n ),\n readingTimeMin: z\n .number()\n .int()\n .optional()\n .describe(\n 'Estimated reading time in minutes, derived from wordCount and wordsPerMinute.',\n ),\n siteName: z\n .string()\n .optional()\n .describe('Publishing site name, resolved from OpenGraph or <meta>.'),\n structured: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\n 'Parsed schema.org JSON-LD primary object (Recipe/Product/Event/HowTo/Article…) when present — the raw graph node with @context stripped and @type normalized to a \"+\"-joined string. Absent when the page has no recognizable structured data.',\n ),\n title: z\n .string()\n .optional()\n .describe(\n 'Article title, resolved by priority cascade (JSON-LD → OpenGraph → Twitter → <meta> → Readability → <title>).',\n ),\n tokenEstimate: z\n .number()\n .int()\n .optional()\n .describe(\n 'Rough output token count (chars/4 by default) for context budgeting.',\n ),\n baseUrl: z\n .string()\n .optional()\n .describe('The baseUrl passed in (origin context).'),\n wordCount: z\n .number()\n .int()\n .optional()\n .describe('Number of whitespace-separated words in the extracted text.'),\n })\n .describe(\n 'Resolved article metadata. Each field is the first non-empty value across a priority cascade.',\n );\n\nexport const chunkObjectSchema = z\n .object({\n index: z\n .number()\n .int()\n .min(0)\n .describe('Zero-based chunk position within the emitted sequence.'),\n text: z\n .string()\n .describe(\n 'The chunk body (markdown or text), trimmed, sized to stay within the requested token budget.',\n ),\n tokenCount: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Estimated token count of text (chars/4), same heuristic as metadata.tokenEstimate.',\n ),\n headingContext: z\n .string()\n .describe(\n 'Nearest preceding markdown heading text in effect at the chunk’s first block. Empty string when the chunk precedes any heading; carried from the overlap source when a chunk begins with overlap text.',\n ),\n })\n .describe(\n 'One token-bounded slice of the extracted markdown, with its section heading for context.',\n );\n\nexport const imageEntrySchema = z\n .object({\n src: z\n .string()\n .describe('Absolute (resolved) image URL, absolutized against baseUrl.'),\n alt: z\n .string()\n .describe('The img alt attribute, or empty string when absent.'),\n width: z\n .number()\n .int()\n .optional()\n .describe('Pixel dimension from the attribute, when present.'),\n height: z\n .number()\n .int()\n .optional()\n .describe('Pixel dimension from the attribute, when present.'),\n caption: z\n .string()\n .describe('figcaption text from the enclosing <figure>, else alt.'),\n })\n .describe('One extracted image with resolved source and caption.');\n\nexport const outputSchemaShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'The human/LLM-readable payload — Markdown/html/text, or the serialized JSON when format=json.',\n ),\n chunks: z\n .array(chunkObjectSchema)\n .optional()\n .describe(\n 'Token-bounded chunks of the extracted markdown, populated by `extract` only when the `chunk` option is set and the format yields a markdown/text body. Absent for html_to_markdown and for html/json extract formats.',\n ),\n images: z\n .array(imageEntrySchema)\n .optional()\n .describe(\n 'Inventory of article images (absolute src, alt, dimensions, caption); populated only when imageInventory:true is passed to extract.',\n ),\n metadata: metadataObjectSchema,\n diagnostics: z\n .object({\n boilerplateRemoved: z\n .number()\n .int()\n .optional()\n .describe(\n 'Count of boilerplate blocks (related-posts, newsletter signup, read-next) stripped before conversion.',\n ),\n cache: z\n .object({\n hit: z\n .boolean()\n .describe(\n 'True when this call was served from the in-memory cache without re-running the pipeline.',\n ),\n normalizedHash: z\n .string()\n .describe(\n 'sha256 of the volatility-normalized HTML (whitespace collapsed, scripts/nonce/CSP/generated ids stripped) — the actual cache key, shared across re-renders that differ only in volatile markup.',\n ),\n originalHash: z\n .string()\n .describe(\n 'sha256 of the raw HTML as passed (trimmed). Differs from normalizedHash when volatile markup (nonce/CSP/generated ids) was collapsed; equal when the input was already stable. Used to diagnose should-have-hit-but-didn’t misses.',\n ),\n })\n .optional()\n .describe(\n 'Cache signal — populated only by `extract` when called with cache:true. Absent otherwise (goldens, default path, and other tools never emit this field).',\n ),\n chromeRemoved: z\n .number()\n .int()\n .optional()\n .describe(\n 'Count of browser-chrome nodes stripped before conversion (scrollbars, consent banners, overlays).',\n ),\n extractedNode: z\n .string()\n .optional()\n .describe(\n 'DOM root extraction came from: \"readability\" (main path), a fallback selector (e.g. \"article\", \"main\"), or \"fragment\" for html_to_markdown.',\n ),\n fallbackUsed: z\n .boolean()\n .describe(\n 'True if Readability parse failed and a selector cascade salvaged content. Always true for html_to_markdown.',\n ),\n gated: z\n .object({\n likely: z\n .boolean()\n .describe(\n 'True when heuristics strongly suggest the content is paywalled or truncated.',\n ),\n reason: z\n .string()\n .describe(\n 'Short label naming the detected signal (e.g. \"paywall overlay\", \"metered paywall message\").',\n ),\n })\n .optional()\n .describe(\n 'Likely paywall / gating signal. The extraction may be partial; the host can re-capture after authenticating. Detection only — this server never fetches or authenticates.',\n ),\n imagesResolved: z\n .number()\n .int()\n .optional()\n .describe(\n 'Count of lazy/placeholder images resolved to their real src before conversion.',\n ),\n pagination: z\n .object({\n type: z\n .enum(['infinite', 'paginated'])\n .describe('Kind of pagination signal detected in the document.'),\n nextUrl: z\n .string()\n .optional()\n .describe(\n 'Absolute URL of the detected next page (paginated only). Mirrors the href found in the DOM; never fetched by this server.',\n ),\n selector: z\n .string()\n .optional()\n .describe(\n 'CSS selector of the detected load-more / infinite-scroll sentinel (infinite only).',\n ),\n })\n .optional()\n .describe(\n 'Detected pagination or infinite-scroll signal. Detection only — the host drives loading; this server never fetches.',\n ),\n readerable: z\n .boolean()\n .optional()\n .describe(\n 'Readability isProbablyReaderable verdict on the document (extract main path only).',\n ),\n removedNodes: z\n .number()\n .int()\n .optional()\n .describe(\n 'Net element count removed across the whole pipeline (delta vs. the parsed document).',\n ),\n sanitization: z\n .object({\n iframes: z\n .number()\n .int()\n .describe('<iframe> elements removed by sanitization.'),\n scripts: z\n .number()\n .int()\n .describe(\n '<script> and event-handler nodes removed by sanitization.',\n ),\n })\n .optional()\n .describe('Counts of nodes removed by DOMPurify sanitization.'),\n truncated: z\n .boolean()\n .describe('True if the payload was truncated by maxChars.'),\n trace: z\n .array(\n z\n .object({\n stage: z\n .string()\n .describe(\n 'Pipeline stage name (e.g. \"normalize\", \"readability\", \"sanitize\", \"turndown\", \"metadata\").',\n ),\n ms: z\n .number()\n .describe(\n 'Wall-clock duration of the stage in milliseconds, measured via performance.now().',\n ),\n })\n .describe('One timed pipeline stage.'),\n )\n .optional()\n .describe(\n 'Per-stage timings emitted only when debug:true is passed to extract/html_to_markdown. Stages are non-overlapping and ordered; absent otherwise.',\n ),\n })\n .describe(\n 'Pipeline telemetry describing what was extracted, sanitized, and removed.',\n ),\n} as const;\n\nexport const outputSchema = z.object(outputSchemaShape);\n\nexport type StructuredContent = z.infer<typeof outputSchema>;\n\nexport const outlineOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'Indented-bullet table of contents, one line per heading, nested by depth.',\n ),\n outline: z\n .array(\n z\n .object({\n level: z\n .number()\n .int()\n .min(1)\n .max(6)\n .describe('Heading level (1–6).'),\n text: z.string().describe('Heading text content.'),\n anchor: z\n .string()\n .describe(\n 'Stable anchor id: the heading own id, a descendant permalink fragment, or a slug of the text (deduped -1, -2, … for generated slugs).',\n ),\n })\n .describe('A single document heading with its stable anchor.'),\n )\n .describe(\n 'Document headings (h1–h6) in document order, each with a stable anchor id.',\n ),\n metadata: z\n .object({\n title: z\n .string()\n .optional()\n .describe(\n 'Document title from <title>, falling back to the first <h1>.',\n ),\n baseUrl: z\n .string()\n .optional()\n .describe('The baseUrl passed in (origin context, never fetched).'),\n })\n .describe('Outline document metadata.'),\n} as const;\n\nexport const outlineOutput = z.object(outlineOutputShape);\n\nexport type OutlineStructuredContent = z.infer<typeof outlineOutput>;\n\nexport const extractMetadataOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'Human-readable key:value rendering of the metadata block, so content[0].text is never empty.',\n ),\n metadata: metadataObjectSchema,\n} as const;\n\nexport const extractMetadataOutput = z.object(extractMetadataOutputShape);\n\nexport type ExtractMetadataStructuredContent = z.infer<\n typeof extractMetadataOutput\n>;\n\nexport const chunkTextOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'Readable index of the chunks (one numbered section per chunk, each prefixed with its heading context), so content[0].text is always scannable.',\n ),\n chunks: z\n .array(chunkObjectSchema)\n .describe(\n 'The emitted chunks in order. Empty array when the input contains no non-whitespace content.',\n ),\n} as const;\n\nexport const chunkTextOutput = z.object(chunkTextOutputShape);\n\nexport type ChunkTextStructuredContent = z.infer<typeof chunkTextOutput>;\n\nexport const linkObjectSchema = z\n .object({\n text: z\n .string()\n .describe(\n 'Anchor text content, whitespace-collapsed and trimmed (capped at 300 chars).',\n ),\n href: z\n .string()\n .describe(\n 'Absolute href (resolved against baseUrl when provided); unchanged when baseUrl is absent or the pair fails to parse.',\n ),\n rel: z\n .string()\n .describe(\n 'The raw rel attribute value (e.g. \"noopener noreferrer\", \"nofollow\"), or the empty string when absent.',\n ),\n isExternal: z\n .boolean()\n .describe(\n 'True when baseUrl is provided and the href parses to a different origin than baseUrl. False for relative, fragment, same-origin, non-http(s) (mailto/tel/javascript), and malformed hrefs.',\n ),\n })\n .describe(\n 'A single anchor link with its text, absolute href, rel, and origin.',\n );\n\nexport const extractLinksOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'Readable rendering of the link list (one `- [text](href)` line per link), so content[0].text is never empty.',\n ),\n links: z\n .array(linkObjectSchema)\n .describe(\n 'Anchors in document order, hrefs absolutized against baseUrl. No deduplication.',\n ),\n metadata: z\n .object({\n baseUrl: z\n .string()\n .optional()\n .describe('The baseUrl passed in (origin context, never fetched).'),\n })\n .describe('Extract-links document metadata.'),\n} as const;\n\nexport const extractLinksOutput = z.object(extractLinksOutputShape);\n\nexport type ExtractLinksStructuredContent = z.infer<typeof extractLinksOutput>;\n\nexport const tableEntrySchema = z\n .object({\n index: z\n .number()\n .int()\n .min(0)\n .describe('0-based position among emitted tables.'),\n rows: z\n .number()\n .int()\n .min(0)\n .describe('Row count of the matrix (after rowspan/colspan resolution).'),\n cols: z\n .number()\n .int()\n .min(0)\n .describe('Column count of the matrix (after colspan resolution).'),\n markdown: z\n .string()\n .describe('The table rendered in the requested format (gfm/csv/json).'),\n })\n .describe('One extracted table with its dimensions and rendered form.');\n\nexport const extractTablesOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'All tables rendered in the requested format, joined by blank lines; \"(no tables found)\" when none.',\n ),\n tables: z\n .array(tableEntrySchema)\n .describe(\n 'Every <table> on the page (rowspan/colspan-resolved), in document order.',\n ),\n metadata: z\n .object({\n baseUrl: z\n .string()\n .optional()\n .describe('The baseUrl passed in (origin context).'),\n format: tableFormatSchema.describe('The requested render format.'),\n tableCount: z.number().int().describe('Number of tables emitted.'),\n })\n .describe('Tables-tool metadata.'),\n} as const;\n\nexport const extractTablesOutput = z.object(extractTablesOutputShape);\n\nexport type ExtractTablesStructuredContent = z.infer<\n typeof extractTablesOutput\n>;\n\nexport const gridEntrySchema = z\n .object({\n rows: z\n .number()\n .int()\n .min(0)\n .describe('Row count of the detected grid (after ragged-row padding).'),\n cols: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Column count of the detected grid (max cell width across rows).',\n ),\n markdown: z\n .string()\n .describe('The grid rendered in the requested format (gfm/csv/json).'),\n })\n .describe('One detected grid with its dimensions and rendered form.');\n\nexport const extractGridOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'The grid rendered in the requested format, or \"(no repeating grid found)\" when no grid is detected.',\n ),\n grid: gridEntrySchema.describe(\n 'The detected grid (dimensions + rendered markdown). Rows/cols are 0 and markdown is empty when nothing is detected.',\n ),\n diagnostics: z\n .object({\n confidence: z\n .enum(['high', 'low', 'medium'])\n .describe(\n '`high` when ≥6 detected data rows, `medium` when ≥3 (minRows), `low` otherwise. Counts the detected cluster only — a recovered header row is inference and does not raise it. `low` for non-grid pages.',\n ),\n containerSelector: z\n .string()\n .describe(\n 'CSS-ish hint (tag#id.class) of the winning container, or the rowSelector in selector mode. Empty when not detected.',\n ),\n detected: z\n .boolean()\n .describe(\n 'True when a repeating grid was found (≥3 same-shape sibling rows each with ≥2 direct element-children, outside nav/header/footer/aside).',\n ),\n rowCount: z\n .number()\n .int()\n .describe(\n 'Number of rows emitted, including any recovered header row. 0 when not detected.',\n ),\n colCount: z\n .number()\n .int()\n .describe(\n 'Number of columns (max cell width across rows). 0 when not detected.',\n ),\n rowTag: z\n .string()\n .describe(\n 'Uppercase DOM tag name of the repeating row container (e.g. \"DIV\", \"TR\", \"LI\"). Empty when not detected.',\n ),\n note: z\n .string()\n .describe(\n 'Short human-readable status: the detection reason when detected, or the rejection reason when not.',\n ),\n })\n .describe('Grid-detection telemetry describing the winning candidate.'),\n metadata: z\n .object({\n baseUrl: z\n .string()\n .optional()\n .describe('The baseUrl passed in (origin context, never fetched).'),\n format: tableFormatSchema.describe('The requested render format.'),\n detected: z.boolean().describe('Mirrors diagnostics.detected.'),\n })\n .describe('Extract-grid document metadata.'),\n} as const;\n\nexport const extractGridOutput = z.object(extractGridOutputShape);\n\nexport type ExtractGridStructuredContent = z.infer<typeof extractGridOutput>;\n\nconst listItemSchema = z\n .object({\n score: z\n .number()\n .int()\n .describe(\n 'Item substance score: primary-anchor text length + non-link body text length. Long titles (real feed items) score higher than short nav labels; items with both a long title and surrounding body text (snippets, excerpts) score highest.',\n ),\n snippet: z\n .string()\n .describe(\n 'Item body text with the title peeled off, whitespace-collapsed and clipped at 200 chars. Empty when the item is title-only.',\n ),\n title: z\n .string()\n .describe(\n 'Primary anchor text (longest-text <a> in the item, whitespace-collapsed). Always non-empty for emitted items.',\n ),\n url: z\n .string()\n .describe(\n 'Absolute href of the primary anchor, resolved against baseUrl. Always non-empty for emitted items.',\n ),\n })\n .describe('One detected list item with its title, URL, snippet, and score.');\n\nexport const extractListOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'Readable rendering of the items (one numbered `title — url` line per item, each followed by an indented snippet), or a single `not a list: …` line when no list structure is detected.',\n ),\n items: z\n .array(listItemSchema)\n .describe(\n 'Detected list items in document order. Empty when the page has no repeated same-shape sibling structure with anchors (e.g. article pages).',\n ),\n diagnostics: z\n .object({\n confidence: z\n .enum(['high', 'low', 'medium'])\n .describe(\n '`high` when ≥6 items and avg score ≥30, `medium` when ≥3 items, `low` otherwise. `low` for non-list pages.',\n ),\n containerSelector: z\n .string()\n .describe(\n 'CSS-ish hint (tag#id.class) of the winning container. Empty when not detected.',\n ),\n detected: z\n .boolean()\n .describe(\n 'True when a list/feed/index structure was found (≥3 same-shape siblings each with a navigation anchor, outside nav/header/footer/aside).',\n ),\n itemCount: z\n .number()\n .int()\n .describe('Number of items emitted. 0 when not detected.'),\n itemTag: z\n .string()\n .describe(\n 'Uppercase DOM tag name of the winning sibling group (e.g. \"TR\", \"LI\", \"ARTICLE\", \"DIV\"). Empty when not detected.',\n ),\n note: z\n .string()\n .describe(\n 'Short human-readable status: the detection reason when detected, or the rejection reason when not.',\n ),\n })\n .describe('List-detection telemetry describing the winning candidate.'),\n metadata: z\n .object({\n baseUrl: z\n .string()\n .optional()\n .describe('The baseUrl passed in (origin context, never fetched).'),\n })\n .describe('Extract-list document metadata.'),\n} as const;\n\nexport const extractListOutput = z.object(extractListOutputShape);\n\nexport type ExtractListStructuredContent = z.infer<typeof extractListOutput>;\n","import { JSDOM } from 'jsdom';\n\nexport interface BuiltDocument {\n readonly document: Document;\n readonly window: Window;\n}\n\n// Built only from caller-supplied `html`; `baseUrl` is origin context for\n// absolutization, never fetched. Parse and the downstream Readability walk run\n// synchronously on the event loop — no worker isolation or wall-clock timeout\n// guards them. A worker pool was deferred rather than rejected: the synchronous\n// parse can't be interrupted in-process, and `worker_threads` can't load the dev\n// hot-reload loop's in-memory Vite modules. `maxNodes` is the only input-size\n// bound until a holistic worker strategy lands; a standalone `timeout` option\n// would mislead (it can't interrupt the parse), so it ships with the worker.\nexport function buildDocument(html: string, baseUrl?: string): BuiltDocument {\n const dom = new JSDOM(html, { url: baseUrl });\n return { document: dom.window.document, window: dom.window };\n}\n\nexport function isElement(node: Node): node is Element {\n return node.nodeType === 1;\n}\n","// Math renderers leave the original LaTeX in a small, engine-agnostic pocket:\n// the `<annotation encoding=\"application/x-tex\">` child of a `<math>` element.\n// KaTeX wraps it inside `.katex`, arXiv/LaTeXML (ar5iv) and MDN ship it in a\n// bare `<math display=\"…\">`, and MathJax v2/v3 do not use `<annotation>` at\n// all — their source lives in `<script type=\"math/tex\">` that the\n// script-removal pass strips. Move each into one controlled marker span BEFORE\n// normalize's `<script>` removal so every engine shares a single turndown rule\n// that emits the LaTeX verbatim.\n\nconst MARKER_CLASS = 'rdrm-math';\nconst DISPLAY_ATTR = 'data-display';\nconst KATEX_CLASS = 'katex';\nconst KATEX_DISPLAY_CLASS = 'katex-display';\nconst TEX_ANNOTATION_SELECTOR = 'annotation[encoding=\"application/x-tex\"]';\nconst MATHJAX_INLINE_TYPE = 'math/tex';\nconst MATHJAX_DISPLAY_TYPE = 'math/tex; mode=display';\n// arXiv/LaTeXML (`ltx_*`) and common docs-site display-math wrappers. A bare\n// `<math display=\"block\">` also flags display, but pages often omit the\n// attribute and signal block context only through the wrapper class.\nconst MATHML_DISPLAY_SELECTOR =\n '.ltx_equation, .ltx_displaymath, .equation-display, .math-display';\n// Placeholder for a math container whose annotation and alttext are both empty\n// or absent; rendered markup has no recoverable LaTeX, so emit a token rather\n// than crash.\nconst BROKEN_PLACEHOLDER = '[?]';\n\nfunction createMarker(\n document: Document,\n tex: string,\n display: boolean,\n): HTMLElement {\n const marker = document.createElement('span');\n marker.className = MARKER_CLASS;\n marker.setAttribute(DISPLAY_ATTR, display ? 'true' : 'false');\n marker.textContent = tex;\n return marker;\n}\n\n// Drives off the annotation, not the wrapper, so one pass recovers LaTeX from\n// every engine that emits `<annotation encoding=\"application/x-tex\">` — KaTeX\n// inside `.katex`, arXiv/MDN inside a bare `<math>`. The container replaced is\n// engine-specific: the `.katex` span (which also drops the `.katex-html`\n// rendered sibling), or the bare `<math>` element. Replacing a container\n// detaches other annotations that lived inside it, so disconnected nodes\n// reached later in document order are skipped. arXiv carries the LaTeX in\n// `<math alttext=\"…\">` as well, which is the fallback when the annotation text\n// is empty.\nfunction convertAnnotations(document: Document): void {\n const annotations = document.querySelectorAll(TEX_ANNOTATION_SELECTOR);\n for (const annotation of Array.from(annotations)) {\n if (!annotation.isConnected) {\n continue;\n }\n try {\n const katex = annotation.closest(`.${KATEX_CLASS}`);\n const math = annotation.closest('math');\n let container: Element = annotation;\n let display = false;\n if (katex) {\n container = katex;\n display = katex.closest(`.${KATEX_DISPLAY_CLASS}`) !== null;\n } else if (math) {\n container = math;\n display =\n math.getAttribute('display') === 'block' ||\n math.closest(MATHML_DISPLAY_SELECTOR) !== null;\n }\n const tex =\n annotation.textContent.trim() ||\n (math?.getAttribute('alttext') ?? '').trim();\n container.replaceWith(\n createMarker(document, tex || BROKEN_PLACEHOLDER, display),\n );\n } catch {\n annotation.replaceWith(createMarker(document, BROKEN_PLACEHOLDER, false));\n }\n }\n}\n\n// A `.katex` that survived the annotation pass had no recoverable annotation\n// (stripped or never present). Its `.katex-html` rendered tree carries no\n// recoverable LaTeX, so swap the whole span for a placeholder marker rather\n// than let rendered spans leak into turndown.\nfunction convertOrphanedKatex(document: Document): void {\n for (const katex of Array.from(\n document.getElementsByClassName(KATEX_CLASS),\n )) {\n if (!katex.isConnected) {\n continue;\n }\n try {\n const display = katex.closest(`.${KATEX_DISPLAY_CLASS}`) !== null;\n katex.replaceWith(createMarker(document, BROKEN_PLACEHOLDER, display));\n } catch {\n // Defensive: malformed markup must not break the extract pipeline.\n }\n }\n}\n\n// MathJax v2/v3 inline + display source scripts. These `<script>` nodes are\n// removed by normalize's generic script-removal AND by DOMPurify, so they must\n// be converted here, before that loop runs. MathJax scripts do not use\n// `<annotation>`, so they are handled separately from `convertAnnotations`.\nfunction convertMathJax(document: Document): void {\n const scripts = document.querySelectorAll(\n `script[type=\"${MATHJAX_INLINE_TYPE}\"], script[type=\"${MATHJAX_DISPLAY_TYPE}\"]`,\n );\n for (const script of Array.from(scripts)) {\n if (!script.isConnected) {\n continue;\n }\n try {\n const rawType = script.getAttribute('type') ?? '';\n const display = rawType.includes('mode=display');\n const tex = script.textContent.trim();\n script.replaceWith(\n createMarker(document, tex || BROKEN_PLACEHOLDER, display),\n );\n } catch {\n script.replaceWith(createMarker(document, BROKEN_PLACEHOLDER, false));\n }\n }\n}\n\n// Walks the pre-normalize document and rewrites every math container —\n// annotation-bearing (KaTeX, bare MathML) or MathJax script — into a\n// `<span class=\"rdrm-math\" data-display=\"…\">LATEX</span>` marker. Idempotent:\n// markers carry no `.katex`/`<math>`/annotation, so a second pass is a no-op.\n// Defensive per node so malformed markup never aborts the pass.\nexport function extractMath(document: Document): void {\n convertAnnotations(document);\n convertOrphanedKatex(document);\n convertMathJax(document);\n}\n","import type { ReadabilityOptions } from '../pipeline/readability.js';\n\nexport type ExtractionMode = 'aggressive' | 'balanced' | 'conservative';\n\nexport interface ResolveReadabilityInput {\n readonly extraction?: ExtractionMode;\n readonly keepClasses?: boolean;\n readonly maxNodes?: number;\n readonly minArticleLength?: number;\n readonly readabilityOverrides?: Readonly<Record<string, unknown>>;\n}\n\n// Readability.js confirmed defaults.\nconst DEFAULT_CHAR_THRESHOLD = 500;\nconst DEFAULT_N_TOP_CANDIDATES = 5;\n\n// Readability keeps a class only by strict `classesToPreserve.includes(cls)`\n// equality (not regex), stripping code-block language tokens by default; list\n// the common highlight.js/prism `language-*` tokens so fences keep their tag.\n// `rdrm-math` is the math marker emitted by `policy/math.ts` — Readability\n// would otherwise strip it before turndown's math rule runs. Applies only when\n// `keepClasses` is false.\nconst CLASSES_TO_PRESERVE: readonly string[] = [\n 'hljs',\n 'language-asm',\n 'language-assembly',\n 'language-bash',\n 'language-c',\n 'language-clojure',\n 'language-cpp',\n 'language-cs',\n 'language-csharp',\n 'language-css',\n 'language-dart',\n 'language-diff',\n 'language-dockerfile',\n 'language-elixir',\n 'language-erlang',\n 'language-go',\n 'language-graphql',\n 'language-haskell',\n 'language-html',\n 'language-ini',\n 'language-java',\n 'language-javascript',\n 'language-js',\n 'language-jsx',\n 'language-json',\n 'language-kotlin',\n 'language-lisp',\n 'language-lua',\n 'language-md',\n 'language-markdown',\n 'language-objc',\n 'language-objectivec',\n 'language-perl',\n 'language-php',\n 'language-plaintext',\n 'language-powershell',\n 'language-py',\n 'language-python',\n 'language-r',\n 'language-rb',\n 'language-rs',\n 'language-ruby',\n 'language-rust',\n 'language-scala',\n 'language-sh',\n 'language-shell',\n 'language-sql',\n 'language-swift',\n 'language-text',\n 'language-toml',\n 'language-ts',\n 'language-tsx',\n 'language-typescript',\n 'language-vim',\n 'language-wasm',\n 'language-xml',\n 'language-yaml',\n 'language-yml',\n 'rdrm-math',\n];\n\n// Bare tokens (the `X` in `language-X`) shared by convention resolvers that\n// must validate a candidate against the preserve set, e.g. sandpack's `sp-*`\n// classes, which carry infra strings alongside the language token. Filter to\n// `language-` entries so non-language preserve additions (`hljs`, `rdrm-math`)\n// do not pollute the set with the empty token.\nexport const KNOWN_LANGUAGE_TOKENS: ReadonlySet<string> = new Set(\n CLASSES_TO_PRESERVE.filter(c => c.startsWith('language-')).map(c =>\n c.slice('language-'.length),\n ),\n);\n\ninterface ModeKnobs {\n readonly charThreshold: number;\n readonly nbTopCandidates: number;\n}\n\nfunction knobsForMode(mode: ExtractionMode): ModeKnobs | null {\n switch (mode) {\n case 'aggressive':\n return {\n charThreshold: Math.round(DEFAULT_CHAR_THRESHOLD / 2),\n nbTopCandidates: DEFAULT_N_TOP_CANDIDATES * 2,\n };\n case 'balanced':\n // null ⇒ emit nothing; Readability uses its own defaults.\n return null;\n case 'conservative':\n return {\n charThreshold: DEFAULT_CHAR_THRESHOLD * 2,\n nbTopCandidates: Math.max(1, Math.round(DEFAULT_N_TOP_CANDIDATES / 2)),\n };\n }\n}\n\nexport function resolveReadabilityOptions(\n input: ResolveReadabilityInput,\n): ReadabilityOptions {\n const mode = input.extraction ?? 'balanced';\n const modeKnobs = knobsForMode(mode);\n const keepClasses = input.keepClasses ?? false;\n\n const charThreshold =\n input.minArticleLength !== undefined\n ? input.minArticleLength\n : modeKnobs?.charThreshold;\n const nbTopCandidates = modeKnobs?.nbTopCandidates;\n\n // Escape hatch spreads last so it wins verbatim over every derived knob.\n return {\n classesToPreserve: keepClasses ? [] : [...CLASSES_TO_PRESERVE],\n keepClasses,\n ...(charThreshold !== undefined ? { charThreshold } : {}),\n ...(nbTopCandidates !== undefined ? { nbTopCandidates } : {}),\n ...(input.maxNodes !== undefined\n ? { maxElemsToParse: input.maxNodes }\n : {}),\n ...(input.readabilityOverrides ?? {}),\n };\n}\n","import { extractMath } from '../policy/math.js';\nimport { KNOWN_LANGUAGE_TOKENS } from '../policy/resolver.js';\n\nconst NONCE_ATTR = 'nonce';\n\nexport interface NormalizeCounts {\n readonly boilerplateRemoved: number;\n readonly chromeRemoved: number;\n readonly iframes: number;\n readonly scripts: number;\n}\n\nexport interface NormalizeOptions {\n readonly cleanChrome?: boolean;\n}\n\nexport function normalizeDocument(\n document: Document,\n options?: NormalizeOptions,\n): NormalizeCounts {\n // MathJax source lives in `<script type=\"math/tex\">`, which the script-removal\n // loop below strips — extract math into markers first so both engines share\n // one turndown rule downstream.\n extractMath(document);\n\n const baseEls = document.querySelectorAll('base');\n // Preserve <script type=\"application/ld+json\">: structured metadata consumed by the cascade.\n const scriptEls = document.querySelectorAll(\n 'script:not([type=\"application/ld+json\"])',\n );\n\n baseEls.forEach(el => {\n el.remove();\n });\n scriptEls.forEach(el => {\n el.remove();\n });\n\n const withNonce = document.querySelectorAll(`[${NONCE_ATTR}]`);\n withNonce.forEach(el => {\n el.removeAttribute(NONCE_ATTR);\n });\n\n const chromeRemoved =\n options?.cleanChrome === false ? 0 : stripChrome(document);\n\n const boilerplateRemoved = stripBoilerplate(document);\n\n return {\n boilerplateRemoved,\n chromeRemoved,\n iframes: 0,\n scripts: scriptEls.length,\n };\n}\n\n// Conservative, vendor-specific consent SDK selectors. Curated, not greedy —\n// intentionally NOT `[class*=\"consent\"]` (that eats real article widgets).\nconst CONSENT_SELECTORS = [\n '[role=\"dialog\"]',\n '[aria-modal=\"true\"]',\n '#onetrust-banner-sdk',\n '#onetrust-consent-sdk',\n '#onetrust-pc-sdk',\n '.cc-window',\n '.cc-banner',\n '.cc-revoke',\n '.osano-cm-window',\n '.osano-cm-dialog',\n '.qc-cmp2-container',\n '.qc-cmp-ui-container',\n '#sp_message_container',\n '[id^=\"sp_message_container_\"]',\n '#didomi-host',\n '.didomi-popup-container',\n '#truste-consent-track',\n '#consent_blackbar',\n '#cookie-banner',\n '.cookie-banner',\n '#consent-banner',\n '.consent-banner',\n '.cookie-bar',\n '.gdpr-banner',\n '.privacy-banner',\n];\n\n// jsdom has no layout, so \"covers the viewport\" must be inferred from inline\n// style. Requiring BOTH axes full is what protects a fixed nav bar: a nav is\n// full-width but its height is a fixed px (not 100%/100vh), and inset:0 is not\n// used. Dropping either axis from the conjunction nukes legit chrome.\nfunction isFullViewportOverlay(style: string): boolean {\n if (!/position\\s*:\\s*(?:fixed|sticky)/i.test(style)) {\n return false;\n }\n const zIndexMatch = /z-index\\s*:\\s*(\\d+)/i.exec(style);\n if (!zIndexMatch || Number.parseInt(zIndexMatch[1], 10) < 1000) {\n return false;\n }\n // `inset:0` is shorthand for top/right/bottom/left = 0, so it satisfies BOTH\n // axes at once and is checked up front.\n if (/inset\\s*:\\s*0/i.test(style)) {\n return true;\n }\n const widthFull =\n /width\\s*:\\s*100(?:%|vw)/i.test(style) ||\n (/left\\s*:\\s*0/i.test(style) && /right\\s*:\\s*0/i.test(style));\n const heightFull =\n /height\\s*:\\s*100(?:%|vh)/i.test(style) ||\n (/top\\s*:\\s*0/i.test(style) && /bottom\\s*:\\s*0/i.test(style));\n return widthFull && heightFull;\n}\n\n// Removes before Readability scores them — these poison density math and leak\n// into the article. Inline-style matches require the full isFullViewportOverlay\n// conjunction; `position:fixed` alone never qualifies.\nexport function stripChrome(document: Document): number {\n let removed = 0;\n\n const consentEls = document.querySelectorAll(CONSENT_SELECTORS.join(','));\n for (const el of consentEls) {\n if (!el.isConnected) {\n continue;\n }\n el.remove();\n removed++;\n }\n\n const styledEls = document.querySelectorAll('[style]');\n for (const el of styledEls) {\n if (!el.isConnected) {\n continue;\n }\n if (isFullViewportOverlay(el.getAttribute('style') ?? '')) {\n el.remove();\n removed++;\n }\n }\n\n return removed;\n}\n\n// Unambiguous boilerplate signatures. Bare `related`/`subscribe` are\n// intentionally excluded — they match legit content (related-products section\n// on a store, a \"subscribe to RSS\" line inside an article) far too often.\n// Substring matching means `newsletter` also covers `newsletter-signup`, etc.;\n// the longer spellings are kept as explicit documentation of intended targets.\nconst BOILERPLATE_TOKENS = [\n 'newsletter',\n 'newsletter-signup',\n 'mailing-list',\n 'email-signup',\n 'subscribe-form',\n 'signup-form',\n 'related-posts',\n 'related-post',\n 'read-next',\n 'more-from',\n 'you-might-also',\n 'recommended-posts',\n 'recommended',\n];\n\nfunction signatureMatches(el: Element): boolean {\n const signature =\n `${el.getAttribute('class') ?? ''} ${el.getAttribute('id') ?? ''}`.toLowerCase();\n return BOILERPLATE_TOKENS.some(token => signature.includes(token));\n}\n\n// A boilerplate block is always a structural CONTAINER. A heading or paragraph\n// can carry a token in its `id` (an anchor target like `id=\"related-posts\"`) or\n// `class`, and individually such a leaf passes the footprint guard — so without\n// a container restriction the rule deletes real article content (e.g. an in-body\n// section heading whose id happens to match). Reject non-containers early.\nconst BOILERPLATE_CONTAINER_TAGS = new Set([\n 'ASIDE',\n 'DIV',\n 'FORM',\n 'NAV',\n 'OL',\n 'SECTION',\n 'UL',\n]);\n\n// Strips \"related posts\" / newsletter signup / \"read next\" blocks Readability\n// sometimes retains. Subtractive and high-risk, so a token hit alone is never\n// enough: the footprint guard requires the candidate to be a small fraction of\n// the body. The body length is snapshotted once so every candidate is judged\n// against the same baseline — recomputing after each removal would make the\n// threshold depend on iteration order. A boilerplate block is always a small\n// slice of a real article; capping at 25% guarantees the worst case is leaving\n// a small block in, never deleting a section that is itself a substantial part\n// of the content. Idempotent and order-independent.\n//\n// Only outermost boilerplate roots are candidates. A single signature CONTAINER\n// (e.g. <aside class=\"newsletter-subscribe\">) puts the token on every descendant\n// (\"newsletter-title\", \"newsletter-submit\", …); stripping those leaves\n// independently mangles the container when the footprint guard preserves the\n// root — a newsletter form shorn of its title and submit button. The ancestor\n// walk rejects any element whose class+id already matched on an ancestor.\n// querySelectorAll yields outer-before-inner order, and removing a root detaches\n// its descendants (caught by !isConnected above), so this walk uniformly rejects\n// leaves of both removed roots and preserved roots.\nexport function stripBoilerplate(document: Document): number {\n const bodyLength = document.body.textContent.length;\n const limit = 0.25 * bodyLength;\n\n let removed = 0;\n for (const el of document.querySelectorAll('[class],[id]')) {\n if (!el.isConnected) {\n continue;\n }\n if (!BOILERPLATE_CONTAINER_TAGS.has(el.tagName)) {\n continue;\n }\n if (!signatureMatches(el)) {\n continue;\n }\n let ancestor: Element | null = el.parentElement;\n while (ancestor && !signatureMatches(ancestor)) {\n ancestor = ancestor.parentElement;\n }\n if (ancestor) {\n continue;\n }\n if (el.textContent.length >= limit) {\n continue;\n }\n el.remove();\n removed++;\n }\n return removed;\n}\n\nexport interface SelectorScope {\n readonly exclude?: readonly string[];\n readonly include?: string;\n}\n\n// Scope the document by CSS selector: drop every `exclude` match, then (if\n// `include` matches) replace the body with the first matching subtree.\nexport function applySelectors(\n document: Document,\n selectors: SelectorScope | undefined,\n): void {\n if (!selectors) {\n return;\n }\n if (selectors.exclude) {\n for (const selector of selectors.exclude) {\n document.querySelectorAll(selector).forEach(el => {\n el.remove();\n });\n }\n }\n if (selectors.include) {\n const body = document.body;\n const root = body.querySelector(selectors.include);\n if (root && root !== body) {\n body.innerHTML = '';\n body.appendChild(root);\n }\n }\n}\n\n// Matched anywhere in a src (case-insensitive) to flag a lazy-load placeholder.\nconst PLACEHOLDER_TOKENS = [\n 'placeholder',\n 'blank',\n 'spacer',\n 'lazy',\n 'loading',\n '1x1',\n 'transparent',\n 'pixel',\n 'dummy',\n];\n\nexport function isPlaceholderSrc(src: string): boolean {\n if (!src) {\n return true;\n }\n if (src.startsWith('data:')) {\n return true;\n }\n const lowered = src.toLowerCase();\n return PLACEHOLDER_TOKENS.some(token => lowered.includes(token));\n}\n\nfunction usableAttr(el: Element, names: readonly string[]): string | undefined {\n for (const name of names) {\n const value = el.getAttribute(name);\n if (value) {\n return value;\n }\n }\n return undefined;\n}\n\n// Largest-descriptor candidate wins (`Nw`/`Nx`, never mixed in valid srcset);\n// first candidate wins when none carry a descriptor. Malformed entries are skipped.\nfunction pickLargestSrcset(srcset: string): string | undefined {\n let bestUrl: string | undefined;\n let bestValue = -1;\n let firstUrl: string | undefined;\n let sawDescriptor = false;\n for (const raw of srcset.split(',')) {\n const entry = raw.trim();\n if (!entry) {\n continue;\n }\n const splitAt = entry.search(/\\s/);\n const rawUrl = splitAt === -1 ? entry : entry.slice(0, splitAt);\n const descriptor = splitAt === -1 ? '' : entry.slice(splitAt).trim();\n const url =\n rawUrl.length >= 2 &&\n ((rawUrl.startsWith('\"') && rawUrl.endsWith('\"')) ||\n (rawUrl.startsWith(\"'\") && rawUrl.endsWith(\"'\")))\n ? rawUrl.slice(1, -1)\n : rawUrl;\n if (!url) {\n continue;\n }\n if (firstUrl === undefined) {\n firstUrl = url;\n }\n if (!descriptor) {\n continue;\n }\n const value = /^(\\d+(?:\\.\\d+)?)[wx]$/.exec(descriptor);\n if (!value) {\n continue;\n }\n sawDescriptor = true;\n const numeric = Number.parseFloat(value[1]);\n if (numeric > bestValue) {\n bestValue = numeric;\n bestUrl = url;\n }\n }\n return sawDescriptor ? bestUrl : firstUrl;\n}\n\n// Skip media-constrained <source>: art-direction crops for specific viewports,\n// not the default image the page shows.\nfunction resolveFromPicture(img: HTMLImageElement): string | undefined {\n const picture = img.closest('picture');\n if (!picture) {\n return undefined;\n }\n for (const source of picture.querySelectorAll('source')) {\n if (source.hasAttribute('media')) {\n continue;\n }\n const srcset = source.getAttribute('srcset');\n if (srcset) {\n const url = pickLargestSrcset(srcset);\n if (url) {\n return url;\n }\n }\n }\n return undefined;\n}\n\n// Precedence (first usable wins): data-src → <picture><source srcset> → own\n// srcset (largest) → data-original → data-lazy-src.\nfunction resolveRealSource(img: HTMLImageElement): string | undefined {\n const dataSrc = usableAttr(img, ['data-src']);\n if (dataSrc) {\n return dataSrc;\n }\n const fromPicture = resolveFromPicture(img);\n if (fromPicture) {\n return fromPicture;\n }\n const ownSrcset = img.getAttribute('srcset');\n if (ownSrcset) {\n const url = pickLargestSrcset(ownSrcset);\n if (url) {\n return url;\n }\n }\n return usableAttr(img, ['data-original', 'data-lazy-src']);\n}\n\n// Swap placeholder src values for the real source before Readability clones.\n// Only a placeholder-like current src is rewritten, so a real src is left alone\n// even if a data-src also exists. Idempotent.\nexport function resolveLazyImages(document: Document): number {\n let resolved = 0;\n for (const img of document.querySelectorAll('img')) {\n const currentSrc = img.getAttribute('src') ?? '';\n if (!isPlaceholderSrc(currentSrc)) {\n continue;\n }\n const real = resolveRealSource(img);\n if (real && real !== currentSrc) {\n img.setAttribute('src', real);\n resolved++;\n }\n }\n return resolved;\n}\n\n// Readability keeps a class only by literal `classesToPreserve.includes(cls)`\n// equality, so non-canonical code-block conventions — GitHub's\n// `highlight-source-js`, sandpack's `sp-javascript`, SyntaxHighlighter's\n// `brush: js` — are stripped before turndown runs and the fence loses its\n// language tag. Rewrite each convention to the canonical\n// `<code class=\"language-X\">` form before Readability clones the document.\nconst HIGHLIGHT_SOURCE_PREFIX = 'highlight-source-';\nconst LANGUAGE_PREFIX = 'language-';\nconst LANG_PREFIX = 'lang-';\nconst SP_PREFIX = 'sp-';\nconst BRUSH_RE = /brush:\\s*([A-Za-z][\\w-]*)/;\nconst BOGUS_TOKENS = new Set(['', 'highlight', 'source', 'sp']);\n\ninterface CodeToken {\n readonly fromHighlightSource: boolean;\n readonly token: string;\n}\n\nfunction isValidToken(token: string): boolean {\n return !BOGUS_TOKENS.has(token);\n}\n\nfunction hasLanguageClass(el: Element): boolean {\n for (const cls of el.classList) {\n if (cls !== LANGUAGE_PREFIX && cls.startsWith(LANGUAGE_PREFIX)) {\n return true;\n }\n }\n return false;\n}\n\ninterface ClassSource {\n readonly classes: readonly string[];\n readonly raw: string;\n}\n\n// Pool the <pre>'s own classes, then ancestors up to the highlight wrapper\n// (capped at two levels), then any descendant <code>'s classes — in that order.\nfunction collectClassSources(\n pre: Element,\n code: Element | null,\n): ClassSource[] {\n const sources: ClassSource[] = [\n { classes: [...pre.classList], raw: pre.getAttribute('class') ?? '' },\n ];\n let depth = 0;\n let el: Element | null = pre.parentElement;\n while (el && depth < 2) {\n sources.push({\n classes: [...el.classList],\n raw: el.getAttribute('class') ?? '',\n });\n depth++;\n if (\n el.tagName === 'DIV' &&\n [...el.classList].some(c => c.startsWith('highlight'))\n ) {\n break;\n }\n el = el.parentElement;\n }\n if (code) {\n sources.push({\n classes: [...code.classList],\n raw: code.getAttribute('class') ?? '',\n });\n }\n return sources;\n}\n\nfunction resolveCodeToken(\n pre: Element,\n code: Element | null,\n): CodeToken | null {\n const sources = collectClassSources(pre, code);\n\n function findByPrefix(prefix: string): null | string {\n for (const src of sources) {\n for (const cls of src.classes) {\n if (cls.startsWith(prefix)) {\n const token = cls.slice(prefix.length).toLowerCase();\n if (isValidToken(token)) {\n return token;\n }\n }\n }\n }\n return null;\n }\n\n // Convention priority: highlight-source > language > lang > sp > brush.\n // `brush:` is a class-attribute value, not a single class token, so it cannot\n // be matched by prefix and is parsed from the raw attribute string instead.\n const highlightToken = findByPrefix(HIGHLIGHT_SOURCE_PREFIX);\n if (highlightToken) {\n return { token: highlightToken, fromHighlightSource: true };\n }\n const languageToken = findByPrefix(LANGUAGE_PREFIX);\n if (languageToken) {\n return { token: languageToken, fromHighlightSource: false };\n }\n const langToken = findByPrefix(LANG_PREFIX);\n if (langToken) {\n return { token: langToken, fromHighlightSource: false };\n }\n // Sandpack emits infra classes alongside the language class (sp-cm,\n // sp-pristine, sp-pre-placeholder); unlike the single-token highlight-source\n // / language / lang conventions, SP must pick the class whose token is a\n // recognized language or it grabs an infra string the preserve list strips.\n for (const src of sources) {\n for (const cls of src.classes) {\n if (!cls.startsWith(SP_PREFIX)) {\n continue;\n }\n const token = cls.slice(SP_PREFIX.length).toLowerCase();\n if (KNOWN_LANGUAGE_TOKENS.has(token)) {\n return { token, fromHighlightSource: false };\n }\n }\n }\n for (const src of sources) {\n const match = BRUSH_RE.exec(src.raw);\n if (match) {\n const token = match[1].toLowerCase();\n if (isValidToken(token)) {\n return { token, fromHighlightSource: false };\n }\n }\n }\n return null;\n}\n\nfunction canonicalizePre(pre: Element): boolean {\n const code = pre.querySelector('code');\n // turndown reads the language from the <code> class; if one is present the\n // block is already canonical and rewriting it can only drop information.\n if (code && hasLanguageClass(code)) {\n return false;\n }\n const match = resolveCodeToken(pre, code);\n if (!match) {\n return false;\n }\n\n let target = code;\n if (!target) {\n target = pre.ownerDocument.createElement('code');\n while (pre.firstChild) {\n target.appendChild(pre.firstChild);\n }\n pre.appendChild(target);\n }\n\n const classes = [`language-${match.token}`];\n if (target.classList.contains('hljs')) {\n classes.push('hljs');\n }\n target.setAttribute('class', classes.join(' '));\n\n // GitHub carries the language on a <div class=\"highlight\"> wrapper rather than\n // the <pre>; once that hint is moved onto <code>, hoist the <pre> out so the\n // block stands alone instead of inside a div Readability scores separately.\n if (match.fromHighlightSource) {\n const parent = pre.parentElement;\n if (\n parent?.tagName === 'DIV' &&\n [...parent.classList].some(c => c.startsWith('highlight'))\n ) {\n parent.replaceWith(pre);\n }\n }\n return true;\n}\n\nexport function canonicalizeCodeBlocks(document: Document): number {\n let count = 0;\n for (const pre of document.querySelectorAll('pre')) {\n if (!pre.isConnected) {\n continue;\n }\n try {\n if (canonicalizePre(pre)) {\n count++;\n }\n } catch {\n // Defensive: malformed markup must not break the extract pipeline.\n }\n }\n return count;\n}\n","import { isProbablyReaderable, Readability } from '@mozilla/readability';\n\nexport interface ReadabilityParseResult {\n readonly byline?: null | string;\n readonly content?: null | string;\n readonly dir?: null | string;\n readonly excerpt?: null | string;\n readonly lang?: null | string;\n readonly length?: null | number;\n readonly publishedTime?: null | string;\n readonly siteName?: null | string;\n readonly textContent?: null | string;\n readonly title?: null | string;\n}\n\nexport interface ReadabilityOptions {\n readonly charThreshold?: number;\n readonly keepClasses?: boolean;\n readonly maxElemsToParse?: number;\n readonly nbTopCandidates?: number;\n readonly [key: string]: unknown;\n}\n\nexport function isReaderable(document: Document): boolean {\n return isProbablyReaderable(document);\n}\n\nexport function parseArticle(\n document: Document,\n options?: Readonly<ReadabilityOptions>,\n): null | ReadabilityParseResult {\n // Readability mutates its input document; clone so the pipeline's doc is untouched.\n const clone = document.cloneNode(true) as Document;\n const reader = new Readability(clone, options);\n return reader.parse();\n}\n","import type {\n CacheSignal,\n Diagnostics,\n SanitizationDiagnostics,\n TraceStage,\n} from '../pipeline/context.js';\nimport type { GatingSignal } from './gating.js';\nimport type { PaginationSignal } from './pagination.js';\n\nexport interface DiagnosticsInput {\n readonly articleHtml?: string;\n readonly boilerplateRemoved?: number;\n readonly cache?: CacheSignal;\n readonly chromeRemoved?: number;\n readonly document?: Document;\n readonly documentElementCount?: number;\n readonly extractedNode?: string;\n readonly fallbackUsed?: boolean;\n readonly gated?: GatingSignal;\n readonly imagesResolved?: number;\n readonly pagination?: PaginationSignal;\n readonly readerable?: boolean;\n readonly sanitization?: SanitizationDiagnostics;\n readonly trace?: readonly TraceStage[];\n readonly truncated?: boolean;\n readonly window?: Window;\n}\n\nfunction countElements(html: string, window?: Window): number {\n if (!window || !html) {\n return 0;\n }\n const template = window.document.createElement('div');\n template.innerHTML = html;\n return template.querySelectorAll('*').length;\n}\n\nexport function assembleDiagnostics(\n input: Readonly<DiagnosticsInput>,\n): Diagnostics {\n const articleElementCount = countElements(\n input.articleHtml ?? '',\n input.window,\n );\n const removedNodes = Math.max(\n 0,\n (input.documentElementCount ?? 0) - articleElementCount,\n );\n\n return {\n readerable: input.readerable,\n extractedNode: input.extractedNode,\n fallbackUsed: input.fallbackUsed ?? false,\n gated: input.gated,\n imagesResolved: input.imagesResolved,\n pagination: input.pagination,\n removedNodes,\n boilerplateRemoved: input.boilerplateRemoved,\n chromeRemoved: input.chromeRemoved,\n sanitization: input.sanitization,\n trace: input.trace,\n truncated: input.truncated ?? false,\n ...(input.cache ? { cache: input.cache } : {}),\n };\n}\n\n// Emits trace entries only when enabled, so the debug-off hot path allocates\n// nothing (the fn runs bare). Used by the orchestrator tools to time pipeline\n// stages without threading timers into lower layers.\nexport class TraceCollector {\n private readonly enabled: boolean;\n private readonly entries: TraceStage[] = [];\n\n constructor(enabled: boolean) {\n this.enabled = enabled;\n }\n\n collect(): readonly TraceStage[] | undefined {\n return this.enabled ? this.entries : undefined;\n }\n\n run<T>(stage: string, fn: () => T): T {\n if (!this.enabled) {\n return fn();\n }\n const start = performance.now();\n try {\n return fn();\n } finally {\n this.entries.push({ ms: performance.now() - start, stage });\n }\n }\n}\n","export interface GatingSignal {\n readonly likely: boolean;\n readonly reason: string;\n}\n\n// Curated vendor/structural paywall selectors — intentionally NOT a greedy\n// `[class*=\"subscribe\"]` (that catches newsletter CTAs on clean articles and\n// false-positives). Each entry names a known paywall surface.\nconst PAYWALL_SELECTORS = [\n '[class*=\"paywall\"]',\n '[id*=\"paywall\"]',\n '.piano',\n '#piano',\n '.tp-modal',\n '.tp-active',\n '[id*=\"piano\"]',\n '[class*=\"piano\"]',\n '[class*=\"subscribe-wall\"]',\n '[id*=\"subscribe-wall\"]',\n '[class*=\"metered-wall\"]',\n '[id*=\"metered-wall\"]',\n '.leaky-paywall',\n] as const;\n\n// Phrases that essentially never appear on a fully-unlocked article. Bare\n// \"Subscribe\" nav links / newsletter CTAs are intentionally excluded — they\n// are ubiquitous and would mislead the host into discarding complete content.\nconst METERED_TEXT_RE =\n /(\\d+)\\s*(?:free\\s*)?(?:articles?|stories?)\\s*(?:left|remaining)|you\\s+have\\s+reached\\s+(?:your\\s+)?(?:free\\s+)?(?:article\\s+|story\\s+)?limit|subscribe\\s+to\\s+(?:continue\\s+)?reading|read\\s+the\\s+full\\s+(?:article|story)|unlock\\s+(?:this|full|all)\\s+(?:article|story|content)|keep\\s+reading\\s+with/i;\n\nfunction findPaywallOverlay(document: Document): GatingSignal | undefined {\n for (const selector of PAYWALL_SELECTORS) {\n const el = document.querySelector(selector);\n if (el?.isConnected) {\n return { likely: true, reason: 'paywall overlay' };\n }\n }\n return undefined;\n}\n\n// One textContent read is cheaper than a per-element scan of a large doc and\n// avoids ordering a query per selector. jsdom computes body.textContent once.\nfunction findMeteredMessage(document: Document): GatingSignal | undefined {\n const text = document.body.textContent;\n if (METERED_TEXT_RE.test(text)) {\n return { likely: true, reason: 'metered paywall message' };\n }\n return undefined;\n}\n\n// Reads the DOM only — never fetches, authenticates, or mutates the document.\n// Conservative by design: a false positive misleads the host into treating\n// complete content as truncated, which is worse than a miss.\nexport function detectGating(document: Document): GatingSignal | undefined {\n return findPaywallOverlay(document) ?? findMeteredMessage(document);\n}\n","// Shared by the turndown rules (image/anchor absolutization) and the\n// extract_links tool. Returns the input unchanged when either side is missing\n// or the URL constructor rejects the pair, so callers never throw on bad input.\nexport function absolutize(src: string, baseUrl: string | undefined): string {\n if (!src || !baseUrl) {\n return src;\n }\n try {\n return new URL(src, baseUrl).href;\n } catch {\n return src;\n }\n}\n","import { absolutize } from '../pipeline/urls.js';\n\nexport interface PaginationSignal {\n readonly nextUrl?: string;\n readonly selector?: string;\n readonly type: 'infinite' | 'paginated';\n}\n\n// Short pagination-style link text only; matching prose like \"next, we cover\n// rendering\" would burn an article's internal link as a next-page signal.\n// `next ›/»/→` and bare arrows cover the common next-page glyphs in one place.\nconst NEXT_LINK_TEXT_RE =\n /^(next(\\s+page)?|older(\\s+posts?)?|[›»→]|next\\s*[›»→]|older\\s*[›»→])$/i;\n\nfunction usableHref(href: null | string): string | undefined {\n if (!href || href === '#') {\n return undefined;\n }\n return href;\n}\n\nfunction findPaginated(\n document: Document,\n baseUrl: string | undefined,\n): PaginationSignal | undefined {\n const linkNext = document.querySelector('link[rel=\"next\"][href]');\n if (linkNext) {\n const href = usableHref(linkNext.getAttribute('href'));\n if (href) {\n return { type: 'paginated', nextUrl: absolutize(href, baseUrl) };\n }\n }\n\n const aRelNext = document.querySelector('a[rel=\"next\"][href]');\n if (aRelNext) {\n const href = usableHref(aRelNext.getAttribute('href'));\n if (href) {\n return { type: 'paginated', nextUrl: absolutize(href, baseUrl) };\n }\n }\n\n for (const anchor of document.querySelectorAll('a[href]')) {\n const text = anchor.textContent.trim();\n if (!text || !NEXT_LINK_TEXT_RE.test(text)) {\n continue;\n }\n const href = usableHref(anchor.getAttribute('href'));\n if (!href) {\n continue;\n }\n return { type: 'paginated', nextUrl: absolutize(href, baseUrl) };\n }\n\n return undefined;\n}\n\nconst INFINITE_ATTR_SELECTORS = [\n '[data-load-more]',\n '[data-infinite-scroll]',\n '[data-pagination]',\n '[infinite-scroll]',\n] as const;\n\nconst INFINITE_SUBSTRING_SELECTORS = [\n '[class*=\"load-more\"]',\n '[class*=\"loadmore\"]',\n '[class*=\"infinite\"]',\n '[id*=\"load-more\"]',\n '[class*=\"sentinel\"]',\n] as const;\n\nconst LOAD_MORE_BUTTON_RE =\n /^(load more|show more|view more|more results|load more comments)$/i;\n\nfunction findInfinite(document: Document): PaginationSignal | undefined {\n for (const selector of INFINITE_ATTR_SELECTORS) {\n const el = document.querySelector(selector);\n if (el?.isConnected) {\n return { selector, type: 'infinite' };\n }\n }\n for (const selector of INFINITE_SUBSTRING_SELECTORS) {\n const el = document.querySelector(selector);\n if (el?.isConnected) {\n return { selector, type: 'infinite' };\n }\n }\n for (const button of document.querySelectorAll('button')) {\n const text = button.textContent.trim();\n if (text && LOAD_MORE_BUTTON_RE.test(text)) {\n return { selector: 'button', type: 'infinite' };\n }\n }\n return undefined;\n}\n\n// Paginated beats infinite: an explicit \"next\" link is a stronger signal than\n// a load-more sentinel. Read-only — never follows or fetches.\nexport function detectPagination(\n document: Document,\n baseUrl?: string,\n): PaginationSignal | undefined {\n return findPaginated(document, baseUrl) ?? findInfinite(document);\n}\n","import { readFileSync } from 'node:fs';\n\nexport function readHtmlFile(localPath: string): string {\n const raw = readFileSync(localPath, 'utf8');\n const trimmed = raw.trim();\n // A top-level JSON string scalar is a chrome-devtools capture (its JSON-encoded\n // return value written to disk), not raw markup — unwrap it before parsing.\n if (trimmed.length < 2 || trimmed.at(0) !== '\"' || trimmed.at(-1) !== '\"') {\n return raw;\n }\n try {\n const parsed: unknown = JSON.parse(trimmed);\n return typeof parsed === 'string' ? parsed : raw;\n } catch {\n return raw;\n }\n}\n","// Resolve a table/grid cell element to the string that represents it in the\n// matrix. Shared by policy/tables.ts (the <table> serializer) and\n// policy/grid-detector.ts (the div-grid detector) so both paths project a cell\n// the same way: whitespace-normalized visible text, with two fallbacks.\n//\n// 1. Tooltip/badge/aria wrappers carry chrome, not data — they are stripped\n// from a clone so a label cell reads \"Market Capitalization\" rather than\n// concatenating every descendant. But stripping must never empty a cell: a\n// badge/aria element that holds a cell's only text is data, not chrome, so\n// when stripping would blank the cell the full text is kept.\n// 2. A cell whose only content is an icon link has no visible text at all; its\n// value lives in the anchor's href, which is surfaced as a last resort.\nconst CELL_CHROME_SELECTOR =\n '[aria-label], [data-tooltip], [data-toggle=\"tooltip\"], .tooltip, .badge';\n\nfunction normalize(text: string): string {\n return text.replace(/\\s+/g, ' ').trim();\n}\n\n// All non-empty hrefs reachable from the cell, including the cell itself when it\n// is an anchor (a div-grid \"cell\" can be an <a>). Descendant-only search would\n// miss a link-cell whose element IS the anchor.\nfunction nonEmptyHrefs(cell: Element): string[] {\n const anchors: Element[] =\n cell.tagName === 'A'\n ? [cell, ...Array.from(cell.querySelectorAll('a[href]'))]\n : Array.from(cell.querySelectorAll('a[href]'));\n const hrefs: string[] = [];\n for (const a of anchors) {\n const href = (a.getAttribute('href') ?? '').trim();\n if (href !== '') {\n hrefs.push(href);\n }\n }\n return hrefs;\n}\n\nexport function resolveCellText(cell: Element): string {\n const full = normalize(cell.textContent);\n let resolved = full;\n if (full !== '') {\n const clone = cell.cloneNode(true) as Element;\n clone.querySelectorAll(CELL_CHROME_SELECTOR).forEach(el => {\n el.remove();\n });\n const stripped = normalize(clone.textContent);\n resolved = stripped !== '' ? stripped : full;\n }\n if (resolved === '') {\n const hrefs = nonEmptyHrefs(cell);\n if (hrefs.length > 0) {\n return hrefs.join(' ');\n }\n }\n return resolved;\n}\n","import { resolveCellText } from './cell-text.js';\n\nexport type TableFormat = 'csv' | 'gfm' | 'json';\n\n// `<thead>`/`<tbody>`/`<tfoot>` group `<tr>` rows; a `<table>` may also hold `<tr>`\n// directly. Walking these levels (and only these) keeps nested `<table>`s out of\n// the matrix — those are emitted as their own replacement by turndown.\nconst SECTION_TAGS = new Set(['TBODY', 'TFOOT', 'THEAD']);\nconst CELL_TAGS = new Set(['TD', 'TH']);\n\nfunction spanOf(cell: Element, attr: 'colspan' | 'rowspan'): number {\n const raw = cell.getAttribute(attr);\n if (raw === null) {\n return 1;\n }\n const parsed = Number.parseInt(raw, 10);\n // HTML treats 0 (and any non-positive / unparseable value) as 1.\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return 1;\n }\n return parsed;\n}\n\nfunction collectRows(table: Element): readonly Element[] {\n const rows: Element[] = [];\n for (const child of Array.from(table.children)) {\n if (child.tagName === 'TR') {\n rows.push(child);\n } else if (SECTION_TAGS.has(child.tagName)) {\n for (const tr of Array.from(child.children)) {\n if (tr.tagName === 'TR') {\n rows.push(tr);\n }\n }\n }\n }\n return rows;\n}\n\nfunction cellsOf(tr: Element): readonly Element[] {\n return Array.from(tr.children).filter(child => CELL_TAGS.has(child.tagName));\n}\n\n// Text projection of buildCellGrid: origin cells → resolveCellText, span/pad slots → ''.\nexport function parseTableMatrix(table: Element): string[][] {\n return buildCellGrid(table).map(row =>\n row.map(cell => (cell === null ? '' : resolveCellText(cell))),\n );\n}\n\nfunction escapeGfmCell(text: string): string {\n return text.replace(/\\\\/g, '\\\\\\\\').replace(/\\|/g, '\\\\|');\n}\n\nexport function renderTableGfm(matrix: string[][]): string {\n if (matrix.length === 0) {\n return '';\n }\n const cols = matrix[0].length;\n const lines: string[] = [];\n lines.push(`| ${matrix[0].map(escapeGfmCell).join(' | ')} |`);\n lines.push(`| ${Array.from({ length: cols }, () => '---').join(' | ')} |`);\n for (let r = 1; r < matrix.length; r++) {\n lines.push(`| ${matrix[r].map(escapeGfmCell).join(' | ')} |`);\n }\n return lines.join('\\n');\n}\n\nfunction escapeCsvField(text: string): string {\n if (/[\",\\r\\n]/.test(text)) {\n return `\"${text.replace(/\"/g, '\"\"')}\"`;\n }\n return text;\n}\n\nexport function renderTableCsv(matrix: string[][]): string {\n if (matrix.length === 0) {\n return '';\n }\n return matrix.map(row => row.map(escapeCsvField).join(',')).join('\\n');\n}\n\nfunction headerKeys(header: readonly string[]): string[] {\n return header.map((cell, i) => (cell === '' ? `column_${i}` : cell));\n}\n\n// Tracks the originating cell element at each grid position after resolving\n// rowspan/colspan, so resolveHeaderKeys can read td/th attributes the text\n// matrix discards. parseTableMatrix is the text projection of this grid; `null`\n// marks positions filled by a span (no origin cell).\nfunction buildCellGrid(table: Element): (Element | null)[][] {\n const rows = collectRows(table);\n if (rows.length === 0) {\n return [];\n }\n const grid: (Element | null)[][] = [];\n const occupied: boolean[][] = [];\n let maxCols = 0;\n\n for (let r = 0; r < rows.length; r++) {\n while (grid.length <= r) {\n grid.push([]);\n occupied.push([]);\n }\n const rowCells = grid[r];\n const rowOccupied = occupied[r];\n\n let col = 0;\n for (const cell of cellsOf(rows[r])) {\n while (rowOccupied[col]) {\n col++;\n }\n const rowspan = spanOf(cell, 'rowspan');\n const colspan = spanOf(cell, 'colspan');\n rowCells[col] = cell;\n rowOccupied[col] = true;\n for (let dr = 0; dr < rowspan; dr++) {\n for (let dc = 0; dc < colspan; dc++) {\n if (dr === 0 && dc === 0) {\n continue;\n }\n const rr = r + dr;\n while (grid.length <= rr) {\n grid.push([]);\n occupied.push([]);\n }\n const occ = occupied[rr];\n while (occ.length <= col + dc) {\n occ.push(false);\n grid[rr].push(null);\n }\n occ[col + dc] = true;\n }\n }\n col += colspan;\n if (col > maxCols) {\n maxCols = col;\n }\n }\n }\n\n const dense: (Element | null)[][] = [];\n for (const row of grid) {\n dense.push(Array.from({ length: maxCols }, (_, i) => row[i] ?? null));\n }\n return dense;\n}\n\nfunction isHeaderRow(tr: Element): boolean {\n const cells = cellsOf(tr);\n return cells.length > 0 && cells.every(c => c.tagName === 'TH');\n}\n\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '');\n}\n\nfunction readCellLabel(cell: Element): null | string {\n return (\n cell.getAttribute('aria-label') ??\n cell.getAttribute('title') ??\n cell.getAttribute('data-label')\n );\n}\n\nexport function resolveHeaderKeys(\n table: Element,\n matrix: string[][],\n): string[] {\n if (matrix.length === 0) {\n return [];\n }\n const width = matrix[0].length;\n const keys = headerKeys(matrix[0]);\n\n const rows = collectRows(table);\n if (rows.length === 0) {\n return keys;\n }\n\n const cellGrid = buildCellGrid(table);\n\n // Headerless columns lose their name without a th; recover from a colspan\n // parent in row 0, falling back to the position under that parent.\n const parents: string[] = Array.from({ length: width }, () => '');\n const parentChildren = new Map<string, number[]>();\n {\n let col = 0;\n for (const cell of cellsOf(rows[0])) {\n const colspan = spanOf(cell, 'colspan');\n const text = resolveCellText(cell);\n if (colspan > 1 && text) {\n const slug = slugify(text);\n const kids: number[] = [];\n for (let dc = 0; dc < colspan && col + dc < width; dc++) {\n parents[col + dc] = slug;\n kids.push(col + dc);\n }\n parentChildren.set(slug, kids);\n }\n col += colspan;\n }\n }\n\n const hasSubRow = rows.length > 1 && isHeaderRow(rows[1]);\n\n for (let c = 0; c < width; c++) {\n const dataCell = cellGrid[1]?.[c];\n if (dataCell) {\n const label = readCellLabel(dataCell);\n if (label) {\n keys[c] = label;\n continue;\n }\n }\n\n const parent = parents[c];\n if (!parent) {\n continue;\n }\n\n if (hasSubRow) {\n const sub = matrix[1][c] ? slugify(matrix[1][c]) : '';\n if (sub) {\n keys[c] = `${parent}_${sub}`;\n continue;\n }\n }\n\n if (matrix[0][c] === '') {\n const kids = parentChildren.get(parent);\n const idx = kids ? kids.indexOf(c) + 1 : c;\n keys[c] = `${parent}_${idx}`;\n }\n }\n return keys;\n}\n\nfunction resolveJsonKeys(\n matrix: string[][],\n keys: readonly string[] | undefined,\n): string[] {\n const width = matrix[0].length;\n if (!keys) {\n return headerKeys(matrix[0]);\n }\n return Array.from({ length: width }, (_, i) => keys[i] ?? `column_${i}`);\n}\n\nexport function renderTableJson(\n matrix: string[][],\n keys?: readonly string[],\n): string {\n if (matrix.length < 2) {\n return '[]';\n }\n const resolved = resolveJsonKeys(matrix, keys);\n const records: Record<string, string>[] = [];\n for (let r = 1; r < matrix.length; r++) {\n const row = matrix[r];\n const record: Record<string, string> = {};\n for (let c = 0; c < resolved.length; c++) {\n record[resolved[c]] = row[c] ?? '';\n }\n records.push(record);\n }\n return JSON.stringify(records, null, 2);\n}\n\nexport function renderTable(\n matrix: string[][],\n format: TableFormat,\n keys?: readonly string[],\n): string {\n switch (format) {\n case 'csv':\n return renderTableCsv(matrix);\n case 'gfm':\n return renderTableGfm(matrix);\n case 'json':\n return renderTableJson(matrix, keys);\n }\n}\n","export const TOKEN_ESTIMATOR = 'chars/4';\n\nexport function estimateTokens(textContent: string): {\n estimator: string;\n tokenEstimate: number;\n} {\n return {\n tokenEstimate: Math.round(textContent.length / 4),\n estimator: TOKEN_ESTIMATOR,\n };\n}\n\nexport function countWords(text: string): number {\n return (text.match(/\\S+/g) ?? []).length;\n}\n\nexport function nonEmpty(value: string | undefined): string | undefined {\n return value?.trim() ? value : undefined;\n}\n\nexport interface TextMetrics {\n readonly estimator: string;\n readonly readingTimeMin: number;\n readonly tokenEstimate: number;\n readonly wordCount: number;\n}\n\nexport function computeTextMetrics(\n text: string,\n wordsPerMinute: number,\n): TextMetrics {\n const wordCount = countWords(text);\n const readingTimeMin =\n wordCount === 0 ? 0 : Math.max(1, Math.round(wordCount / wordsPerMinute));\n return { wordCount, readingTimeMin, ...estimateTokens(text) };\n}\n","import type { Metadata, StructuredData } from '../pipeline/context.js';\nimport type { ReadabilityParseResult } from '../pipeline/readability.js';\n\nimport { estimateTokens, nonEmpty } from './text.js';\n\nconst ARTICLE_TYPES = new Set([\n 'Article',\n 'BlogPosting',\n 'NewsArticle',\n 'Report',\n 'ScholarlyArticle',\n 'SocialMediaPosting',\n 'TechArticle',\n 'WebPage',\n]);\n\ntype JsonLdValue =\n boolean | JsonLdObject | JsonLdValue[] | null | number | string;\ninterface JsonLdObject {\n readonly [key: string]: JsonLdValue;\n}\n\nfunction first(...values: readonly (string | undefined)[]): string | undefined {\n for (const value of values) {\n const picked = nonEmpty(value);\n if (picked) {\n return picked;\n }\n }\n return undefined;\n}\n\nfunction metaProperty(\n document: Document,\n property: string,\n): string | undefined {\n return nonEmpty(\n document\n .querySelector(`meta[property=\"${property}\"]`)\n ?.getAttribute('content') ?? undefined,\n );\n}\n\nfunction metaName(document: Document, name: string): string | undefined {\n return nonEmpty(\n document.querySelector(`meta[name=\"${name}\"]`)?.getAttribute('content') ??\n undefined,\n );\n}\n\n// Flatten ld+json scripts (a script may hold an array or a @graph array).\n// Invalid JSON is skipped — never crash extraction over malformed metadata.\nfunction parseJsonLd(document: Document): JsonLdObject[] {\n const nodes = document.querySelectorAll('script[type=\"application/ld+json\"]');\n const out: JsonLdObject[] = [];\n nodes.forEach(node => {\n const raw = node.textContent;\n if (!raw.trim()) {\n return;\n }\n let parsed: JsonLdValue;\n try {\n parsed = JSON.parse(raw) as JsonLdValue;\n } catch {\n return;\n }\n collectObjects(parsed, out);\n });\n return out;\n}\n\nfunction collectObjects(value: JsonLdValue, out: JsonLdObject[]): void {\n if (!value || typeof value !== 'object') {\n return;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n collectObjects(item, out);\n }\n return;\n }\n // `in` guard: the index signature types values as JsonLdValue (no undefined),\n // so a `!== undefined` check has no overlap.\n if ('@graph' in value) {\n collectObjects(value['@graph'], out);\n }\n out.push(value);\n}\n\nfunction typeMatches(type: JsonLdValue): boolean {\n const types = Array.isArray(type) ? type : [type];\n return types.some(t => typeof t === 'string' && ARTICLE_TYPES.has(t));\n}\n\nfunction pickArticleNode(\n candidates: readonly JsonLdObject[],\n): JsonLdObject | undefined {\n if (candidates.length === 0) {\n return undefined;\n }\n return candidates.find(node => typeMatches(node['@type'])) ?? candidates[0];\n}\n\n// Preference order for the primary structured object: richest schema.org\n// main-entity first. Article types are the tail fallback — Readability already\n// captured the article body, so they are low-value-but-recognizable. Generic\n// graph scaffolding (WebSite/Organization/BreadcrumbList) is intentionally NOT\n// listed: emitting it as `structured` would be noise, and we prefer undefined\n// over a misleading trivial node.\nconst STRUCTURED_PRIORITY = [\n 'Recipe',\n 'Product',\n 'Event',\n 'HowTo',\n 'Course',\n 'Movie',\n 'Book',\n 'MusicRecording',\n 'JobPosting',\n 'FAQPage',\n ...ARTICLE_TYPES,\n] as const;\n\nfunction nodeTypeList(node: JsonLdObject): readonly string[] {\n const type = node['@type'];\n if (Array.isArray(type)) {\n return type.filter((t): t is string => typeof t === 'string');\n }\n return typeof type === 'string' ? [type] : [];\n}\n\n// Iterate priority types in order so a Recipe wins over an earlier Article\n// node, and document order wins among candidates of the same type.\nfunction pickStructuredObject(\n candidates: readonly JsonLdObject[],\n): JsonLdObject | undefined {\n for (const priorityType of STRUCTURED_PRIORITY) {\n const hit = candidates.find(node =>\n nodeTypeList(node).includes(priorityType),\n );\n if (hit) {\n return hit;\n }\n }\n return undefined;\n}\n\n// Strip @context (always https://schema.org, pure noise) and normalize a\n// multi-typed node to a \"+\"-joined string so the host sees a single type label.\n// Data fields are kept verbatim — the host wants the real schema.org object.\nfunction cleanStructured(obj: JsonLdObject): StructuredData {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (key === '@context') {\n continue;\n }\n if (key === '@type') {\n const types = Array.isArray(value)\n ? value.filter((t): t is string => typeof t === 'string')\n : typeof value === 'string'\n ? [value]\n : [];\n out[key] = types.join('+');\n continue;\n }\n out[key] = value;\n }\n return out;\n}\n\n// Author may be a string, a Person/Organization object, an array, or a\n// {@list: [...]} container; reduce to a comma-joined byline.\nfunction resolveJsonLdAuthor(author: JsonLdValue): string | undefined {\n const names: string[] = [];\n function visit(value: JsonLdValue): void {\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed) {\n names.push(trimmed);\n }\n return;\n }\n if (!value || typeof value !== 'object') {\n return;\n }\n if (Array.isArray(value)) {\n value.forEach(visit);\n return;\n }\n // {@list: [...]} is an ordering wrapper; unwrap it.\n const list = value['@list'];\n if (Array.isArray(list)) {\n list.forEach(visit);\n return;\n }\n const name = value.name;\n if (typeof name === 'string') {\n const trimmed = name.trim();\n if (trimmed) {\n names.push(trimmed);\n }\n }\n }\n visit(author);\n return names.length > 0 ? names.join(', ') : undefined;\n}\n\nfunction asString(value: JsonLdValue | undefined): string | undefined {\n return typeof value === 'string' ? nonEmpty(value) : undefined;\n}\n\nfunction field(\n obj: JsonLdValue | undefined,\n key: string,\n): JsonLdValue | undefined {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n return undefined;\n }\n return obj[key];\n}\n\nexport interface MetadataInput {\n readonly baseUrl?: string;\n readonly document: Document;\n readonly readability?: null | ReadabilityParseResult;\n readonly readingTimeMin: number;\n readonly textContent: string;\n readonly wordCount: number;\n}\n\nexport function resolveMetadata(input: Readonly<MetadataInput>): Metadata {\n const { document, readability } = input;\n\n const jsonLdObjects = parseJsonLd(document);\n const jsonLd = pickArticleNode(jsonLdObjects);\n const structuredRaw = pickStructuredObject(jsonLdObjects);\n const htmlLang = nonEmpty(\n document.documentElement.getAttribute('lang') ?? undefined,\n );\n const titleFromTitleTag = nonEmpty(document.title);\n\n const title = first(\n asString(jsonLd?.headline),\n metaProperty(document, 'og:title'),\n metaName(document, 'twitter:title'),\n readability?.title ?? undefined,\n titleFromTitleTag,\n );\n\n const byline = first(\n jsonLd ? resolveJsonLdAuthor(jsonLd.author) : undefined,\n metaProperty(document, 'article:author'),\n metaName(document, 'author'),\n readability?.byline ?? undefined,\n );\n\n const siteName = first(\n asString(field(field(jsonLd, 'publisher'), 'name')),\n metaProperty(document, 'og:site_name'),\n readability?.siteName ?? undefined,\n );\n\n const lang = first(\n asString(jsonLd?.inLanguage),\n htmlLang,\n readability?.lang ?? undefined,\n );\n\n const publishedTime = first(\n asString(jsonLd?.datePublished),\n metaProperty(document, 'article:published_time'),\n // First <time datetime> is the canonical publish time when rendered inline.\n nonEmpty(\n document.querySelector('time[datetime]')?.getAttribute('datetime') ??\n undefined,\n ),\n readability?.publishedTime ?? undefined,\n );\n\n const excerpt = first(\n asString(jsonLd?.description),\n metaProperty(document, 'og:description'),\n metaName(document, 'twitter:description'),\n metaName(document, 'description'),\n readability?.excerpt ?? undefined,\n );\n\n // canonical/og:url are absolute by spec; returned raw so callers can tell\n // the declared canonical apart from the input baseUrl (origin context only).\n const canonical = first(\n nonEmpty(\n document.querySelector('link[rel=\"canonical\"]')?.getAttribute('href') ??\n undefined,\n ),\n metaProperty(document, 'og:url'),\n );\n\n return {\n title,\n byline,\n siteName,\n lang,\n publishedTime,\n excerpt,\n canonical,\n baseUrl: input.baseUrl,\n wordCount: input.wordCount,\n readingTimeMin: input.readingTimeMin,\n ...estimateTokens(input.textContent),\n ...(structuredRaw ? { structured: cleanStructured(structuredRaw) } : {}),\n };\n}\n","import { stringify as stringifyYaml } from 'yaml';\n\nimport type { Diagnostics, Metadata } from '../pipeline/context.js';\n\nimport { headingText, parseBlocks } from '../policy/markdown.js';\n\nexport type Format = 'html' | 'json' | 'markdown' | 'text';\nexport type MetadataMode = 'json' | 'none' | 'yaml';\n\nexport interface FormatInput {\n readonly diagnostics: Diagnostics;\n readonly format: Format;\n readonly markdown: string;\n readonly metadata: Metadata;\n readonly metadataMode: MetadataMode;\n readonly sanitizedHtml: string;\n readonly textContent: string;\n}\n\ninterface JsonObject {\n readonly content: string;\n readonly diagnostics: Diagnostics;\n readonly metadata: Metadata;\n}\n\n// Readability demotes the article <h1> to <h2> inside `content` and mirrors its\n// text into the title; drop the echoed sub-heading so the title prints once.\nfunction dropEchoedTitle(body: string, title: string): string {\n const blocks = parseBlocks(body);\n if (blocks.length === 0) {\n return body;\n }\n const first = blocks[0];\n if (\n first.kind === 'heading' &&\n headingText(body.slice(first.start, first.end)) === title.trim()\n ) {\n return body.slice(first.end).replace(/^\\n+/, '');\n }\n return body;\n}\n\nfunction renderMarkdown(input: Readonly<FormatInput>): string {\n const title = input.metadata.title?.trim();\n let body = input.markdown;\n if (title) {\n body = dropEchoedTitle(body, title);\n return `# ${title}\\n\\n${body}`.replace(/\\n+$/, '\\n');\n }\n return body.replace(/\\n+$/, '\\n');\n}\n\n// Stable key order for deterministic frontmatter. `structured` is deliberately\n// absent: it is a nested object, not a scalar, and would dump a whole JSON-LD\n// graph into frontmatter. It reaches the host via format=json and\n// structuredContent.metadata, which serialize the full metadata object.\nconst METADATA_KEYS = [\n 'title',\n 'byline',\n 'siteName',\n 'lang',\n 'publishedTime',\n 'excerpt',\n 'canonical',\n 'baseUrl',\n 'wordCount',\n 'readingTimeMin',\n 'tokenEstimate',\n 'estimator',\n] as const satisfies readonly (keyof Metadata)[];\n\nfunction pickMetadata(\n metadata: Readonly<Metadata>,\n): Record<string, number | string> {\n const picked: Record<string, number | string> = {};\n for (const key of METADATA_KEYS) {\n const value = metadata[key];\n if (value !== undefined) {\n picked[key] = value;\n }\n }\n return picked;\n}\n\n// lineWidth: 0 disables folding so each field stays on one line.\nfunction yamlFrontmatter(metadata: Readonly<Metadata>): string {\n return `---\\n${stringifyYaml(pickMetadata(metadata), { lineWidth: 0 })}---\\n`;\n}\n\nfunction jsonFrontmatter(metadata: Readonly<Metadata>): string {\n return (\n '```json\\n' + JSON.stringify(pickMetadata(metadata), null, 2) + '\\n```\\n'\n );\n}\n\nfunction withFrontmatter(\n payload: string,\n mode: MetadataMode,\n metadata: Readonly<Metadata>,\n): string {\n if (mode === 'yaml') {\n return `${yamlFrontmatter(metadata)}${payload}`;\n }\n if (mode === 'json') {\n return `${jsonFrontmatter(metadata)}\\n${payload}`;\n }\n return payload;\n}\n\nexport function formatPayload(input: Readonly<FormatInput>): string {\n let payload: string;\n switch (input.format) {\n case 'html':\n return input.sanitizedHtml;\n case 'json': {\n const body: JsonObject = {\n metadata: input.metadata,\n content: input.markdown,\n diagnostics: input.diagnostics,\n };\n return JSON.stringify(body, null, 2);\n }\n case 'text':\n payload = input.textContent;\n break;\n case 'markdown':\n default:\n payload = renderMarkdown(input);\n break;\n }\n return withFrontmatter(payload, input.metadataMode, input.metadata);\n}\n","import DOMPurify, {\n type DOMPurify as DOMPurifyInstance,\n type WindowLike as DOMPurifyWindow,\n} from 'dompurify';\n\nexport interface SanitizeResult {\n readonly html: string;\n readonly iframesRemoved: number;\n readonly scriptsRemoved: number;\n}\n\nfunction countRemoved(\n removed: DOMPurifyInstance['removed'],\n tagName: string,\n): number {\n let count = 0;\n for (const entry of removed) {\n // RemovedAttribute entries have no `element`; count element removals only.\n if (\n 'element' in entry &&\n entry.element.nodeName.toUpperCase() === tagName\n ) {\n count += 1;\n }\n }\n return count;\n}\n\n// DOMPurify scrubs Readability's output (scripts, inline event handlers,\n// iframes) before Turndown converts it to Markdown — it is a pipeline stage,\n// not a product. A sanitized-HTML output mode was rejected: the server emits\n// Markdown, and callers wanting sanitized HTML should run DOMPurify directly.\nexport function sanitizeHtml(dirty: string, window: Window): SanitizeResult {\n // DOMPurify's WindowLike is structurally compatible at runtime but not under TS.\n const purify = DOMPurify(window as unknown as DOMPurifyWindow);\n const html = purify.sanitize(dirty);\n return {\n html,\n iframesRemoved: countRemoved(purify.removed, 'IFRAME'),\n scriptsRemoved: countRemoved(purify.removed, 'SCRIPT'),\n };\n}\n","import { buildDocument } from '../pipeline/dom.js';\n\nexport interface FootnoteResult {\n readonly footnoteDefs: readonly string[];\n readonly html: string;\n}\n\n// jsdom-parsing every turndown call would dominate hot paths; this pre-check\n// gates on a string scan so footnote-less articles pay nothing.\nconst FOOTNOTE_SIGNAL_RE =\n /cite_note|cite_ref|class=\"footnotes\"|class=\"references\"|data-footnote|role=\"doc-endnote|<sup\\b[^>]*>\\s*<a\\s[^>]*href=\"#/i;\n\n// First match wins per group; an `<ol class=\"footnotes\">` is the common case.\nconst DEFINITION_CONTAINER_SELECTORS = [\n 'ol.footnotes',\n 'ol[class*=\"footnotes\"]',\n 'ol.references',\n 'ol[class*=\"references\"]',\n 'section[class*=\"footnote\"]',\n 'div[class*=\"footnote\"]',\n '[role=\"doc-endnotes\"]',\n '[role=\"doc-bibliography\"]',\n] as const;\n\nconst STANDALONE_DEF_ID_RE = /^(?:fn|cite_note|footnote|note)[:_-]/i;\n\ninterface RefHit {\n readonly defId: string;\n readonly n: number;\n readonly sup: Element;\n}\n\n// Some sites prepend a back-to-text caret or \"Jump to\" label to each definition.\n// Stripping only a leading occurrence keeps accidental mid-text carets intact.\nconst BACKREF_LEADING_RE = /^(?:↑\\s?|↩\\s?|\\^\\s|Jump to\\s*)/;\n\nfunction cleanDefText(text: string): string {\n return text.replace(/\\s+/g, ' ').trim().replace(BACKREF_LEADING_RE, '');\n}\n\nfunction collectDefinitions(document: Document): {\n readonly containers: ReadonlySet<Element>;\n readonly defs: ReadonlyMap<string, string>;\n readonly standaloneIds: ReadonlySet<string>;\n} {\n const containers = new Set<Element>();\n const defs = new Map<string, string>();\n const standaloneIds = new Set<string>();\n\n for (const selector of DEFINITION_CONTAINER_SELECTORS) {\n let matched: NodeListOf<Element>;\n try {\n matched = document.querySelectorAll(selector);\n } catch {\n continue;\n }\n for (const container of Array.from(matched)) {\n if (!containers.has(container)) {\n containers.add(container);\n }\n for (const item of Array.from(\n container.querySelectorAll('li, [role=\"doc-endnote\"]'),\n )) {\n const id = item.id;\n if (!id || defs.has(id)) {\n continue;\n }\n const text = cleanDefText(item.textContent);\n if (text) {\n defs.set(id, text);\n }\n }\n }\n }\n\n // Some sites scatter `<li id=\"fn1\">` outside any container; collect those too.\n for (const li of Array.from(document.querySelectorAll('li[id]'))) {\n const id = li.id;\n if (!id || defs.has(id) || !STANDALONE_DEF_ID_RE.test(id)) {\n continue;\n }\n const text = cleanDefText(li.textContent);\n if (text) {\n defs.set(id, text);\n standaloneIds.add(id);\n }\n }\n\n return { containers, defs, standaloneIds };\n}\n\nexport function processFootnotes(html: string): FootnoteResult | null {\n if (!html || !FOOTNOTE_SIGNAL_RE.test(html)) {\n return null;\n }\n\n let document: Document;\n try {\n document = buildDocument(html).document;\n } catch {\n return null;\n }\n\n const { containers, defs, standaloneIds } = collectDefinitions(document);\n if (defs.size === 0) {\n return null;\n }\n\n const refHits: RefHit[] = [];\n const defIdToNumber = new Map<string, number>();\n for (const sup of Array.from(document.querySelectorAll('sup'))) {\n if (!sup.isConnected) {\n continue;\n }\n try {\n const anchor = sup.querySelector('a[href^=\"#\"]');\n if (!anchor) {\n continue;\n }\n const frag = (anchor.getAttribute('href') ?? '').slice(1);\n if (!frag || !defs.has(frag)) {\n continue;\n }\n let n = defIdToNumber.get(frag);\n if (n === undefined) {\n n = defIdToNumber.size + 1;\n defIdToNumber.set(frag, n);\n }\n refHits.push({ defId: frag, n, sup });\n } catch {\n // Defensive: malformed <sup> must not abort the whole pass.\n }\n }\n\n if (refHits.length === 0) {\n return null;\n }\n\n for (const { n, sup } of refHits) {\n try {\n sup.replaceWith(document.createTextNode(`[^${n}]`));\n } catch {\n // skip\n }\n }\n\n // Drop the rendered references list so it doesn't duplicate the appended\n // `[^N]:` block. Removing containers also takes care of the standalone <li>\n // elements that lived inside them; only out-of-container standalones need\n // individual removal here.\n for (const container of containers) {\n if (container.isConnected) {\n container.remove();\n }\n }\n for (const id of standaloneIds) {\n const el = document.getElementById(id);\n if (el?.isConnected) {\n el.remove();\n }\n }\n\n const numberToDefId = new Map<number, string>();\n for (const [id, n] of defIdToNumber) {\n numberToDefId.set(n, id);\n }\n const footnoteDefs: string[] = [];\n for (let n = 1; n <= numberToDefId.size; n++) {\n const id = numberToDefId.get(n);\n if (id === undefined) {\n break;\n }\n const text = defs.get(id);\n if (text !== undefined) {\n footnoteDefs.push(text);\n }\n }\n\n // buildDocument wraps both full documents and bare fragments in a <body>;\n // serializing its innerHTML returns the equivalent of the input region.\n return {\n footnoteDefs,\n html: document.body.innerHTML,\n };\n}\n","import TurndownService from 'turndown';\nimport { gfm } from 'turndown-plugin-gfm';\n\nimport type { TableFormat } from '../policy/tables.js';\n\nimport { processFootnotes } from '../policy/footnotes.js';\nimport { parseTableMatrix, renderTable } from '../policy/tables.js';\nimport { absolutize } from './urls.js';\n\nexport type CodeBlockStyle = 'fenced' | 'indented';\nexport type HeadingStyle = 'atx' | 'setext';\nexport type ImageMode = 'drop' | 'keep' | 'reference' | 'src-only';\nexport type { TableFormat };\n\nexport interface TurndownOptions {\n readonly baseUrl?: string;\n readonly codeBlockStyle?: CodeBlockStyle;\n readonly gfm?: boolean;\n readonly headingStyle?: HeadingStyle;\n readonly images?: ImageMode;\n readonly tables?: TableFormat;\n}\n\nexport function toMarkdown(\n html: string,\n options?: Readonly<TurndownOptions>,\n): string {\n const service = new TurndownService({\n bulletListMarker: '-',\n // Code renders inline in the Markdown as fenced blocks; normalize rewrites\n // source conventions to `<code class=\"language-X\">` first, so the fence\n // carries a language tag. A code-only extraction tool was rejected as\n // redundant surface for a niche the main path already covers.\n codeBlockStyle: options?.codeBlockStyle ?? 'fenced',\n emDelimiter: '_',\n fence: '```',\n headingStyle: options?.headingStyle ?? 'atx',\n strongDelimiter: '**',\n });\n\n if (options?.gfm !== false) {\n service.use(gfm);\n }\n\n const tableFormat = options?.tables;\n if (tableFormat !== undefined) {\n // addRule prepends, so this shadows the gfm plugin's table rule AND its\n // headerless-table keep filter (which lives in a separate _keep list, only\n // consulted when no rule in the array matches). Matching every TABLE here\n // means the keep list is never reached for tables.\n service.addRule('tableMatrix', {\n filter: node => node.nodeName === 'TABLE',\n replacement: (_content, node) => {\n const matrix = parseTableMatrix(node);\n if (matrix.length === 0) {\n return '';\n }\n const body = renderTable(matrix, tableFormat);\n // GFM rows are already markdown table syntax; csv/json need a fenced block.\n if (tableFormat === 'gfm') {\n return `\\n\\n${body}\\n\\n`;\n }\n return `\\n\\n\\`\\`\\`${tableFormat}\\n${body}\\n\\`\\`\\`\\n\\n`;\n },\n });\n }\n\n const imageMode = options?.images;\n const baseUrl = options?.baseUrl;\n // `reference` mode collects definitions as images render; append them after the pass.\n const references: string[] = [];\n applyImagePolicy(service, imageMode, baseUrl, references);\n // Readability pre-absolutizes anchors on the main path, but html_to_markdown\n // and the extract fallback bypass it; turndown is shared, so absolutize here\n // for every path. Empty/absent href falls through to bare content to match\n // turndown's default link rule (which never matches when href is falsy).\n service.addRule('anchorAbsolutize', {\n filter: 'a',\n replacement: (content, node) => {\n const rawHref = node.getAttribute('href');\n if (!rawHref) {\n return content;\n }\n const href = absolutize(rawHref, baseUrl);\n const title = node.getAttribute('title');\n const titlePart = title ? ` \"${title.replace(/\"/g, '\\\\\"')}\"` : '';\n return `[${content}](${href}${titlePart})`;\n },\n });\n // Math markers are emitted by `policy/math.ts` and survive Readability via the\n // `rdrm-math` preserve entry. A rule's replacement is inserted verbatim, so\n // the raw LaTeX backslashes survive turndown untouched.\n service.addRule('mathMarker', {\n filter: node =>\n node.nodeName === 'SPAN' && node.classList.contains('rdrm-math'),\n replacement: (_content, node) => {\n const tex = node.textContent.trim();\n if (!tex) {\n return '';\n }\n const display = node.getAttribute('data-display') === 'true';\n return display ? `$$${tex}$$` : `$${tex}$`;\n },\n });\n\n const fnResult = processFootnotes(html);\n const sourceHtml = fnResult?.html ?? html;\n let body = service.turndown(sourceHtml);\n\n if (fnResult) {\n // Turndown sees `[^N]` as a shortcut-reference link and backslash-escapes\n // the brackets; reverse that for the markers we emitted so they render as\n // footnote refs. Code blocks pass through verbatim and never carry the\n // escape, so they are unaffected.\n for (let n = 1; n <= fnResult.footnoteDefs.length; n++) {\n body = body.replaceAll(`\\\\[^${n}\\\\]`, `[^${n}]`);\n }\n }\n\n const trailingBlocks: string[] = [];\n if (imageMode === 'reference' && references.length > 0) {\n trailingBlocks.push(\n references.map((ref, i) => `[img-${i + 1}]: ${ref}`).join('\\n'),\n );\n }\n if (fnResult && fnResult.footnoteDefs.length > 0) {\n trailingBlocks.push(\n fnResult.footnoteDefs.map((def, i) => `[^${i + 1}]: ${def}`).join('\\n'),\n );\n }\n if (trailingBlocks.length === 0) {\n return body;\n }\n return `${body.replace(/\\n+$/, '')}\\n\\n${trailingBlocks.join('\\n')}`;\n}\n\nfunction applyImagePolicy(\n service: TurndownService,\n mode: ImageMode | undefined,\n baseUrl: string | undefined,\n references: string[],\n): void {\n switch (mode) {\n case 'drop': {\n // `.remove('img')` doesn't reliably drop void elements in turndown 7.x.\n service.addRule('dropImage', {\n filter: 'img',\n replacement: () => '',\n });\n break;\n }\n case 'keep':\n case undefined: {\n service.addRule('imageKeep', {\n filter: 'img',\n replacement: (_content, node) => {\n const src = absolutize(node.getAttribute('src') ?? '', baseUrl);\n const alt = node.getAttribute('alt') ?? '';\n return src ? `![${alt}](${src})` : '';\n },\n });\n break;\n }\n case 'reference': {\n service.addRule('imageReference', {\n filter: 'img',\n replacement: (_content, node) => {\n const src = absolutize(node.getAttribute('src') ?? '', baseUrl);\n if (!src) {\n return '';\n }\n references.push(src);\n const id = references.length;\n const alt = node.getAttribute('alt') ?? '';\n return `![${alt}][img-${id}]`;\n },\n });\n break;\n }\n case 'src-only': {\n service.addRule('imageSrcOnly', {\n filter: 'img',\n replacement: (_content, node) => {\n const src = absolutize(node.getAttribute('src') ?? '', baseUrl);\n return src ? `\\n\\n${src}\\n\\n` : '';\n },\n });\n break;\n }\n }\n}\n","import type { SanitizationDiagnostics } from '../pipeline/context.js';\nimport type { ImageMode, TableFormat } from '../pipeline/turndown.js';\n\nimport { sanitizeHtml } from '../pipeline/sanitize.js';\nimport { toMarkdown } from '../pipeline/turndown.js';\n\nexport interface FallbackOptions {\n readonly baseUrl?: string;\n readonly codeBlockStyle?: 'fenced' | 'indented';\n readonly gfm?: boolean;\n readonly headingStyle?: 'atx' | 'setext';\n readonly images?: ImageMode;\n readonly sanitize: boolean;\n readonly tables?: TableFormat;\n readonly window: Window;\n}\n\nexport interface FallbackResult {\n readonly markdown: string;\n readonly rootSelector: string;\n readonly sanitization: SanitizationDiagnostics;\n readonly sanitizedHtml: string;\n readonly textContent: string;\n}\n\n// Below this a block is treated as not text-dense (nav/boilerplate-ish).\nconst MIN_DENSE_BLOCK_CHARS = 200;\n\nfunction convert(\n element: Element,\n options: Readonly<FallbackOptions>,\n):\n | undefined\n | {\n markdown: string;\n sanitization: SanitizationDiagnostics;\n sanitizedHtml: string;\n textContent: string;\n } {\n const rawHtml = element.outerHTML;\n let sanitizedHtml = rawHtml;\n let sanitization: SanitizationDiagnostics = { iframes: 0, scripts: 0 };\n if (options.sanitize) {\n const res = sanitizeHtml(rawHtml, options.window);\n sanitizedHtml = res.html;\n sanitization = { iframes: res.iframesRemoved, scripts: res.scriptsRemoved };\n }\n const markdown = toMarkdown(sanitizedHtml, {\n codeBlockStyle: options.codeBlockStyle,\n gfm: options.gfm,\n headingStyle: options.headingStyle,\n images: options.images,\n tables: options.tables,\n baseUrl: options.baseUrl,\n });\n const textContent = element.textContent;\n if (markdown.trim().length === 0) {\n return undefined;\n }\n return { markdown, sanitization, sanitizedHtml, textContent };\n}\n\nfunction largestTextDenseBlock(document: Document): Element | undefined {\n let best: Element | undefined;\n let bestLen = 0;\n const candidates = document.querySelectorAll('div, section');\n candidates.forEach(el => {\n const len = el.textContent.trim().length;\n if (len > bestLen && len >= MIN_DENSE_BLOCK_CHARS) {\n best = el;\n bestLen = len;\n }\n });\n return best;\n}\n\nexport function extractViaFallback(\n document: Document,\n options: Readonly<FallbackOptions>,\n): FallbackResult | null {\n const orderedSelectors: readonly string[] = [\n 'article',\n 'main',\n '[role=main]',\n ];\n for (const selector of orderedSelectors) {\n const root = document.querySelector(selector);\n if (root) {\n const converted = convert(root, options);\n if (converted) {\n return { ...converted, rootSelector: selector };\n }\n }\n }\n\n const dense = largestTextDenseBlock(document);\n if (dense) {\n const converted = convert(dense, options);\n if (converted) {\n return { ...converted, rootSelector: 'largest-block' };\n }\n }\n\n const converted = convert(document.body, options);\n if (converted) {\n return { ...converted, rootSelector: 'body' };\n }\n\n return null;\n}\n","import { isPlaceholderSrc } from '../pipeline/normalize.js';\nimport { absolutize } from '../pipeline/urls.js';\n\nexport interface ImageEntry {\n readonly alt: string;\n readonly caption: string;\n readonly height?: number;\n readonly src: string;\n readonly width?: number;\n}\n\nfunction positiveInt(value: null | string): number | undefined {\n if (value === null) {\n return undefined;\n }\n const n = Number.parseInt(value, 10);\n return Number.isInteger(n) && n > 0 ? n : undefined;\n}\n\n// resolveLazyImages has already mutated placeholder srcs to real ones in the\n// article HTML by the time this runs — reading `src` here gets the resolved\n// value, so this walker does NOT re-run source resolution. isPlaceholderSrc\n// skips any remaining data:/placeholder srcs the lazy resolver left alone.\nexport function collectImageInventory(\n html: string,\n window: Window,\n baseUrl?: string,\n): ImageEntry[] {\n if (!html) {\n return [];\n }\n const probe = window.document.createElement('div');\n probe.innerHTML = html;\n\n const entries: ImageEntry[] = [];\n for (const img of probe.querySelectorAll('img')) {\n const rawSrc = img.getAttribute('src') ?? '';\n if (isPlaceholderSrc(rawSrc)) {\n continue;\n }\n const src = absolutize(rawSrc, baseUrl);\n if (!src) {\n continue;\n }\n const alt = img.getAttribute('alt') ?? '';\n const width = positiveInt(img.getAttribute('width'));\n const height = positiveInt(img.getAttribute('height'));\n // <figcaption> may precede OR follow the <img> inside the <figure>, so\n // closest('figure') + querySelector('figcaption') covers both orderings.\n const figure = img.closest('figure');\n const figcaption = figure?.querySelector('figcaption');\n const caption = figcaption\n ? figcaption.textContent.replace(/\\s+/g, ' ').trim()\n : alt;\n entries.push({\n src,\n alt,\n ...(width !== undefined ? { width } : {}),\n ...(height !== undefined ? { height } : {}),\n caption,\n });\n }\n return entries;\n}\n","import { hardSplitLines, parseBlocks } from './markdown.js';\n\nexport interface TruncateResult {\n readonly text: string;\n readonly truncated: boolean;\n}\n\nconst TRUNCATION_MARKER = '\\n\\n…[truncated]';\n\n// Cuts at a top-level block boundary. A fenced code block is a single mdast\n// node, so a boundary cut never lands inside one — no open-fence rollback is\n// needed. The kept span is sliced from the original string so the payload is a\n// byte-for-byte prefix of the Turndown output.\nexport function truncateMarkdown(\n markdown: string,\n maxChars: number,\n): TruncateResult {\n if (markdown.length <= maxChars) {\n return { text: markdown, truncated: false };\n }\n\n const blocks = parseBlocks(markdown);\n let start = -1;\n let end = -1;\n let truncated = false;\n for (const block of blocks) {\n const from = start === -1 ? block.start : start;\n if (block.end - from > maxChars) {\n // First non-code block alone overflows: take a line-aware prefix instead\n // of dropping everything. A code block is never split — a half-open fence\n // is unrecoverable — so an oversized first code block stays dropped.\n if (start === -1 && block.kind !== 'code') {\n const firstPiece =\n hardSplitLines(markdown.slice(block.start, block.end), maxChars)[0] ??\n '';\n return {\n text: firstPiece.replace(/\\s+$/, '') + TRUNCATION_MARKER,\n truncated: true,\n };\n }\n truncated = true;\n break;\n }\n if (start === -1) {\n start = block.start;\n }\n end = block.end;\n }\n\n if (!truncated) {\n return { text: markdown, truncated: false };\n }\n\n const kept = start === -1 ? '' : markdown.slice(start, end);\n return {\n text: kept.replace(/\\s+$/, '') + TRUNCATION_MARKER,\n truncated: true,\n };\n}\n","import type { SanitizationDiagnostics } from '../pipeline/context.js';\nimport type { ToolHandle } from '../server.js';\n\nimport { ExtractionError, toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { formatPayload } from '../output/format.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport {\n applySelectors,\n canonicalizeCodeBlocks,\n normalizeDocument,\n resolveLazyImages,\n} from '../pipeline/normalize.js';\nimport { isReaderable, parseArticle } from '../pipeline/readability.js';\nimport { sanitizeHtml } from '../pipeline/sanitize.js';\nimport { toMarkdown } from '../pipeline/turndown.js';\nimport { chunkMarkdown } from '../policy/chunk.js';\nimport { assembleDiagnostics, TraceCollector } from '../policy/diagnostics.js';\nimport { extractViaFallback } from '../policy/fallback.js';\nimport { detectGating } from '../policy/gating.js';\nimport { collectImageInventory } from '../policy/images.js';\nimport { resolveMetadata } from '../policy/metadata.js';\nimport { detectPagination } from '../policy/pagination.js';\nimport { resolveReadabilityOptions } from '../policy/resolver.js';\nimport { computeTextMetrics } from '../policy/text.js';\nimport { truncateMarkdown } from '../policy/truncate.js';\nimport * as cache from '../resources.js';\nimport { readHtmlFile } from './html-source.js';\nimport { outputSchemaShape } from './output-schema.js';\nimport {\n type ExtractFromHtmlInput,\n type ExtractInput,\n extractInputSchema,\n extractInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\n// Names the extraction path, not a DOM tag — Readability doesn't expose the source node.\nconst EXTRACTED_NODE = 'readability';\n\nexport function extractArticle(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractInputSchema.parse(rawArgs);\n return extractArticleFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\n// Schema defaults materialized once. Callers that pass only a subset of the\n// knobs (extract_section's scoped delegation, the CLI, tests) get the rest\n// filled from here, so the worker always sees a complete input. `localPath` is\n// required only to anchor the source; a sentinel parse materializes every\n// `.default()` and the type narrows it back out.\nconst DEFAULTS: Omit<ExtractInput, 'localPath'> = extractInputSchema.parse({\n localPath: '',\n});\n\nexport function extractArticleFromHtml(\n input: Readonly<ExtractFromHtmlInput>,\n): CallToolResult {\n const merged = { ...DEFAULTS, ...input };\n const {\n html,\n baseUrl,\n cache: useCache,\n selectors,\n extraction,\n minArticleLength,\n maxNodes,\n keepClasses,\n readabilityOverrides,\n format,\n metadataMode,\n gfm,\n headingStyle,\n codeBlockStyle,\n images,\n sanitize: shouldSanitize,\n maxChars,\n wordsPerMinute,\n cleanChrome,\n tables,\n chunk,\n imageInventory,\n debug,\n } = merged;\n\n // Cache hit short-circuits the pipeline. A clone is returned so the cache\n // field can be overwritten to reflect *this* call's hashes (normalizedHash is\n // stable across re-renders, but originalHash differs by nonce/CSP/…).\n if (useCache) {\n const hit = cache.lookup(html, merged);\n if (hit) {\n const cloned = JSON.parse(\n JSON.stringify(hit.entry.structuredContent),\n ) as {\n [key: string]: unknown;\n diagnostics: Record<string, unknown>;\n };\n const structuredContent = {\n ...cloned,\n diagnostics: {\n ...cloned.diagnostics,\n cache: {\n hit: true,\n normalizedHash: hit.normalizedHash,\n originalHash: hit.originalHash,\n },\n },\n };\n return {\n content: [{ text: hit.entry.contentText, type: 'text' }],\n structuredContent,\n };\n }\n }\n\n // Stages are timed at the orchestrator boundary so they stay non-overlapping\n // and sum to the pipeline's wall-clock — stripConsent/absolutize live inside\n // normalize/turndown respectively and aren't carved out as their own stages.\n const trace = new TraceCollector(debug);\n\n const { document, window } = buildDocument(html, baseUrl);\n // Paywall overlays are stripped by normalizeDocument's stripChrome (a Piano\n // modal is role=\"dialog\" + full-viewport fixed), so gating must be detected\n // before normalization. Pagination chrome survives stripChrome and is detected after.\n const {\n gating,\n documentElementCount,\n normalizeCounts,\n imagesResolved,\n pagination,\n } = trace.run('normalize', () => {\n const gating = detectGating(document);\n const documentElementCount = document.querySelectorAll('*').length;\n const normalizeCounts = normalizeDocument(document, { cleanChrome });\n const imagesResolved = resolveLazyImages(document);\n // Detect before applySelectors: a caller's selectors.include could scope the\n // body and hide pagination chrome, but \"more content exists\" is still true.\n const pagination = detectPagination(document, baseUrl);\n applySelectors(document, selectors);\n const codeBlocksCanonicalized = canonicalizeCodeBlocks(document);\n if (codeBlocksCanonicalized > 0) {\n logger.debug(\n `canonicalized ${codeBlocksCanonicalized} code-block language tag(s)`,\n );\n }\n return {\n documentElementCount,\n gating,\n imagesResolved,\n normalizeCounts,\n pagination,\n };\n });\n\n const { readerable, article } = trace.run('readability', () => {\n const readerable = isReaderable(document);\n const readabilityOptions = resolveReadabilityOptions({\n extraction,\n keepClasses,\n maxNodes,\n minArticleLength,\n readabilityOverrides,\n });\n const article = parseArticle(document, readabilityOptions);\n return { article, readerable };\n });\n\n let markdown: string;\n let sanitizedHtml: string;\n let textContent: string;\n let extractedNode: string;\n let fallbackUsed: boolean;\n let sanitizeCounts: SanitizationDiagnostics;\n\n if (article?.content) {\n extractedNode = EXTRACTED_NODE;\n fallbackUsed = false;\n textContent = article.textContent ?? '';\n const articleHtml = article.content;\n const sanitized = trace.run(\n 'sanitize',\n (): {\n counts: SanitizationDiagnostics;\n html: string;\n } => {\n if (!shouldSanitize) {\n return { counts: { iframes: 0, scripts: 0 }, html: articleHtml };\n }\n const res = sanitizeHtml(articleHtml, window);\n return {\n counts: { iframes: res.iframesRemoved, scripts: res.scriptsRemoved },\n html: res.html,\n };\n },\n );\n sanitizedHtml = sanitized.html;\n sanitizeCounts = sanitized.counts;\n markdown = trace.run('turndown', () =>\n toMarkdown(sanitizedHtml, {\n codeBlockStyle,\n gfm,\n headingStyle,\n images,\n tables,\n baseUrl,\n }),\n );\n } else {\n // Cascade only on parse failure, not on isProbablyReaderable.\n // Fallback owns sanitize + turndown internally, so the two stages collapse\n // into one timed block here — keeping the trace non-overlapping.\n const fallback = trace.run('fallback', () =>\n extractViaFallback(document, {\n codeBlockStyle,\n gfm,\n headingStyle,\n images,\n sanitize: shouldSanitize,\n tables,\n baseUrl,\n window,\n }),\n );\n if (!fallback) {\n throw new ExtractionError(\n 'Readability returned no article and the selector cascade yielded no usable content.',\n );\n }\n extractedNode = fallback.rootSelector;\n fallbackUsed = true;\n markdown = fallback.markdown;\n sanitizedHtml = fallback.sanitizedHtml;\n textContent = fallback.textContent;\n sanitizeCounts = fallback.sanitization;\n }\n\n const { metadata } = trace.run('metadata', () => {\n const { wordCount, readingTimeMin } = computeTextMetrics(\n textContent,\n wordsPerMinute,\n );\n const metadata = resolveMetadata({\n document,\n readability: article,\n readingTimeMin,\n textContent,\n baseUrl,\n wordCount,\n });\n return { metadata };\n });\n\n const sanitization: SanitizationDiagnostics = {\n iframes: normalizeCounts.iframes + sanitizeCounts.iframes,\n scripts: normalizeCounts.scripts + sanitizeCounts.scripts,\n };\n const baseDiagnostics = assembleDiagnostics({\n articleHtml: sanitizedHtml,\n boilerplateRemoved: normalizeCounts.boilerplateRemoved,\n chromeRemoved: normalizeCounts.chromeRemoved,\n documentElementCount,\n extractedNode,\n fallbackUsed,\n gated: gating,\n imagesResolved,\n pagination,\n readerable,\n sanitization,\n trace: trace.collect(),\n truncated: false,\n window,\n });\n\n let payload = formatPayload({\n diagnostics: baseDiagnostics,\n format,\n markdown,\n metadata,\n metadataMode,\n sanitizedHtml,\n textContent,\n });\n\n let truncated = false;\n if (maxChars !== undefined && (format === 'markdown' || format === 'text')) {\n const res = truncateMarkdown(payload, maxChars);\n payload = res.text;\n truncated = res.truncated;\n }\n const diagnostics = truncated\n ? { ...baseDiagnostics, truncated }\n : baseDiagnostics;\n\n // Chunk the final payload (post-format, post-truncation) so token counts\n // reflect what the host sees in content[0].text. HTML/JSON formats carry no\n // markdown body to slice, so `chunks` stays unset for them.\n const chunks =\n chunk && (format === 'markdown' || format === 'text')\n ? chunkMarkdown(payload, chunk)\n : undefined;\n\n // Runs against sanitizedHtml so the inventory reflects exactly what the host\n // sees in content[0].text — post-sanitization, post-lazy-resolution.\n const imageInventoryEntries = imageInventory\n ? collectImageInventory(sanitizedHtml, window, baseUrl)\n : undefined;\n\n const baseStructuredContent = {\n schemaVersion: 1 as const,\n content: payload,\n metadata,\n diagnostics,\n ...(chunks ? { chunks } : {}),\n ...(imageInventoryEntries ? { images: imageInventoryEntries } : {}),\n };\n\n // On cache miss (when cache:true), persist a cache-agnostic copy so the\n // stored entry never carries a stale hit/miss verdict, then surface the\n // signal on the returned object only.\n let structuredContent = baseStructuredContent;\n if (useCache) {\n const stored = cache.storeResult(html, merged, {\n contentText: payload,\n structuredContent: baseStructuredContent,\n });\n structuredContent = {\n ...baseStructuredContent,\n diagnostics: {\n ...diagnostics,\n cache: {\n hit: false,\n normalizedHash: stored.normalizedHash,\n originalHash: stored.originalHash,\n },\n },\n };\n }\n\n return {\n content: [{ text: payload, type: 'text' }],\n structuredContent,\n };\n}\n\nexport const EXTRACT_TOOL_DESCRIPTION = `Extract the main article from already-rendered (post-JavaScript) HTML and return clean Markdown plus metadata and diagnostics. The server fetches nothing: \\`localPath\\` (a file holding the rendered HTML, e.g. \\`document.documentElement.outerHTML\\` written to disk by a browser/devtools capture) is the only source, and \\`baseUrl\\` (optional) is used solely to absolutize relative links.`;\n\nexport function extractHandler(args: unknown): CallToolResult {\n try {\n return extractArticle(args);\n } catch (err) {\n logger.error(\n `extract failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract',\n {\n title: 'Extract article to Markdown',\n description: EXTRACT_TOOL_DESCRIPTION,\n inputSchema: extractInputShape,\n outputSchema: outputSchemaShape,\n },\n extractHandler,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extract-CZgSOqXG.js","names":[],"sources":["../../package.json","../../src/config.ts","../../src/resources.ts","../../src/errors.ts","../../src/logger.ts","../../src/policy/markdown.ts","../../src/policy/chunk.ts","../../src/tools/schemas.ts","../../src/tools/output-schema.ts","../../src/pipeline/dom.ts","../../src/policy/math.ts","../../src/policy/resolver.ts","../../src/pipeline/normalize.ts","../../src/pipeline/readability.ts","../../src/policy/diagnostics.ts","../../src/policy/gating.ts","../../src/pipeline/urls.ts","../../src/policy/pagination.ts","../../src/tools/html-source.ts","../../src/policy/cell-text.ts","../../src/policy/tables.ts","../../src/policy/text.ts","../../src/policy/metadata.ts","../../src/output/format.ts","../../src/pipeline/sanitize.ts","../../src/policy/footnotes.ts","../../src/pipeline/turndown.ts","../../src/policy/fallback.ts","../../src/policy/images.ts","../../src/policy/truncate.ts","../../src/tools/extract.ts"],"sourcesContent":["{\n \"name\": \"@v1nvn/readability-mcp\",\n \"version\": \"0.14.1\",\n \"description\": \"MCP server that turns rendered (post-JS) HTML into clean Markdown + metadata via Readability, Turndown, and DOMPurify.\",\n \"type\": \"module\",\n \"main\": \"dist/index.js\",\n \"bin\": \"dist/index.js\",\n \"scripts\": {\n \"build\": \"vite build\",\n \"bench\": \"vite-node test/bench/run.ts\",\n \"dev\": \"vite-node src/dev.ts\",\n \"start\": \"node dist/index.js\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:update-goldens\": \"UPDATE_GOLDENS=1 vitest run\",\n \"coverage\": \"vitest run --coverage\"\n },\n \"keywords\": [\n \"mcp\",\n \"readability\",\n \"markdown\",\n \"turndown\",\n \"model-context-protocol\"\n ],\n \"author\": \"v1nvn\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/v1nvn/agentic.git\",\n \"directory\": \"packages/readability-mcp\"\n },\n \"files\": [\n \"dist\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"@mozilla/readability\": \"^0.6.0\",\n \"dompurify\": \"^3.4.12\",\n \"jsdom\": \"^29.1.1\",\n \"remark-gfm\": \"^4.0.1\",\n \"remark-parse\": \"^11.0.0\",\n \"turndown\": \"^7.2.4\",\n \"turndown-plugin-gfm\": \"^1.0.2\",\n \"unified\": \"^11.0.5\",\n \"yaml\": \"^2.9.0\",\n \"zod\": \"^4.4.3\"\n },\n \"devDependencies\": {\n \"@types/mdast\": \"^4.0.4\",\n \"@types/turndown\": \"^5.0.6\",\n \"@vitest/coverage-v8\": \"^4.1.10\",\n \"vite\": \"^8.1.4\",\n \"vite-node\": \"^6.0.0\",\n \"vitest\": \"^4.1.10\"\n }\n}\n","import pkg from '../package.json' with { type: 'json' };\n\nexport interface ServerConfig {\n readonly description: string;\n readonly instructions: string;\n readonly logLevel: LogLevel;\n readonly name: 'readability-mcp';\n readonly title: string;\n readonly version: string;\n}\n\nexport type LogLevel = 'debug' | 'error' | 'info' | 'silent' | 'warn';\n\nconst VALID_LEVELS: readonly LogLevel[] = [\n 'debug',\n 'info',\n 'warn',\n 'error',\n 'silent',\n];\n\nconst LEVEL_RANK: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n silent: Number.MAX_SAFE_INTEGER,\n};\n\nconst DEFAULT_LOG_LEVEL: LogLevel = 'info';\n\nfunction resolveLogLevel(env: NodeJS.ProcessEnv): LogLevel {\n const raw = env.READABILITY_MCP_LOG_LEVEL;\n if (raw && (VALID_LEVELS as readonly string[]).includes(raw)) {\n return raw as LogLevel;\n }\n return DEFAULT_LOG_LEVEL;\n}\n\nconst SERVER_TITLE = 'Readability MCP';\n\nconst SERVER_DESCRIPTION =\n 'Turn already-rendered (post-JavaScript) HTML into clean, LLM-friendly Markdown plus metadata, via Mozilla Readability, Turndown, and DOMPurify. Makes no outbound requests — input is the rendered HTML, read from a file path (localPath) so the page bytes never enter the model context.';\n\nconst SERVER_INSTRUCTIONS = `Eleven always-on tools, all fed a file path (localPath) holding already-rendered HTML (e.g. document.documentElement.outerHTML written to disk by a browser/devtools capture) — except \\`chunk_text\\`, which operates on already-extracted text. A sampling-gated \\`summarize\\` adds a twelfth when (and only when) the host advertises the MCP \\`sampling\\` capability. The server never fetches URLs.\n\n- extract: main tool. Runs Readability to pull the article and returns Markdown + metadata + diagnostics. Use by default for article-like pages. Pass the \\`chunk\\` option to also emit token-bounded chunks for RAG/embedding.\n- extract_links: return a structured list of anchor links ({text, href, rel, isExternal}) from the raw DOM — hrefs absolutized against baseUrl; pairs with chrome-devtools for crawl/navigation decisions.\n- extract_list: second engine for feed/index/search/HN-style pages Readability cannot turn into one article. Returns {items:[{title,url,snippet,score}], diagnostics} — the same-shape sibling-anchor cluster with the most items wins. Reports \\`detected:false\\` on article pages.\n- extract_grid: detect and extract a CSS-grid / div \"table\" — the div equivalent of \\`extract_tables\\` for SPAs that render data into repeating \\`<div>\\` rows. Auto-detects the largest same-shape sibling group of ≥3 rows (each ≥2 cells) or takes explicit \\`rowSelector\\` + \\`cellSelector\\`; renders through the same gfm/csv/json matrix renderer.\n- extract_metadata: return only the bibliographic metadata (title, byline, siteName, lang, publishedTime, excerpt, canonical, baseUrl) without running Readability — fast pre-check for crawlers/citation.\n- extract_section: return one section by CSS selector OR heading text. Selector mode is a straight pass-through to extract’s selectors.include; heading mode spans the matched heading to the next same-or-higher level (case-insensitive, first match wins).\n- extract_tables: extract every <table> on the page (page-wide walk → gfm/csv/json), reusing the same rowspan/colspan-aware matrix serializer as the \\`tables\\` option on \\`extract\\`. Captures tables outside the article body — nav, aside, boilerplate — that the \\`tables\\` option never sees.\n- explain: post-mortem for an extraction — surfaces Readability’s REAL per-candidate contentScore values, the chosen root, a removed-nodes breakdown, gating/pagination signals, and the pre-Readability HTML snapshot. Same normalize + Readability path as \\`extract\\`, no fallback cascade/Turndown.\n- html_to_markdown: convert an arbitrary HTML fragment to Markdown with NO Readability scoring (e.g. a snippet already isolated via devtools).\n- outline: cheap heading pre-check (h1-h6 with stable anchor ids) before paying for full extraction.\n- chunk_text: split already-extracted text into token-bounded chunks (each with index, tokenCount, and nearest preceding heading) for embedding/RAG.\n\nSampling-gated (listed only when the client advertises \\`sampling\\` on initialize):\n- summarize: delegate to the HOST’s model via \\`sampling/createMessage\\` — input {text, maxTokens?}, typically the output of \\`extract\\`/\\`html_to_markdown\\`. The server embeds no model and calls no provider directly; the host picks the model and may prompt the user first.\n\nRounding out the surface:\n- resources: \\`extract({cache:true})\\` caches results as addressable \\`readability://page/{hash}\\` Resources; \\`diagnostics.cache = {hit, normalizedHash, originalHash}\\`. Re-renders that differ only in nonce/CSP/generated-id collapse to the same key (normalized-hash keying).\n\nThe optional baseUrl is origin context only (absolutizes relative links); it is never fetched. Every tool returns MCP structured content (metadata, diagnostics) validated by an output schema, plus a readable payload in content[0].text. Failures surface as { isError: true } results, never thrown across the wire.`;\n\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n return {\n name: 'readability-mcp',\n version: pkg.version,\n title: SERVER_TITLE,\n description: SERVER_DESCRIPTION,\n instructions: SERVER_INSTRUCTIONS,\n logLevel: resolveLogLevel(env),\n };\n}\n\nexport function levelEnabled(\n config: ServerConfig,\n level: Exclude<LogLevel, 'silent'>,\n): boolean {\n return LEVEL_RANK[level] >= LEVEL_RANK[config.logLevel];\n}\n","// In-memory cache of extract results keyed by a volatility-normalized hash of\n// the HTML plus an args fingerprint, exposed to clients as addressable MCP\n// Resources (`readability://page/{hash}`). Re-renders that differ only in\n// nonce/CSP/generated ids collapse to the same key; the original hash rides\n// alongside each entry so a should-have-hit-but-didn't miss points at a\n// normalizer bug rather than a genuinely different page.\n\nimport { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { createHash } from 'node:crypto';\n\nimport type { ToolHandle } from './server.js';\nimport type { ExtractFromHtmlInput } from './tools/schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type {\n ListResourcesResult,\n ReadResourceResult,\n} from '@modelcontextprotocol/sdk/types.js';\n\nconst MAX_ENTRIES = 256;\nconst TTL_MS = 30 * 60 * 1000;\nconst RESOURCE_SCHEME = 'readability://page/';\n\ninterface CacheEntry {\n readonly argsFingerprint: string;\n readonly cacheKey: string;\n readonly contentText: string;\n readonly expiresAt: number;\n readonly normalizedHash: string;\n readonly originalHash: string;\n // Held opaquely: cloned on hit and returned as CallToolResult.structuredContent\n // without re-validation, so a wider element type than the strict output schema\n // (e.g. readonly trace arrays) is fine here.\n readonly structuredContent: unknown;\n}\n\ninterface CacheLookup {\n readonly entry: CacheEntry;\n readonly normalizedHash: string;\n readonly originalHash: string;\n}\n\nconst entries = new Map<string, CacheEntry>();\n\nexport function resetCache(): void {\n entries.clear();\n}\n\nfunction evictExpired(now: number): void {\n for (const [key, entry] of entries) {\n if (entry.expiresAt <= now) {\n entries.delete(key);\n }\n }\n}\n\n// Bounded LRU: Map preserves insertion order, so re-insert on access to surface\n// recently-used entries to the end and drop the oldest when over capacity.\nfunction touch(key: string, entry: CacheEntry): void {\n entries.delete(key);\n entries.set(key, entry);\n}\n\nfunction pruneToMax(): void {\n while (entries.size > MAX_ENTRIES) {\n const oldest = entries.keys().next();\n if (oldest.done) {\n break;\n }\n entries.delete(oldest.value);\n }\n}\n\n// --- Hashing ---------------------------------------------------------------\n\n// Conservative volatility normalization — over-stripping collides distinct\n// pages. Each rule targets a specific known-volatile source.\nfunction normalizeForHash(html: string): string {\n let s = html;\n // Non-JSON-LD scripts carry CSP nonces, build hashes, A/B test buckets —\n // same page, different bytes on every render — so strip them. Preserve\n // <script type=\"application/ld+json\">: structured metadata is content, so\n // a changed datePublished must bust the cache. Mirrors the extraction\n // normalizer in pipeline/normalize.ts.\n s = s.replace(\n /<script\\b(?![^>]*type\\s*=\\s*[\"']application\\/ld\\+json[\"'])[^>]*>[\\s\\S]*?<\\/script>/gi,\n '',\n );\n s = s.replace(\n /<script\\b(?![^>]*type\\s*=\\s*[\"']application\\/ld\\+json[\"'])[^>]*\\/?>/gi,\n '',\n );\n // CSP <meta http-equiv=\"Content-Security-Policy\"> carries the per-response\n // nonce directive — strip the whole tag.\n s = s.replace(\n /<meta\\b[^>]*http-equiv=[\"']?content-security-policy[\"']?[^>]*>/gi,\n '',\n );\n // Per-render nonce attributes on any tag (script, link, style, …).\n s = s.replace(/\\snonce\\s*=\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)/gi, '');\n // Build-tool generated attribute names: Vue `data-v-1a2b3c4d`,\n // CSS-modules `data-css-1a2b3c4d`, Svelte `data-svelte-1a2b3c4d`. The\n // value-part is optional — Vue's compiler emits the bare `data-v-<hash>`\n // form with no `=\"...\"`.\n s = s.replace(\n /\\sdata-(?:v|css|svelte|h)-[a-z0-9]{6,}(?:\\s*=\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+))?/gi,\n '',\n );\n // React useId / RSC / Next.js generated ids: id=\":R1:\", ':r1:',\n // __next_internal_action_…, react internal identifiers.\n s = s.replace(\n /\\sid\\s*=\\s*(\":[A-Za-z0-9_-]+:\"|':[A-Za-z0-9_-]+:'|__next_[A-Za-z0-9_-]+)/g,\n '',\n );\n s = s.replace(\n /\\sid\\s*=\\s*(\"react[A-Z]_[A-Za-z0-9_]+\"|'react[A-Z]_[A-Za-z0-9_]+')/gi,\n '',\n );\n // Whitespace runs and indentation differ by minifier/pretty-printer.\n s = s.replace(/\\s+/g, ' ');\n return s.trim();\n}\n\nfunction sha256(text: string): string {\n return createHash('sha256').update(text).digest('hex');\n}\n\nexport function normalizedHashOf(html: string): string {\n return sha256(normalizeForHash(html));\n}\n\nexport function originalHashOf(html: string): string {\n return sha256(html.trim());\n}\n\n// Output-affecting options participate in the fingerprint so the same page with\n// format:'html' vs format:'markdown' does not collide. `baseUrl` absolutizes\n// links in the output, so it must participate too.\nfunction buildArgsFingerprint(args: ExtractFromHtmlInput): string {\n const sel = args.selectors\n ? {\n exclude: args.selectors.exclude ?? [],\n include: args.selectors.include ?? '',\n }\n : null;\n const chunk = args.chunk\n ? {\n maxTokens: args.chunk.maxTokens,\n overlap: args.chunk.overlap,\n strategy: args.chunk.strategy,\n }\n : null;\n const fp = {\n chunk,\n cleanChrome: args.cleanChrome,\n codeBlockStyle: args.codeBlockStyle,\n debug: args.debug,\n extraction: args.extraction,\n format: args.format,\n gfm: args.gfm,\n headingStyle: args.headingStyle,\n imageInventory: args.imageInventory,\n images: args.images,\n keepClasses: args.keepClasses,\n maxChars: args.maxChars ?? null,\n maxNodes: args.maxNodes ?? null,\n metadataMode: args.metadataMode,\n minArticleLength: args.minArticleLength ?? null,\n readabilityOverrides: args.readabilityOverrides ?? null,\n sanitize: args.sanitize,\n selectors: sel,\n tables: args.tables ?? null,\n baseUrl: args.baseUrl ?? null,\n wordsPerMinute: args.wordsPerMinute,\n };\n return JSON.stringify(fp);\n}\n\nfunction computeHashes(html: string): {\n normalizedHash: string;\n originalHash: string;\n} {\n return {\n normalizedHash: normalizedHashOf(html),\n originalHash: originalHashOf(html),\n };\n}\n\nfunction combineKey(normalizedHash: string, argsFingerprint: string): string {\n return sha256(`${normalizedHash}:${argsFingerprint}`);\n}\n\n// --- Store API -------------------------------------------------------------\n\nexport function lookup(\n html: string,\n args: ExtractFromHtmlInput,\n): CacheLookup | undefined {\n const now = Date.now();\n evictExpired(now);\n const { normalizedHash, originalHash } = computeHashes(html);\n const cacheKey = combineKey(normalizedHash, buildArgsFingerprint(args));\n const entry = entries.get(cacheKey);\n if (!entry || entry.expiresAt <= now) {\n if (entry) {\n entries.delete(cacheKey);\n }\n return undefined;\n }\n touch(cacheKey, entry);\n return { entry, normalizedHash, originalHash };\n}\n\nexport function storeResult(\n html: string,\n args: ExtractFromHtmlInput,\n result: {\n contentText: string;\n structuredContent: unknown;\n },\n): { normalizedHash: string; originalHash: string } {\n const now = Date.now();\n evictExpired(now);\n const { normalizedHash, originalHash } = computeHashes(html);\n const argsFingerprint = buildArgsFingerprint(args);\n const cacheKey = combineKey(normalizedHash, argsFingerprint);\n const entry: CacheEntry = {\n argsFingerprint,\n cacheKey,\n contentText: result.contentText,\n expiresAt: now + TTL_MS,\n normalizedHash,\n originalHash,\n structuredContent: result.structuredContent,\n };\n entries.set(cacheKey, entry);\n pruneToMax();\n return { normalizedHash, originalHash };\n}\n\nexport function getEntryByHash(hash: string): CacheEntry | undefined {\n for (const entry of entries.values()) {\n if (entry.normalizedHash === hash || entry.cacheKey === hash) {\n return entry;\n }\n }\n return undefined;\n}\n\nexport function listEntries(): readonly CacheEntry[] {\n const now = Date.now();\n evictExpired(now);\n return [...entries.values()];\n}\n\n// --- Resource registration -------------------------------------------------\n\n// \"page-cache\" names the registered template; the readability:// URI scheme\n// identifies the cache namespace and {hash} is the combined cache key.\nconst PAGE_CACHE_TEMPLATE = new ResourceTemplate('readability://page/{hash}', {\n // Dynamic set: the cache comes and goes, so we enumerate current entries.\n list: (): ListResourcesResult => ({\n resources: listEntries().map(entry => ({\n description: `Cached extract for normalized hash ${entry.normalizedHash.slice(0, 12)}…`,\n mimeType: 'text/markdown',\n name: entry.normalizedHash,\n uri: `${RESOURCE_SCHEME}${entry.cacheKey}`,\n })),\n }),\n});\n\nexport function registerCacheResources(server: McpServer): ToolHandle {\n return server.registerResource(\n 'page-cache',\n PAGE_CACHE_TEMPLATE,\n {\n title: 'Cached page extractions',\n description:\n 'Addressable cache of `extract` results called with cache:true. Each entry is keyed by the volatility-normalized hash of the HTML plus the output options; the URI path segment is the combined cache key.',\n mimeType: 'text/markdown',\n },\n (uri, variables): ReadResourceResult => {\n const hash = String(variables.hash);\n const entry = getEntryByHash(hash);\n if (!entry) {\n return {\n contents: [\n {\n uri: uri.href,\n text: '',\n },\n ],\n };\n }\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'text/markdown',\n text: entry.contentText,\n },\n ],\n };\n },\n );\n}\n\nexport function registerResources(server: McpServer): ToolHandle[] {\n return [registerCacheResources(server)];\n}\n","import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nexport interface TextContent {\n readonly text: string;\n readonly type: 'text';\n}\n\nexport interface ErrorResult {\n // Mutable so the literal assigns to the SDK's CallToolResult (mutable content).\n readonly content: TextContent[];\n readonly isError: true;\n}\n\nexport class ExtractionError extends Error {\n public override readonly cause: unknown;\n\n public constructor(message: string, options: { cause?: unknown } = {}) {\n super(message);\n this.name = 'ExtractionError';\n this.cause = options.cause;\n }\n}\n\nfunction describeError(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n return String(err);\n}\n\nexport function toErrorResult(err: unknown): CallToolResult {\n const label = err instanceof ExtractionError ? err.name : 'Error';\n return {\n isError: true,\n content: [{ type: 'text', text: `${label}: ${describeError(err)}` }],\n };\n}\n","import { levelEnabled, loadConfig, type LogLevel } from './config.js';\n\ntype Writer = (message: string) => void;\n\nconst LEVEL_LABEL: Record<Exclude<LogLevel, 'silent'>, string> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n};\n\nfunction format(level: Exclude<LogLevel, 'silent'>, message: string): string {\n return `${LEVEL_LABEL[level]} ${message}`;\n}\n\nfunction emit(\n writer: Writer,\n level: Exclude<LogLevel, 'silent'>,\n message: string,\n): void {\n writer(format(level, message));\n}\n\nclass Logger {\n private readonly stderr: Writer;\n\n constructor(stderr: Writer = line => process.stderr.write(`${line}\\n`)) {\n this.stderr = stderr;\n }\n\n debug(message: string): void {\n if (levelEnabled(activeConfig, 'debug')) {\n emit(this.stderr, 'debug', message);\n }\n }\n\n error(message: string): void {\n if (levelEnabled(activeConfig, 'error')) {\n emit(this.stderr, 'error', message);\n }\n }\n\n info(message: string): void {\n if (levelEnabled(activeConfig, 'info')) {\n emit(this.stderr, 'info', message);\n }\n }\n\n warn(message: string): void {\n if (levelEnabled(activeConfig, 'warn')) {\n emit(this.stderr, 'warn', message);\n }\n }\n}\n\nconst activeConfig = loadConfig();\n\nexport const logger = new Logger();\n","import remarkGfm from 'remark-gfm';\nimport remarkParse from 'remark-parse';\nimport { unified } from 'unified';\n\nexport type MarkdownBlockKind = 'code' | 'heading' | 'other';\n\n// A top-level CommonMark block located by source offset. remark is used as a\n// boundary finder, never a serializer: callers slice the *original* string at\n// these offsets so truncate/chunk return byte-for-byte slices of the Turndown\n// output rather than a reflowed re-stringification.\nexport interface MarkdownBlock {\n readonly depth: number;\n readonly end: number;\n readonly kind: MarkdownBlockKind;\n readonly start: number;\n}\n\nconst processor = unified().use(remarkParse).use(remarkGfm);\n\nconst HEADING_MARKERS = /^#{1,6}\\s+/;\n\nexport function parseBlocks(source: string): MarkdownBlock[] {\n const tree = processor.parse(source);\n const blocks: MarkdownBlock[] = [];\n for (const node of tree.children) {\n const start = node.position?.start.offset;\n const end = node.position?.end.offset;\n if (start === undefined || end === undefined) {\n continue;\n }\n const kind: MarkdownBlockKind =\n node.type === 'code'\n ? 'code'\n : node.type === 'heading'\n ? 'heading'\n : 'other';\n const depth = node.type === 'heading' ? node.depth : 0;\n blocks.push({ depth, end, kind, start });\n }\n return blocks;\n}\n\nexport function headingText(raw: string): string {\n return raw.replace(HEADING_MARKERS, '').trim();\n}\n\n// Hard-cap a run to <= maxChars pieces: pack whole lines, then hard-split any\n// single line that alone exceeds the budget.\nexport function hardSplitLines(text: string, maxChars: number): string[] {\n const pieces: string[] = [];\n let buffer = '';\n function flush(): void {\n if (buffer) {\n pieces.push(buffer);\n buffer = '';\n }\n }\n for (const line of text.split('\\n')) {\n if (line.length > maxChars) {\n flush();\n for (let i = 0; i < line.length; i += maxChars) {\n pieces.push(line.slice(i, i + maxChars));\n }\n continue;\n }\n const candidate = buffer ? `${buffer}\\n${line}` : line;\n if (candidate.length > maxChars) {\n flush();\n buffer = line;\n } else {\n buffer = candidate;\n }\n }\n flush();\n return pieces;\n}\n","// Token-bounded chunking of extracted markdown for RAG/embedding. The\n// `chars/4` estimator mirrors policy/metadata.ts so a chunk's `tokenCount` is\n// directly comparable to `metadata.tokenEstimate`. This is the server-side\n// chunking path for large documents; transport-level streaming was rejected —\n// MCP tool-result progress isn't universally host-supported, and this option\n// already keeps a multi-MB page from returning as one payload.\n\nimport { hardSplitLines, headingText, parseBlocks } from './markdown.js';\n\nexport interface Chunk {\n readonly headingContext: string;\n readonly index: number;\n readonly text: string;\n readonly tokenCount: number;\n}\n\nexport type ChunkStrategy = 'char' | 'semantic';\n\nexport interface ChunkOptions {\n readonly maxTokens: number;\n readonly overlap: number;\n readonly strategy: ChunkStrategy;\n}\n\ninterface Block {\n readonly headingContext: string;\n readonly text: string;\n}\n\ninterface Unit {\n readonly headingContext: string;\n readonly text: string;\n}\n\n// Character offsets of a contributing unit within the in-progress chunk text;\n// used to recover the heading context at the start of an overlap tail.\ninterface Span {\n readonly end: number;\n readonly headingContext: string;\n readonly start: number;\n}\n\nconst HEADING_FIRST_LINE = /^#{1,6}\\s/;\n\nfunction splitBlocks(markdown: string): Block[] {\n const parts = markdown.split(/\\n{2,}/);\n const blocks: Block[] = [];\n let heading = '';\n for (const part of parts) {\n const text = part.trim();\n if (!text) {\n continue;\n }\n if (HEADING_FIRST_LINE.test(text)) {\n heading = headingText(text);\n }\n blocks.push({ headingContext: heading, text });\n }\n return blocks;\n}\n\n// Char strategy may break a code block — the semantic strategy avoids that.\nfunction splitOversizedBlock(block: Block, maxChars: number): Unit[] {\n return hardSplitLines(block.text, maxChars).map(text => ({\n headingContext: block.headingContext,\n text,\n }));\n}\n\nfunction toUnits(blocks: readonly Block[], maxChars: number): Unit[] {\n const units: Unit[] = [];\n for (const block of blocks) {\n if (block.text.length <= maxChars) {\n units.push(block);\n } else {\n units.push(...splitOversizedBlock(block, maxChars));\n }\n }\n return units;\n}\n\nfunction chunkMarkdownChar(\n markdown: string,\n options: Readonly<ChunkOptions>,\n): Chunk[] {\n if (!markdown) {\n return [];\n }\n const maxTokens = Math.max(1, Math.floor(options.maxTokens));\n const maxChars = maxTokens * 4;\n const overlapTokens = Math.max(0, Math.floor(options.overlap));\n // Overlap must be strictly less than maxChars — otherwise a chunk's tail\n // could consume the entire budget and we'd loop forever re-emitting it.\n const overlapChars = Math.min(overlapTokens * 4, maxChars - 1);\n\n const blocks = splitBlocks(markdown);\n if (blocks.length === 0) {\n return [];\n }\n const units = toUnits(blocks, maxChars);\n if (units.length === 0) {\n return [];\n }\n\n const chunks: Chunk[] = [];\n let i = 0;\n let overlapText = '';\n let overlapHeading = '';\n\n while (i < units.length) {\n const spans: Span[] = [];\n let chunkText = '';\n\n if (overlapText) {\n chunkText = overlapText;\n spans.push({\n end: overlapText.length,\n headingContext: overlapHeading,\n start: 0,\n });\n }\n\n // Force at least one new unit per chunk so the loop terminates. If carrying\n // the overlap would push the first new unit past maxChars, drop the overlap\n // for this chunk (the unit was pre-split to <= maxChars so it always fits alone).\n const firstUnit = units[i];\n const sepLen = chunkText ? 2 : 0;\n if (\n chunkText &&\n chunkText.length + sepLen + firstUnit.text.length > maxChars\n ) {\n chunkText = '';\n spans.length = 0;\n }\n\n {\n const sep = chunkText ? '\\n\\n' : '';\n const start = chunkText.length + sep.length;\n chunkText = chunkText + sep + firstUnit.text;\n spans.push({\n end: chunkText.length,\n headingContext: firstUnit.headingContext,\n start,\n });\n i += 1;\n }\n\n while (i < units.length) {\n const unit = units[i];\n const candidate = `${chunkText}\\n\\n${unit.text}`;\n if (candidate.length > maxChars) {\n break;\n }\n const start = chunkText.length + 2;\n chunkText = candidate;\n spans.push({\n end: chunkText.length,\n headingContext: unit.headingContext,\n start,\n });\n i += 1;\n }\n\n const text = chunkText.trim();\n if (text) {\n chunks.push({\n headingContext: spans[0]?.headingContext ?? '',\n index: chunks.length,\n text,\n tokenCount: Math.round(text.length / 4),\n });\n }\n\n if (overlapChars > 0 && i < units.length) {\n const overlapStart = Math.max(0, chunkText.length - overlapChars);\n const carrier =\n spans.find(\n span => overlapStart >= span.start && overlapStart < span.end,\n ) ?? spans.find(span => span.start >= overlapStart);\n overlapHeading = carrier?.headingContext ?? '';\n overlapText = chunkText.slice(overlapStart).replace(/^\\n+/, '');\n } else {\n overlapText = '';\n overlapHeading = '';\n }\n }\n\n return chunks;\n}\n\n// --- Semantic strategy ------------------------------------------------------\n//\n// Break on heading/section boundaries and never split a fenced code block. Each\n// top-level mdast node is one unit; a fenced code block is a single `code` node\n// so it stays atomic for free.\n\ntype SemanticUnitKind = 'code' | 'heading' | 'text';\n\ninterface SemanticUnit {\n readonly headingContext: string;\n readonly kind: SemanticUnitKind;\n readonly text: string;\n}\n\ninterface SemanticSection {\n readonly headingContext: string;\n readonly units: readonly SemanticUnit[];\n}\n\n// A group is the unit-list slice that becomes one chunk's body, plus the\n// heading-context of its first non-carrier unit (so prepending an overlap\n// carrier cannot steal the heading's context).\ninterface SemanticGroup {\n readonly headingContext: string;\n readonly units: readonly SemanticUnit[];\n}\n\nfunction parseSemanticUnits(markdown: string): SemanticUnit[] {\n const units: SemanticUnit[] = [];\n const stack: { level: number; text: string }[] = [];\n\n function context(): string {\n return stack.map(h => h.text).join(' > ');\n }\n\n for (const block of parseBlocks(markdown)) {\n const text = markdown.slice(block.start, block.end);\n if (block.kind === 'heading') {\n while (stack.length > 0 && stack[stack.length - 1].level >= block.depth) {\n stack.pop();\n }\n stack.push({ level: block.depth, text: headingText(text) });\n units.push({ headingContext: context(), kind: 'heading', text });\n continue;\n }\n units.push({\n headingContext: context(),\n kind: block.kind === 'code' ? 'code' : 'text',\n text,\n });\n }\n return units;\n}\n\n// A section = a heading unit plus its following non-heading units until the\n// next heading. Pre-first-heading content forms its own section with an empty\n// hierarchy path.\nfunction groupSections(units: readonly SemanticUnit[]): SemanticSection[] {\n const sections: SemanticSection[] = [];\n let current: SemanticUnit[] = [];\n let currentContext = '';\n\n function flush(): void {\n if (current.length > 0) {\n sections.push({ headingContext: currentContext, units: current });\n current = [];\n }\n }\n\n for (const unit of units) {\n if (unit.kind === 'heading') {\n flush();\n current = [unit];\n currentContext = unit.headingContext;\n } else {\n if (current.length === 0) {\n currentContext = unit.headingContext;\n }\n current.push(unit);\n }\n }\n flush();\n return sections;\n}\n\nfunction joinedLength(units: readonly SemanticUnit[]): number {\n if (units.length === 0) {\n return 0;\n }\n let total = units[0].text.length;\n for (let i = 1; i < units.length; i++) {\n total += 2 + units[i].text.length;\n }\n return total;\n}\n\n// Hard-split a text unit that alone exceeds the budget — line-aware first, then\n// hard-split any oversized line. Only ever applied to paragraphs; code blocks\n// are never passed through here.\nfunction splitOversizedTextUnit(\n unit: SemanticUnit,\n maxChars: number,\n): SemanticUnit[] {\n return hardSplitLines(unit.text, maxChars).map(text => ({\n headingContext: unit.headingContext,\n kind: 'text',\n text,\n }));\n}\n\n// Pack sections into chunk groups <= maxChars, preferring heading boundaries.\n// A section that fits is never split; an oversized section is split into\n// sub-chunks by body unit (paragraphs may split; code blocks never do).\nfunction buildBaseGroups(\n sections: readonly SemanticSection[],\n maxChars: number,\n): SemanticUnit[][] {\n const groups: SemanticUnit[][] = [];\n let current: SemanticUnit[] = [];\n let currentLen = 0;\n\n function emit(): void {\n if (current.length > 0) {\n groups.push(current);\n current = [];\n currentLen = 0;\n }\n }\n\n function append(unit: SemanticUnit): void {\n const sep = current.length > 0 ? 2 : 0;\n current.push(unit);\n currentLen += sep + unit.text.length;\n }\n\n for (const section of sections) {\n const sectionLen = joinedLength(section.units);\n\n const sep = current.length > 0 ? 2 : 0;\n if (currentLen + sep + sectionLen <= maxChars) {\n for (const unit of section.units) {\n append(unit);\n }\n continue;\n }\n\n // Doesn't fit merged — close the current group and retry the section alone\n // so it has first dibs on the next group (it may still merge with a later\n // section that fits behind it).\n emit();\n\n if (sectionLen <= maxChars) {\n for (const unit of section.units) {\n append(unit);\n }\n continue;\n }\n\n // Oversized section: pack body units under the heading; split oversized\n // paragraphs, emit oversized code blocks whole.\n const [headingUnit, ...body] = section.units;\n current.push(headingUnit);\n currentLen = headingUnit.text.length;\n for (const unit of body) {\n if (unit.kind === 'code' && unit.text.length > maxChars) {\n // Semantic never splits a code fence: a fence cut mid-way is\n // unrecoverable, so the block is emitted as its own chunk even though\n // it exceeds the token budget. Deliberate tradeoff vs. the char cap.\n emit();\n groups.push([unit]);\n continue;\n }\n const sepNow = current.length > 0 ? 2 : 0;\n if (\n unit.text.length <= maxChars &&\n currentLen + sepNow + unit.text.length <= maxChars\n ) {\n append(unit);\n continue;\n }\n const pieces =\n unit.text.length > maxChars\n ? splitOversizedTextUnit(unit, maxChars)\n : [unit];\n for (const piece of pieces) {\n const pieceSep = current.length > 0 ? 2 : 0;\n if (\n current.length > 0 &&\n currentLen + pieceSep + piece.text.length > maxChars\n ) {\n emit();\n }\n append(piece);\n }\n }\n // Leave the trailing sub-chunk in `current` so a following section can\n // merge into it if it fits.\n }\n emit();\n return groups;\n}\n\n// Carry the trailing text (non-code) units from the previous group as the\n// prefix of the next, up to overlapChars. Never carry into or out of a code\n// block: a trailing code run is skipped, then contiguous text units before it\n// are carried (stop at the first heading or code block encountered).\nfunction applySemanticOverlap(\n groups: readonly SemanticUnit[][],\n overlapChars: number,\n): SemanticGroup[] {\n const result: SemanticGroup[] = [];\n for (let i = 0; i < groups.length; i++) {\n const group = groups[i];\n const carrier: SemanticUnit[] = [];\n if (i > 0 && overlapChars > 0) {\n const prev = groups[i - 1];\n let end = prev.length;\n while (end > 0 && prev[end - 1].kind === 'code') {\n end -= 1;\n }\n let carrierLen = 0;\n for (let j = end - 1; j >= 0; j--) {\n const unit = prev[j];\n if (unit.kind !== 'text') {\n break;\n }\n const sep = carrier.length > 0 ? 2 : 0;\n if (carrierLen + sep + unit.text.length > overlapChars) {\n break;\n }\n carrier.unshift(unit);\n carrierLen += sep + unit.text.length;\n }\n }\n const units = carrier.length === 0 ? group : [...carrier, ...group];\n result.push({\n // The chunk's heading context follows its first non-carrier unit, so a\n // heading-leading chunk keeps its heading's path even with overlap.\n headingContext: group[0]?.headingContext ?? '',\n units,\n });\n }\n return result;\n}\n\nfunction chunkMarkdownSemantic(\n markdown: string,\n options: Readonly<ChunkOptions>,\n): Chunk[] {\n if (!markdown) {\n return [];\n }\n const maxTokens = Math.max(1, Math.floor(options.maxTokens));\n const maxChars = maxTokens * 4;\n const overlapTokens = Math.max(0, Math.floor(options.overlap));\n const overlapChars = Math.min(overlapTokens * 4, maxChars - 1);\n\n const units = parseSemanticUnits(markdown);\n if (units.length === 0) {\n return [];\n }\n const sections = groupSections(units);\n const baseGroups = buildBaseGroups(sections, maxChars);\n const groups = applySemanticOverlap(baseGroups, overlapChars);\n\n const chunks: Chunk[] = [];\n for (const group of groups) {\n if (group.units.length === 0) {\n continue;\n }\n const text = group.units\n .map(unit => unit.text)\n .join('\\n\\n')\n .trim();\n if (!text) {\n continue;\n }\n chunks.push({\n headingContext: group.headingContext,\n index: chunks.length,\n text,\n tokenCount: Math.round(text.length / 4),\n });\n }\n return chunks;\n}\n\nexport function chunkMarkdown(\n markdown: string,\n options: Readonly<ChunkOptions>,\n): Chunk[] {\n if (options.strategy === 'semantic') {\n return chunkMarkdownSemantic(markdown, options);\n }\n return chunkMarkdownChar(markdown, options);\n}\n","import { z } from 'zod';\n\nexport const formatSchema = z.enum(['html', 'json', 'markdown', 'text']);\nexport const metadataModeSchema = z.enum(['json', 'none', 'yaml']);\nexport const extractionSchema = z.enum([\n 'aggressive',\n 'balanced',\n 'conservative',\n]);\nexport const headingStyleSchema = z.enum(['atx', 'setext']);\nexport const codeBlockStyleSchema = z.enum(['fenced', 'indented']);\nexport const imageModeSchema = z.enum([\n 'drop',\n 'keep',\n 'reference',\n 'src-only',\n]);\nexport const tableFormatSchema = z.enum(['csv', 'gfm', 'json']);\n\nexport const localPathField = z\n .string()\n .describe(\n 'Absolute or relative path to a file holding the already-rendered (post-JavaScript) HTML to process, e.g. `document.documentElement.outerHTML` written to disk by a browser/devtools capture. Read by the server so the page bytes never enter the model context — only this path string does. Resolved relative to the server process working directory; the server makes no outbound requests.',\n );\n\nexport const selectorsSchema = z\n .object({\n include: z\n .string()\n .optional()\n .describe(\n 'CSS selector restricting extraction to a matching subtree (e.g. \"main\", \"article\", \".post\"). The first match replaces the document body before processing.',\n ),\n exclude: z\n .array(z.string())\n .optional()\n .describe(\n 'CSS selectors for boilerplate to remove before extraction (e.g. [\"nav\", \"footer\", \"[role=banner]\"]).',\n ),\n })\n .optional()\n .describe(\n 'Scope the extracted/converted content by CSS selector before processing.',\n );\n\nexport const readabilityOverridesSchema = z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\n 'Escape hatch: a record spread verbatim into the Readability options. Unstable and unvalidated; overrides the extraction/keepClasses/maxNodes/minArticleLength knobs.',\n );\n\nexport const chunkStrategySchema = z.enum(['char', 'semantic']);\n\nexport const chunkOptionsSchema = z\n .object({\n maxTokens: z\n .number()\n .int()\n .min(1)\n .describe(\n 'Per-chunk token budget. Each chunk.text is sized so Math.round(text.length/4) stays within this bound (hard cap; oversized blocks are split by line, then hard-split).',\n ),\n overlap: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Tokens to overlap between consecutive chunks (>=0). The trailing overlapChars of chunk N becomes the leading context of chunk N+1, preserving cross-chunk coherence at a cost of redundant tokens.',\n )\n .default(0),\n strategy: chunkStrategySchema\n .describe(\n \"Chunking strategy. 'semantic' (default) breaks on heading/section boundaries and never splits a fenced code block; 'char' greedily groups blank-line-separated blocks under a chars/4 token budget (may split a code block).\",\n )\n .default('semantic'),\n })\n .describe(\n 'Token-bounded chunking options for splitting the extracted markdown into RAG/embedding-ready slices.',\n );\n\nexport const turndownOptionsShape = {\n debug: z\n .boolean()\n .describe(\n 'Emit per-stage timings (normalize, readability, sanitize, turndown, metadata) under diagnostics.trace. Debug-only — leaves trace absent by default.',\n )\n .default(false),\n cleanChrome: z\n .boolean()\n .describe(\n 'Strip browser chrome (scrollbars, consent/cookie banners, fixed nav and overlays) before conversion. These elements poison Readability density scoring and clutter fragment output.',\n )\n .default(true),\n codeBlockStyle: codeBlockStyleSchema\n .describe(\n \"Markdown code-block style: 'fenced' (triple backticks) or 'indented' (four-space).\",\n )\n .default('fenced'),\n format: formatSchema\n .describe(\n \"Returned payload format: 'markdown' (default), 'html', 'text', or 'json' (emits {metadata, content, diagnostics}).\",\n )\n .default('markdown'),\n gfm: z\n .boolean()\n .describe(\n 'Enable GitHub-Flavored Markdown: tables, strikethrough, and task lists.',\n )\n .default(true),\n headingStyle: headingStyleSchema\n .describe(\n \"Markdown heading style: 'atx' (#) or 'setext' (underlining with = / -).\",\n )\n .default('atx'),\n images: imageModeSchema\n .describe(\n \"Image handling: 'keep' (inline ![alt](url)), 'drop', 'src-only' (bare URL text), or 'reference' (link-reference style).\",\n )\n .default('keep'),\n maxChars: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Truncate markdown/text output at a block boundary; never splits a fenced code block. Ignored for html/json formats.',\n )\n .optional(),\n metadataMode: metadataModeSchema\n .describe(\n \"Prepend a metadata block to the markdown/text payload: 'none' (default), 'yaml', or 'json'.\",\n )\n .default('none'),\n sanitize: z\n .boolean()\n .describe(\n 'Run DOMPurify over the extracted/fragment HTML before conversion (strips scripts, event handlers, and iframes).',\n )\n .default(true),\n tables: tableFormatSchema\n .describe(\n 'Render <table> elements via a rowspan/colspan-aware matrix: \"gfm\" (default native GFM table), \"csv\" (RFC-4180-ish code block), or \"json\" (array of row objects keyed by the header). When unset, tables pass through Turndown\\'s native rule unchanged.',\n )\n .optional(),\n // zod v4 renamed `z.string().url()` to `z.url()`.\n baseUrl: z\n .url()\n .describe(\n 'Base URL for absolutizing relative links and images. NEVER fetched — origin context only.',\n )\n .optional(),\n wordsPerMinute: z\n .number()\n .int()\n .min(1)\n .describe(\n 'Reading speed (words per minute) used to compute metadata.readingTimeMin.',\n )\n .default(200),\n} as const;\n\nexport const extractInputShape = {\n localPath: localPathField,\n ...turndownOptionsShape,\n\n cache: z\n .boolean()\n .describe(\n 'When true, cache the result keyed by a normalized hash of the HTML plus the output-affecting options; repeat calls with the same normalized HTML hit the cache and report diagnostics.cache (hit/miss + both hashes). The cache is in-memory and bounded; entries are also exposed as readability://page/{hash} resources.',\n )\n .default(false),\n extraction: extractionSchema\n .describe(\n \"Readability scoring aggressiveness: 'balanced' (default), 'aggressive', or 'conservative'. Maps to Readability's scorer knobs.\",\n )\n .default('balanced'),\n keepClasses: z\n .boolean()\n .describe(\n 'Retain all CSS classes on extracted nodes. Defaults false, which strips non-language classes.',\n )\n .default(false),\n maxNodes: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Hard cap on elements parsed (Readability maxElemsToParse). Safety/perf guard for very large documents.',\n )\n .optional(),\n minArticleLength: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Minimum article character length below which extraction falls back to the selector cascade (Readability charThreshold).',\n )\n .optional(),\n readabilityOverrides: readabilityOverridesSchema,\n selectors: selectorsSchema,\n chunk: chunkOptionsSchema\n .optional()\n .describe(\n 'Split the extracted markdown into token-bounded chunks (RAG/embedding-ready). When set, structuredContent.chunks is populated. Only applies to format:\"markdown\" | \"text\"; HTML/JSON payloads carry no markdown body to slice and leave chunks unset.',\n ),\n imageInventory: z\n .boolean()\n .describe(\n 'Emit structuredContent.images: a list of {src (absolute, resolved), alt, width?, height?, caption} for every <img> in the extracted article. Independent of the `images` option (which governs inline rendering). Placeholders are skipped.',\n )\n .default(false),\n} as const;\n\nexport const extractInputSchema = z.object(extractInputShape);\n\nexport const htmlToMarkdownInputShape = {\n localPath: localPathField,\n ...turndownOptionsShape,\n selectors: selectorsSchema,\n} as const;\n\nexport const htmlToMarkdownInputSchema = z.object(htmlToMarkdownInputShape);\n\nexport const outlineInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL, carried through to metadata.baseUrl and used to absolutize links. NEVER fetched — origin context only.',\n )\n .optional(),\n selectors: selectorsSchema,\n} as const;\n\nexport const outlineInputSchema = z.object(outlineInputShape);\n\nexport const extractMetadataInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL, carried through to metadata.baseUrl and used to absolutize links. NEVER fetched — origin context only.',\n )\n .optional(),\n} as const;\n\nexport const extractMetadataInputSchema = z.object(extractMetadataInputShape);\n\nexport const extractLinksInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL for absolutizing relative hrefs and computing isExternal. NEVER fetched — origin context only.',\n )\n .optional(),\n sameOriginOnly: z\n .boolean()\n .describe(\n 'Drop cross-origin links; keep same-origin, relative, and fragment links.',\n )\n .default(false),\n selectors: selectorsSchema,\n} as const;\n\nexport const extractLinksInputSchema = z.object(extractLinksInputShape);\n\nexport const chunkTextInputShape = {\n text: z\n .string()\n .describe(\n 'Already-extracted text to split (e.g. markdown from `extract` or any plain text). No HTML parsing or Readability scoring is applied — the input is chunked verbatim.',\n ),\n maxTokens: z\n .number()\n .int()\n .min(1)\n .describe(\n 'Per-chunk token budget. Each chunk.text is sized so Math.round(text.length/4) stays within this bound (hard cap; oversized blocks are split by line, then hard-split).',\n )\n .default(500),\n overlap: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Tokens to overlap between consecutive chunks (>=0). The trailing overlapChars of chunk N becomes the leading context of chunk N+1.',\n )\n .default(0),\n strategy: chunkStrategySchema\n .describe(\n \"Chunking strategy. 'semantic' (default) breaks on heading/section boundaries and never splits a fenced code block; 'char' greedily groups blank-line-separated blocks under a chars/4 token budget.\",\n )\n .default('semantic'),\n} as const;\n\nexport const chunkTextInputSchema = z.object(chunkTextInputShape);\n\nexport const extractSectionInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL for absolutizing relative links and images. NEVER fetched — origin context only.',\n )\n .optional(),\n selector: z\n .string()\n .describe(\n 'CSS selector scoping extraction to one subtree; passed straight through as selectors.include. Provide exactly one of selector/heading.',\n )\n .optional(),\n heading: z\n .string()\n .describe(\n 'Heading text selecting one section; the section spans from this heading to the next same-or-higher-level heading. Case-insensitive; first match wins. Provide exactly one of selector/heading.',\n )\n .optional(),\n} as const;\n\nexport const extractSectionInputSchema = z\n .object(extractSectionInputShape)\n .superRefine((value, ctx) => {\n const hasSelector = value.selector !== undefined;\n const hasHeading = value.heading !== undefined;\n if (hasSelector === hasHeading) {\n ctx.addIssue({\n code: 'custom',\n message:\n 'Provide exactly one of `selector` or `heading` (both set or both unset is invalid).',\n path: ['selector'],\n });\n }\n });\n\nexport const extractTablesInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL, carried through to metadata.baseUrl. NEVER fetched — origin context only.',\n )\n .optional(),\n format: tableFormatSchema\n .describe(\n 'Output format for every table: \"gfm\" (default — native GFM table with a delimiter row), \"csv\" (RFC-4180-ish, quoted fields), or \"json\" (array of row objects keyed by the header row).',\n )\n .default('gfm'),\n selectors: selectorsSchema,\n} as const;\n\nexport const extractTablesInputSchema = z.object(extractTablesInputShape);\n\nexport const extractListInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL for absolutizing item hrefs against. NEVER fetched — origin context only.',\n )\n .optional(),\n selectors: selectorsSchema,\n} as const;\n\nexport const extractListInputSchema = z.object(extractListInputShape);\n\nexport const extractGridInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL, carried through to metadata.baseUrl. NEVER fetched — origin context only.',\n )\n .optional(),\n format: tableFormatSchema\n .describe(\n 'Output format for the grid: \"gfm\" (default — native GFM table with a delimiter row), \"csv\" (RFC-4180-ish, quoted fields), or \"json\" (array of row objects keyed by the header row).',\n )\n .default('gfm'),\n selectors: selectorsSchema,\n rowSelector: z\n .string()\n .describe(\n 'CSS selector for repeating row containers. When set WITH cellSelector, selector mode is used (no auto-detection). Example: \\'[class*=\"estimate-row\"]\\'.',\n )\n .optional(),\n cellSelector: z\n .string()\n .describe(\n 'CSS selector for cells within each row (scoped to the row subtree). Required together with rowSelector for selector mode. Example: \\'[class*=\"cell\"]\\'.',\n )\n .optional(),\n} as const;\n\n// rowSelector and cellSelector are both-or-neither: both set routes through\n// selector mode, neither set routes through auto-detection. Exactly one set is\n// ambiguous (which mode?), so it is rejected up front rather than silently\n// falling back.\nexport const extractGridInputSchema = z\n .object(extractGridInputShape)\n .superRefine((value, ctx) => {\n const hasRow = value.rowSelector !== undefined;\n const hasCell = value.cellSelector !== undefined;\n if (hasRow !== hasCell) {\n ctx.addIssue({\n code: 'custom',\n message:\n 'Provide both `rowSelector` and `cellSelector` for selector mode, or neither for auto-detection (setting only one is invalid).',\n path: ['rowSelector'],\n });\n }\n });\n\nexport type ChunkTextInput = z.infer<typeof chunkTextInputSchema>;\nexport type ExtractGridInput = z.infer<typeof extractGridInputSchema>;\nexport type ExtractInput = z.infer<typeof extractInputSchema>;\nexport type ExtractLinksInput = z.infer<typeof extractLinksInputSchema>;\nexport type ExtractListInput = z.infer<typeof extractListInputSchema>;\nexport type ExtractMetadataInput = z.infer<typeof extractMetadataInputSchema>;\nexport type ExtractSectionInput = z.infer<typeof extractSectionInputSchema>;\nexport type ExtractTablesInput = z.infer<typeof extractTablesInputSchema>;\nexport type HtmlToMarkdownInput = z.infer<typeof htmlToMarkdownInputSchema>;\nexport type OutlineInput = z.infer<typeof outlineInputSchema>;\nexport type Selectors = z.infer<typeof selectorsSchema>;\n\n// A worker takes the same fields as its public schema minus `localPath`, plus\n// the already-resolved `html` string. The option fields are all optional here:\n// the public fn hands a fully-defaulted object (it parsed the schema), while\n// internal callers (the CLI, extract_section, tests) pass only what they care\n// about and the worker merges schema defaults for the rest. Tools whose schema\n// has no `.default()` fields have nothing to merge.\nexport type FromHtmlInput<T> = Partial<Omit<T, 'localPath'>> & { html: string };\n\nexport type ExtractFromHtmlInput = FromHtmlInput<ExtractInput>;\nexport type HtmlToMarkdownFromHtmlInput = FromHtmlInput<HtmlToMarkdownInput>;\nexport type OutlineFromHtmlInput = FromHtmlInput<OutlineInput>;\nexport type ExtractLinksFromHtmlInput = FromHtmlInput<ExtractLinksInput>;\nexport type ExtractListFromHtmlInput = FromHtmlInput<ExtractListInput>;\nexport type ExtractMetadataFromHtmlInput = FromHtmlInput<ExtractMetadataInput>;\nexport type ExtractSectionFromHtmlInput = FromHtmlInput<ExtractSectionInput>;\nexport type ExtractTablesFromHtmlInput = FromHtmlInput<ExtractTablesInput>;\nexport type ExtractGridFromHtmlInput = FromHtmlInput<ExtractGridInput>;\n","import { z } from 'zod';\n\nimport { tableFormatSchema } from './schemas.js';\n\n// Shared between `extract` and `extract_metadata` outputs so the metadata\n// cascade has one shape across tools. wordCount/readingTimeMin/tokenEstimate\n// are optional here — extract_metadata omits them; extract populates them.\nconst metadataObjectSchema = z\n .object({\n byline: z\n .string()\n .optional()\n .describe(\n 'Article author(s), resolved from JSON-LD, OpenGraph, <meta>, or Readability.',\n ),\n canonical: z\n .string()\n .optional()\n .describe(\n 'Declared canonical URL from <link rel=\"canonical\"> (or og:url as fallback). Distinct from baseUrl, which is the origin context passed in.',\n ),\n estimator: z\n .string()\n .optional()\n .describe(\n 'Name of the heuristic backing tokenEstimate (e.g. \"chars/4\").',\n ),\n excerpt: z\n .string()\n .optional()\n .describe('Short article summary produced by Readability.'),\n lang: z.string().optional().describe('Detected document language.'),\n publishedTime: z\n .string()\n .optional()\n .describe(\n 'Publication timestamp resolved from JSON-LD, <meta>, or <time> elements.',\n ),\n readingTimeMin: z\n .number()\n .int()\n .optional()\n .describe(\n 'Estimated reading time in minutes, derived from wordCount and wordsPerMinute.',\n ),\n siteName: z\n .string()\n .optional()\n .describe('Publishing site name, resolved from OpenGraph or <meta>.'),\n structured: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\n 'Parsed schema.org JSON-LD primary object (Recipe/Product/Event/HowTo/Article…) when present — the raw graph node with @context stripped and @type normalized to a \"+\"-joined string. Absent when the page has no recognizable structured data.',\n ),\n title: z\n .string()\n .optional()\n .describe(\n 'Article title, resolved by priority cascade (JSON-LD → OpenGraph → Twitter → <meta> → Readability → <title>).',\n ),\n tokenEstimate: z\n .number()\n .int()\n .optional()\n .describe(\n 'Rough output token count (chars/4 by default) for context budgeting.',\n ),\n baseUrl: z\n .string()\n .optional()\n .describe('The baseUrl passed in (origin context).'),\n wordCount: z\n .number()\n .int()\n .optional()\n .describe('Number of whitespace-separated words in the extracted text.'),\n })\n .describe(\n 'Resolved article metadata. Each field is the first non-empty value across a priority cascade.',\n );\n\nexport const chunkObjectSchema = z\n .object({\n index: z\n .number()\n .int()\n .min(0)\n .describe('Zero-based chunk position within the emitted sequence.'),\n text: z\n .string()\n .describe(\n 'The chunk body (markdown or text), trimmed, sized to stay within the requested token budget.',\n ),\n tokenCount: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Estimated token count of text (chars/4), same heuristic as metadata.tokenEstimate.',\n ),\n headingContext: z\n .string()\n .describe(\n 'Nearest preceding markdown heading text in effect at the chunk’s first block. Empty string when the chunk precedes any heading; carried from the overlap source when a chunk begins with overlap text.',\n ),\n })\n .describe(\n 'One token-bounded slice of the extracted markdown, with its section heading for context.',\n );\n\nexport const imageEntrySchema = z\n .object({\n src: z\n .string()\n .describe('Absolute (resolved) image URL, absolutized against baseUrl.'),\n alt: z\n .string()\n .describe('The img alt attribute, or empty string when absent.'),\n width: z\n .number()\n .int()\n .optional()\n .describe('Pixel dimension from the attribute, when present.'),\n height: z\n .number()\n .int()\n .optional()\n .describe('Pixel dimension from the attribute, when present.'),\n caption: z\n .string()\n .describe('figcaption text from the enclosing <figure>, else alt.'),\n })\n .describe('One extracted image with resolved source and caption.');\n\nexport const outputSchemaShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'The human/LLM-readable payload — Markdown/html/text, or the serialized JSON when format=json.',\n ),\n chunks: z\n .array(chunkObjectSchema)\n .optional()\n .describe(\n 'Token-bounded chunks of the extracted markdown, populated by `extract` only when the `chunk` option is set and the format yields a markdown/text body. Absent for html_to_markdown and for html/json extract formats.',\n ),\n images: z\n .array(imageEntrySchema)\n .optional()\n .describe(\n 'Inventory of article images (absolute src, alt, dimensions, caption); populated only when imageInventory:true is passed to extract.',\n ),\n metadata: metadataObjectSchema,\n diagnostics: z\n .object({\n boilerplateRemoved: z\n .number()\n .int()\n .optional()\n .describe(\n 'Count of boilerplate blocks (related-posts, newsletter signup, read-next) stripped before conversion.',\n ),\n cache: z\n .object({\n hit: z\n .boolean()\n .describe(\n 'True when this call was served from the in-memory cache without re-running the pipeline.',\n ),\n normalizedHash: z\n .string()\n .describe(\n 'sha256 of the volatility-normalized HTML (whitespace collapsed, scripts/nonce/CSP/generated ids stripped) — the actual cache key, shared across re-renders that differ only in volatile markup.',\n ),\n originalHash: z\n .string()\n .describe(\n 'sha256 of the raw HTML as passed (trimmed). Differs from normalizedHash when volatile markup (nonce/CSP/generated ids) was collapsed; equal when the input was already stable. Used to diagnose should-have-hit-but-didn’t misses.',\n ),\n })\n .optional()\n .describe(\n 'Cache signal — populated only by `extract` when called with cache:true. Absent otherwise (goldens, default path, and other tools never emit this field).',\n ),\n chromeRemoved: z\n .number()\n .int()\n .optional()\n .describe(\n 'Count of browser-chrome nodes stripped before conversion (scrollbars, consent banners, overlays).',\n ),\n extractedNode: z\n .string()\n .optional()\n .describe(\n 'DOM root extraction came from: \"readability\" (main path), a fallback selector (e.g. \"article\", \"main\"), or \"fragment\" for html_to_markdown.',\n ),\n fallbackUsed: z\n .boolean()\n .describe(\n 'True if Readability parse failed and a selector cascade salvaged content. Always true for html_to_markdown.',\n ),\n gated: z\n .object({\n likely: z\n .boolean()\n .describe(\n 'True when heuristics strongly suggest the content is paywalled or truncated.',\n ),\n reason: z\n .string()\n .describe(\n 'Short label naming the detected signal (e.g. \"paywall overlay\", \"metered paywall message\").',\n ),\n })\n .optional()\n .describe(\n 'Likely paywall / gating signal. The extraction may be partial; the host can re-capture after authenticating. Detection only — this server never fetches or authenticates.',\n ),\n imagesResolved: z\n .number()\n .int()\n .optional()\n .describe(\n 'Count of lazy/placeholder images resolved to their real src before conversion.',\n ),\n pagination: z\n .object({\n type: z\n .enum(['infinite', 'paginated'])\n .describe('Kind of pagination signal detected in the document.'),\n nextUrl: z\n .string()\n .optional()\n .describe(\n 'Absolute URL of the detected next page (paginated only). Mirrors the href found in the DOM; never fetched by this server.',\n ),\n selector: z\n .string()\n .optional()\n .describe(\n 'CSS selector of the detected load-more / infinite-scroll sentinel (infinite only).',\n ),\n })\n .optional()\n .describe(\n 'Detected pagination or infinite-scroll signal. Detection only — the host drives loading; this server never fetches.',\n ),\n readerable: z\n .boolean()\n .optional()\n .describe(\n 'Readability isProbablyReaderable verdict on the document (extract main path only).',\n ),\n removedNodes: z\n .number()\n .int()\n .optional()\n .describe(\n 'Net element count removed across the whole pipeline (delta vs. the parsed document).',\n ),\n sanitization: z\n .object({\n iframes: z\n .number()\n .int()\n .describe('<iframe> elements removed by sanitization.'),\n scripts: z\n .number()\n .int()\n .describe(\n '<script> and event-handler nodes removed by sanitization.',\n ),\n })\n .optional()\n .describe('Counts of nodes removed by DOMPurify sanitization.'),\n truncated: z\n .boolean()\n .describe('True if the payload was truncated by maxChars.'),\n trace: z\n .array(\n z\n .object({\n stage: z\n .string()\n .describe(\n 'Pipeline stage name (e.g. \"normalize\", \"readability\", \"sanitize\", \"turndown\", \"metadata\").',\n ),\n ms: z\n .number()\n .describe(\n 'Wall-clock duration of the stage in milliseconds, measured via performance.now().',\n ),\n })\n .describe('One timed pipeline stage.'),\n )\n .optional()\n .describe(\n 'Per-stage timings emitted only when debug:true is passed to extract/html_to_markdown. Stages are non-overlapping and ordered; absent otherwise.',\n ),\n })\n .describe(\n 'Pipeline telemetry describing what was extracted, sanitized, and removed.',\n ),\n} as const;\n\nexport const outputSchema = z.object(outputSchemaShape);\n\nexport type StructuredContent = z.infer<typeof outputSchema>;\n\nexport const outlineOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'Indented-bullet table of contents, one line per heading, nested by depth.',\n ),\n outline: z\n .array(\n z\n .object({\n level: z\n .number()\n .int()\n .min(1)\n .max(6)\n .describe('Heading level (1–6).'),\n text: z.string().describe('Heading text content.'),\n anchor: z\n .string()\n .describe(\n 'Stable anchor id: the heading own id, a descendant permalink fragment, or a slug of the text (deduped -1, -2, … for generated slugs).',\n ),\n })\n .describe('A single document heading with its stable anchor.'),\n )\n .describe(\n 'Document headings (h1–h6) in document order, each with a stable anchor id.',\n ),\n metadata: z\n .object({\n title: z\n .string()\n .optional()\n .describe(\n 'Document title from <title>, falling back to the first <h1>.',\n ),\n baseUrl: z\n .string()\n .optional()\n .describe('The baseUrl passed in (origin context, never fetched).'),\n })\n .describe('Outline document metadata.'),\n} as const;\n\nexport const outlineOutput = z.object(outlineOutputShape);\n\nexport type OutlineStructuredContent = z.infer<typeof outlineOutput>;\n\nexport const extractMetadataOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'Human-readable key:value rendering of the metadata block, so content[0].text is never empty.',\n ),\n metadata: metadataObjectSchema,\n} as const;\n\nexport const extractMetadataOutput = z.object(extractMetadataOutputShape);\n\nexport type ExtractMetadataStructuredContent = z.infer<\n typeof extractMetadataOutput\n>;\n\nexport const chunkTextOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'Readable index of the chunks (one numbered section per chunk, each prefixed with its heading context), so content[0].text is always scannable.',\n ),\n chunks: z\n .array(chunkObjectSchema)\n .describe(\n 'The emitted chunks in order. Empty array when the input contains no non-whitespace content.',\n ),\n} as const;\n\nexport const chunkTextOutput = z.object(chunkTextOutputShape);\n\nexport type ChunkTextStructuredContent = z.infer<typeof chunkTextOutput>;\n\nexport const linkObjectSchema = z\n .object({\n text: z\n .string()\n .describe(\n 'Anchor text content, whitespace-collapsed and trimmed (capped at 300 chars).',\n ),\n href: z\n .string()\n .describe(\n 'Absolute href (resolved against baseUrl when provided); unchanged when baseUrl is absent or the pair fails to parse.',\n ),\n rel: z\n .string()\n .describe(\n 'The raw rel attribute value (e.g. \"noopener noreferrer\", \"nofollow\"), or the empty string when absent.',\n ),\n isExternal: z\n .boolean()\n .describe(\n 'True when baseUrl is provided and the href parses to a different origin than baseUrl. False for relative, fragment, same-origin, non-http(s) (mailto/tel/javascript), and malformed hrefs.',\n ),\n })\n .describe(\n 'A single anchor link with its text, absolute href, rel, and origin.',\n );\n\nexport const extractLinksOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'Readable rendering of the link list (one `- [text](href)` line per link), so content[0].text is never empty.',\n ),\n links: z\n .array(linkObjectSchema)\n .describe(\n 'Anchors in document order, hrefs absolutized against baseUrl. No deduplication.',\n ),\n metadata: z\n .object({\n baseUrl: z\n .string()\n .optional()\n .describe('The baseUrl passed in (origin context, never fetched).'),\n })\n .describe('Extract-links document metadata.'),\n} as const;\n\nexport const extractLinksOutput = z.object(extractLinksOutputShape);\n\nexport type ExtractLinksStructuredContent = z.infer<typeof extractLinksOutput>;\n\nexport const tableEntrySchema = z\n .object({\n index: z\n .number()\n .int()\n .min(0)\n .describe('0-based position among emitted tables.'),\n rows: z\n .number()\n .int()\n .min(0)\n .describe('Row count of the matrix (after rowspan/colspan resolution).'),\n cols: z\n .number()\n .int()\n .min(0)\n .describe('Column count of the matrix (after colspan resolution).'),\n markdown: z\n .string()\n .describe('The table rendered in the requested format (gfm/csv/json).'),\n })\n .describe('One extracted table with its dimensions and rendered form.');\n\nexport const extractTablesOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'All tables rendered in the requested format, joined by blank lines; \"(no tables found)\" when none.',\n ),\n tables: z\n .array(tableEntrySchema)\n .describe(\n 'Every <table> on the page (rowspan/colspan-resolved), in document order.',\n ),\n metadata: z\n .object({\n baseUrl: z\n .string()\n .optional()\n .describe('The baseUrl passed in (origin context).'),\n format: tableFormatSchema.describe('The requested render format.'),\n tableCount: z.number().int().describe('Number of tables emitted.'),\n })\n .describe('Tables-tool metadata.'),\n} as const;\n\nexport const extractTablesOutput = z.object(extractTablesOutputShape);\n\nexport type ExtractTablesStructuredContent = z.infer<\n typeof extractTablesOutput\n>;\n\nexport const gridEntrySchema = z\n .object({\n rows: z\n .number()\n .int()\n .min(0)\n .describe('Row count of the detected grid (after ragged-row padding).'),\n cols: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Column count of the detected grid (max cell width across rows).',\n ),\n markdown: z\n .string()\n .describe('The grid rendered in the requested format (gfm/csv/json).'),\n })\n .describe('One detected grid with its dimensions and rendered form.');\n\nexport const extractGridOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'The grid rendered in the requested format, or \"(no repeating grid found)\" when no grid is detected.',\n ),\n grid: gridEntrySchema.describe(\n 'The detected grid (dimensions + rendered markdown). Rows/cols are 0 and markdown is empty when nothing is detected.',\n ),\n diagnostics: z\n .object({\n confidence: z\n .enum(['high', 'low', 'medium'])\n .describe(\n '`high` when ≥6 detected data rows, `medium` when ≥3 (minRows), `low` otherwise. Counts the detected cluster only — a recovered header row is inference and does not raise it. `low` for non-grid pages.',\n ),\n containerSelector: z\n .string()\n .describe(\n 'CSS-ish hint (tag#id.class) of the winning container, or the rowSelector in selector mode. Empty when not detected.',\n ),\n detected: z\n .boolean()\n .describe(\n 'True when a repeating grid was found (≥3 same-shape sibling rows each with ≥2 direct element-children, outside nav/header/footer/aside).',\n ),\n rowCount: z\n .number()\n .int()\n .describe(\n 'Number of rows emitted, including any recovered header row. 0 when not detected.',\n ),\n colCount: z\n .number()\n .int()\n .describe(\n 'Number of columns (max cell width across rows). 0 when not detected.',\n ),\n rowTag: z\n .string()\n .describe(\n 'Uppercase DOM tag name of the repeating row container (e.g. \"DIV\", \"TR\", \"LI\"). Empty when not detected.',\n ),\n note: z\n .string()\n .describe(\n 'Short human-readable status: the detection reason when detected, or the rejection reason when not.',\n ),\n })\n .describe('Grid-detection telemetry describing the winning candidate.'),\n metadata: z\n .object({\n baseUrl: z\n .string()\n .optional()\n .describe('The baseUrl passed in (origin context, never fetched).'),\n format: tableFormatSchema.describe('The requested render format.'),\n detected: z.boolean().describe('Mirrors diagnostics.detected.'),\n })\n .describe('Extract-grid document metadata.'),\n} as const;\n\nexport const extractGridOutput = z.object(extractGridOutputShape);\n\nexport type ExtractGridStructuredContent = z.infer<typeof extractGridOutput>;\n\nconst listItemSchema = z\n .object({\n score: z\n .number()\n .int()\n .describe(\n 'Item substance score: primary-anchor text length + non-link body text length. Long titles (real feed items) score higher than short nav labels; items with both a long title and surrounding body text (snippets, excerpts) score highest.',\n ),\n snippet: z\n .string()\n .describe(\n 'Item body text with the title peeled off, whitespace-collapsed and clipped at 200 chars. Empty when the item is title-only.',\n ),\n title: z\n .string()\n .describe(\n 'Primary anchor text (longest-text <a> in the item, whitespace-collapsed). Always non-empty for emitted items.',\n ),\n url: z\n .string()\n .describe(\n 'Absolute href of the primary anchor, resolved against baseUrl. Always non-empty for emitted items.',\n ),\n })\n .describe('One detected list item with its title, URL, snippet, and score.');\n\nexport const extractListOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'Readable rendering of the items (one numbered `title — url` line per item, each followed by an indented snippet), or a single `not a list: …` line when no list structure is detected.',\n ),\n items: z\n .array(listItemSchema)\n .describe(\n 'Detected list items in document order. Empty when the page has no repeated same-shape sibling structure with anchors (e.g. article pages).',\n ),\n diagnostics: z\n .object({\n confidence: z\n .enum(['high', 'low', 'medium'])\n .describe(\n '`high` when ≥6 items and avg score ≥30, `medium` when ≥3 items, `low` otherwise. `low` for non-list pages.',\n ),\n containerSelector: z\n .string()\n .describe(\n 'CSS-ish hint (tag#id.class) of the winning container. Empty when not detected.',\n ),\n detected: z\n .boolean()\n .describe(\n 'True when a list/feed/index structure was found (≥3 same-shape siblings each with a navigation anchor, outside nav/header/footer/aside).',\n ),\n itemCount: z\n .number()\n .int()\n .describe('Number of items emitted. 0 when not detected.'),\n itemTag: z\n .string()\n .describe(\n 'Uppercase DOM tag name of the winning sibling group (e.g. \"TR\", \"LI\", \"ARTICLE\", \"DIV\"). Empty when not detected.',\n ),\n note: z\n .string()\n .describe(\n 'Short human-readable status: the detection reason when detected, or the rejection reason when not.',\n ),\n })\n .describe('List-detection telemetry describing the winning candidate.'),\n metadata: z\n .object({\n baseUrl: z\n .string()\n .optional()\n .describe('The baseUrl passed in (origin context, never fetched).'),\n })\n .describe('Extract-list document metadata.'),\n} as const;\n\nexport const extractListOutput = z.object(extractListOutputShape);\n\nexport type ExtractListStructuredContent = z.infer<typeof extractListOutput>;\n","import { JSDOM } from 'jsdom';\n\nexport interface BuiltDocument {\n readonly document: Document;\n readonly window: Window;\n}\n\n// Built only from caller-supplied `html`; `baseUrl` is origin context for\n// absolutization, never fetched. Parse and the downstream Readability walk run\n// synchronously on the event loop — no worker isolation or wall-clock timeout\n// guards them. A worker pool was deferred rather than rejected: the synchronous\n// parse can't be interrupted in-process, and `worker_threads` can't load the dev\n// hot-reload loop's in-memory Vite modules. `maxNodes` is the only input-size\n// bound until a holistic worker strategy lands; a standalone `timeout` option\n// would mislead (it can't interrupt the parse), so it ships with the worker.\nexport function buildDocument(html: string, baseUrl?: string): BuiltDocument {\n const dom = new JSDOM(html, { url: baseUrl });\n return { document: dom.window.document, window: dom.window };\n}\n\nexport function isElement(node: Node): node is Element {\n return node.nodeType === 1;\n}\n","// Math renderers leave the original LaTeX in a small, engine-agnostic pocket:\n// the `<annotation encoding=\"application/x-tex\">` child of a `<math>` element.\n// KaTeX wraps it inside `.katex`, arXiv/LaTeXML (ar5iv) and MDN ship it in a\n// bare `<math display=\"…\">`, and MathJax v2/v3 do not use `<annotation>` at\n// all — their source lives in `<script type=\"math/tex\">` that the\n// script-removal pass strips. Move each into one controlled marker span BEFORE\n// normalize's `<script>` removal so every engine shares a single turndown rule\n// that emits the LaTeX verbatim.\n\nconst MARKER_CLASS = 'rdrm-math';\nconst DISPLAY_ATTR = 'data-display';\nconst KATEX_CLASS = 'katex';\nconst KATEX_DISPLAY_CLASS = 'katex-display';\nconst TEX_ANNOTATION_SELECTOR = 'annotation[encoding=\"application/x-tex\"]';\nconst MATHJAX_INLINE_TYPE = 'math/tex';\nconst MATHJAX_DISPLAY_TYPE = 'math/tex; mode=display';\n// arXiv/LaTeXML (`ltx_*`) and common docs-site display-math wrappers. A bare\n// `<math display=\"block\">` also flags display, but pages often omit the\n// attribute and signal block context only through the wrapper class.\nconst MATHML_DISPLAY_SELECTOR =\n '.ltx_equation, .ltx_displaymath, .equation-display, .math-display';\n// Placeholder for a math container whose annotation and alttext are both empty\n// or absent; rendered markup has no recoverable LaTeX, so emit a token rather\n// than crash.\nconst BROKEN_PLACEHOLDER = '[?]';\n\nfunction createMarker(\n document: Document,\n tex: string,\n display: boolean,\n): HTMLElement {\n const marker = document.createElement('span');\n marker.className = MARKER_CLASS;\n marker.setAttribute(DISPLAY_ATTR, display ? 'true' : 'false');\n marker.textContent = tex;\n return marker;\n}\n\n// Drives off the annotation, not the wrapper, so one pass recovers LaTeX from\n// every engine that emits `<annotation encoding=\"application/x-tex\">` — KaTeX\n// inside `.katex`, arXiv/MDN inside a bare `<math>`. The container replaced is\n// engine-specific: the `.katex` span (which also drops the `.katex-html`\n// rendered sibling), or the bare `<math>` element. Replacing a container\n// detaches other annotations that lived inside it, so disconnected nodes\n// reached later in document order are skipped. arXiv carries the LaTeX in\n// `<math alttext=\"…\">` as well, which is the fallback when the annotation text\n// is empty.\nfunction convertAnnotations(document: Document): void {\n const annotations = document.querySelectorAll(TEX_ANNOTATION_SELECTOR);\n for (const annotation of Array.from(annotations)) {\n if (!annotation.isConnected) {\n continue;\n }\n try {\n const katex = annotation.closest(`.${KATEX_CLASS}`);\n const math = annotation.closest('math');\n let container: Element = annotation;\n let display = false;\n if (katex) {\n container = katex;\n display = katex.closest(`.${KATEX_DISPLAY_CLASS}`) !== null;\n } else if (math) {\n container = math;\n display =\n math.getAttribute('display') === 'block' ||\n math.closest(MATHML_DISPLAY_SELECTOR) !== null;\n }\n const tex =\n annotation.textContent.trim() ||\n (math?.getAttribute('alttext') ?? '').trim();\n container.replaceWith(\n createMarker(document, tex || BROKEN_PLACEHOLDER, display),\n );\n } catch {\n annotation.replaceWith(createMarker(document, BROKEN_PLACEHOLDER, false));\n }\n }\n}\n\n// A `.katex` that survived the annotation pass had no recoverable annotation\n// (stripped or never present). Its `.katex-html` rendered tree carries no\n// recoverable LaTeX, so swap the whole span for a placeholder marker rather\n// than let rendered spans leak into turndown.\nfunction convertOrphanedKatex(document: Document): void {\n for (const katex of Array.from(\n document.getElementsByClassName(KATEX_CLASS),\n )) {\n if (!katex.isConnected) {\n continue;\n }\n try {\n const display = katex.closest(`.${KATEX_DISPLAY_CLASS}`) !== null;\n katex.replaceWith(createMarker(document, BROKEN_PLACEHOLDER, display));\n } catch {\n // Defensive: malformed markup must not break the extract pipeline.\n }\n }\n}\n\n// MathJax v2/v3 inline + display source scripts. These `<script>` nodes are\n// removed by normalize's generic script-removal AND by DOMPurify, so they must\n// be converted here, before that loop runs. MathJax scripts do not use\n// `<annotation>`, so they are handled separately from `convertAnnotations`.\nfunction convertMathJax(document: Document): void {\n const scripts = document.querySelectorAll(\n `script[type=\"${MATHJAX_INLINE_TYPE}\"], script[type=\"${MATHJAX_DISPLAY_TYPE}\"]`,\n );\n for (const script of Array.from(scripts)) {\n if (!script.isConnected) {\n continue;\n }\n try {\n const rawType = script.getAttribute('type') ?? '';\n const display = rawType.includes('mode=display');\n const tex = script.textContent.trim();\n script.replaceWith(\n createMarker(document, tex || BROKEN_PLACEHOLDER, display),\n );\n } catch {\n script.replaceWith(createMarker(document, BROKEN_PLACEHOLDER, false));\n }\n }\n}\n\n// Walks the pre-normalize document and rewrites every math container —\n// annotation-bearing (KaTeX, bare MathML) or MathJax script — into a\n// `<span class=\"rdrm-math\" data-display=\"…\">LATEX</span>` marker. Idempotent:\n// markers carry no `.katex`/`<math>`/annotation, so a second pass is a no-op.\n// Defensive per node so malformed markup never aborts the pass.\nexport function extractMath(document: Document): void {\n convertAnnotations(document);\n convertOrphanedKatex(document);\n convertMathJax(document);\n}\n","import type { ReadabilityOptions } from '../pipeline/readability.js';\n\nexport type ExtractionMode = 'aggressive' | 'balanced' | 'conservative';\n\nexport interface ResolveReadabilityInput {\n readonly extraction?: ExtractionMode;\n readonly keepClasses?: boolean;\n readonly maxNodes?: number;\n readonly minArticleLength?: number;\n readonly readabilityOverrides?: Readonly<Record<string, unknown>>;\n}\n\n// Readability.js confirmed defaults.\nconst DEFAULT_CHAR_THRESHOLD = 500;\nconst DEFAULT_N_TOP_CANDIDATES = 5;\n\n// Readability keeps a class only by strict `classesToPreserve.includes(cls)`\n// equality (not regex), stripping code-block language tokens by default; list\n// the common highlight.js/prism `language-*` tokens so fences keep their tag.\n// `rdrm-math` is the math marker emitted by `policy/math.ts` — Readability\n// would otherwise strip it before turndown's math rule runs. Applies only when\n// `keepClasses` is false.\nconst CLASSES_TO_PRESERVE: readonly string[] = [\n 'hljs',\n 'language-asm',\n 'language-assembly',\n 'language-bash',\n 'language-c',\n 'language-clojure',\n 'language-cpp',\n 'language-cs',\n 'language-csharp',\n 'language-css',\n 'language-dart',\n 'language-diff',\n 'language-dockerfile',\n 'language-elixir',\n 'language-erlang',\n 'language-go',\n 'language-graphql',\n 'language-haskell',\n 'language-html',\n 'language-ini',\n 'language-java',\n 'language-javascript',\n 'language-js',\n 'language-jsx',\n 'language-json',\n 'language-kotlin',\n 'language-lisp',\n 'language-lua',\n 'language-md',\n 'language-markdown',\n 'language-objc',\n 'language-objectivec',\n 'language-perl',\n 'language-php',\n 'language-plaintext',\n 'language-powershell',\n 'language-py',\n 'language-python',\n 'language-r',\n 'language-rb',\n 'language-rs',\n 'language-ruby',\n 'language-rust',\n 'language-scala',\n 'language-sh',\n 'language-shell',\n 'language-sql',\n 'language-swift',\n 'language-text',\n 'language-toml',\n 'language-ts',\n 'language-tsx',\n 'language-typescript',\n 'language-vim',\n 'language-wasm',\n 'language-xml',\n 'language-yaml',\n 'language-yml',\n 'rdrm-math',\n];\n\n// Bare tokens (the `X` in `language-X`) shared by convention resolvers that\n// must validate a candidate against the preserve set, e.g. sandpack's `sp-*`\n// classes, which carry infra strings alongside the language token. Filter to\n// `language-` entries so non-language preserve additions (`hljs`, `rdrm-math`)\n// do not pollute the set with the empty token.\nexport const KNOWN_LANGUAGE_TOKENS: ReadonlySet<string> = new Set(\n CLASSES_TO_PRESERVE.filter(c => c.startsWith('language-')).map(c =>\n c.slice('language-'.length),\n ),\n);\n\ninterface ModeKnobs {\n readonly charThreshold: number;\n readonly nbTopCandidates: number;\n}\n\nfunction knobsForMode(mode: ExtractionMode): ModeKnobs | null {\n switch (mode) {\n case 'aggressive':\n return {\n charThreshold: Math.round(DEFAULT_CHAR_THRESHOLD / 2),\n nbTopCandidates: DEFAULT_N_TOP_CANDIDATES * 2,\n };\n case 'balanced':\n // null ⇒ emit nothing; Readability uses its own defaults.\n return null;\n case 'conservative':\n return {\n charThreshold: DEFAULT_CHAR_THRESHOLD * 2,\n nbTopCandidates: Math.max(1, Math.round(DEFAULT_N_TOP_CANDIDATES / 2)),\n };\n }\n}\n\nexport function resolveReadabilityOptions(\n input: ResolveReadabilityInput,\n): ReadabilityOptions {\n const mode = input.extraction ?? 'balanced';\n const modeKnobs = knobsForMode(mode);\n const keepClasses = input.keepClasses ?? false;\n\n const charThreshold =\n input.minArticleLength !== undefined\n ? input.minArticleLength\n : modeKnobs?.charThreshold;\n const nbTopCandidates = modeKnobs?.nbTopCandidates;\n\n // Escape hatch spreads last so it wins verbatim over every derived knob.\n return {\n classesToPreserve: keepClasses ? [] : [...CLASSES_TO_PRESERVE],\n keepClasses,\n ...(charThreshold !== undefined ? { charThreshold } : {}),\n ...(nbTopCandidates !== undefined ? { nbTopCandidates } : {}),\n ...(input.maxNodes !== undefined\n ? { maxElemsToParse: input.maxNodes }\n : {}),\n ...(input.readabilityOverrides ?? {}),\n };\n}\n","import { extractMath } from '../policy/math.js';\nimport { KNOWN_LANGUAGE_TOKENS } from '../policy/resolver.js';\n\nconst NONCE_ATTR = 'nonce';\n\nexport interface NormalizeCounts {\n readonly boilerplateRemoved: number;\n readonly chromeRemoved: number;\n readonly iframes: number;\n readonly scripts: number;\n}\n\nexport interface NormalizeOptions {\n readonly cleanChrome?: boolean;\n}\n\nexport function normalizeDocument(\n document: Document,\n options?: NormalizeOptions,\n): NormalizeCounts {\n // MathJax source lives in `<script type=\"math/tex\">`, which the script-removal\n // loop below strips — extract math into markers first so both engines share\n // one turndown rule downstream.\n extractMath(document);\n\n const baseEls = document.querySelectorAll('base');\n // Preserve <script type=\"application/ld+json\">: structured metadata consumed by the cascade.\n const scriptEls = document.querySelectorAll(\n 'script:not([type=\"application/ld+json\"])',\n );\n\n baseEls.forEach(el => {\n el.remove();\n });\n scriptEls.forEach(el => {\n el.remove();\n });\n\n const withNonce = document.querySelectorAll(`[${NONCE_ATTR}]`);\n withNonce.forEach(el => {\n el.removeAttribute(NONCE_ATTR);\n });\n\n const chromeRemoved =\n options?.cleanChrome === false ? 0 : stripChrome(document);\n\n const boilerplateRemoved = stripBoilerplate(document);\n\n return {\n boilerplateRemoved,\n chromeRemoved,\n iframes: 0,\n scripts: scriptEls.length,\n };\n}\n\n// Conservative, vendor-specific consent SDK selectors. Curated, not greedy —\n// intentionally NOT `[class*=\"consent\"]` (that eats real article widgets).\nconst CONSENT_SELECTORS = [\n '[role=\"dialog\"]',\n '[aria-modal=\"true\"]',\n '#onetrust-banner-sdk',\n '#onetrust-consent-sdk',\n '#onetrust-pc-sdk',\n '.cc-window',\n '.cc-banner',\n '.cc-revoke',\n '.osano-cm-window',\n '.osano-cm-dialog',\n '.qc-cmp2-container',\n '.qc-cmp-ui-container',\n '#sp_message_container',\n '[id^=\"sp_message_container_\"]',\n '#didomi-host',\n '.didomi-popup-container',\n '#truste-consent-track',\n '#consent_blackbar',\n '#cookie-banner',\n '.cookie-banner',\n '#consent-banner',\n '.consent-banner',\n '.cookie-bar',\n '.gdpr-banner',\n '.privacy-banner',\n];\n\n// jsdom has no layout, so \"covers the viewport\" must be inferred from inline\n// style. Requiring BOTH axes full is what protects a fixed nav bar: a nav is\n// full-width but its height is a fixed px (not 100%/100vh), and inset:0 is not\n// used. Dropping either axis from the conjunction nukes legit chrome.\nfunction isFullViewportOverlay(style: string): boolean {\n if (!/position\\s*:\\s*(?:fixed|sticky)/i.test(style)) {\n return false;\n }\n const zIndexMatch = /z-index\\s*:\\s*(\\d+)/i.exec(style);\n if (!zIndexMatch || Number.parseInt(zIndexMatch[1], 10) < 1000) {\n return false;\n }\n // `inset:0` is shorthand for top/right/bottom/left = 0, so it satisfies BOTH\n // axes at once and is checked up front.\n if (/inset\\s*:\\s*0/i.test(style)) {\n return true;\n }\n const widthFull =\n /width\\s*:\\s*100(?:%|vw)/i.test(style) ||\n (/left\\s*:\\s*0/i.test(style) && /right\\s*:\\s*0/i.test(style));\n const heightFull =\n /height\\s*:\\s*100(?:%|vh)/i.test(style) ||\n (/top\\s*:\\s*0/i.test(style) && /bottom\\s*:\\s*0/i.test(style));\n return widthFull && heightFull;\n}\n\n// Removes before Readability scores them — these poison density math and leak\n// into the article. Inline-style matches require the full isFullViewportOverlay\n// conjunction; `position:fixed` alone never qualifies.\nexport function stripChrome(document: Document): number {\n let removed = 0;\n\n const consentEls = document.querySelectorAll(CONSENT_SELECTORS.join(','));\n for (const el of consentEls) {\n if (!el.isConnected) {\n continue;\n }\n el.remove();\n removed++;\n }\n\n const styledEls = document.querySelectorAll('[style]');\n for (const el of styledEls) {\n if (!el.isConnected) {\n continue;\n }\n if (isFullViewportOverlay(el.getAttribute('style') ?? '')) {\n el.remove();\n removed++;\n }\n }\n\n return removed;\n}\n\n// Unambiguous boilerplate signatures. Bare `related`/`subscribe` are\n// intentionally excluded — they match legit content (related-products section\n// on a store, a \"subscribe to RSS\" line inside an article) far too often.\n// Substring matching means `newsletter` also covers `newsletter-signup`, etc.;\n// the longer spellings are kept as explicit documentation of intended targets.\nconst BOILERPLATE_TOKENS = [\n 'newsletter',\n 'newsletter-signup',\n 'mailing-list',\n 'email-signup',\n 'subscribe-form',\n 'signup-form',\n 'related-posts',\n 'related-post',\n 'read-next',\n 'more-from',\n 'you-might-also',\n 'recommended-posts',\n 'recommended',\n];\n\nfunction signatureMatches(el: Element): boolean {\n const signature =\n `${el.getAttribute('class') ?? ''} ${el.getAttribute('id') ?? ''}`.toLowerCase();\n return BOILERPLATE_TOKENS.some(token => signature.includes(token));\n}\n\n// A boilerplate block is always a structural CONTAINER. A heading or paragraph\n// can carry a token in its `id` (an anchor target like `id=\"related-posts\"`) or\n// `class`, and individually such a leaf passes the footprint guard — so without\n// a container restriction the rule deletes real article content (e.g. an in-body\n// section heading whose id happens to match). Reject non-containers early.\nconst BOILERPLATE_CONTAINER_TAGS = new Set([\n 'ASIDE',\n 'DIV',\n 'FORM',\n 'NAV',\n 'OL',\n 'SECTION',\n 'UL',\n]);\n\n// Strips \"related posts\" / newsletter signup / \"read next\" blocks Readability\n// sometimes retains. Subtractive and high-risk, so a token hit alone is never\n// enough: the footprint guard requires the candidate to be a small fraction of\n// the body. The body length is snapshotted once so every candidate is judged\n// against the same baseline — recomputing after each removal would make the\n// threshold depend on iteration order. A boilerplate block is always a small\n// slice of a real article; capping at 25% guarantees the worst case is leaving\n// a small block in, never deleting a section that is itself a substantial part\n// of the content. Idempotent and order-independent.\n//\n// Only outermost boilerplate roots are candidates. A single signature CONTAINER\n// (e.g. <aside class=\"newsletter-subscribe\">) puts the token on every descendant\n// (\"newsletter-title\", \"newsletter-submit\", …); stripping those leaves\n// independently mangles the container when the footprint guard preserves the\n// root — a newsletter form shorn of its title and submit button. The ancestor\n// walk rejects any element whose class+id already matched on an ancestor.\n// querySelectorAll yields outer-before-inner order, and removing a root detaches\n// its descendants (caught by !isConnected above), so this walk uniformly rejects\n// leaves of both removed roots and preserved roots.\nexport function stripBoilerplate(document: Document): number {\n const bodyLength = document.body.textContent.length;\n const limit = 0.25 * bodyLength;\n\n let removed = 0;\n for (const el of document.querySelectorAll('[class],[id]')) {\n if (!el.isConnected) {\n continue;\n }\n if (!BOILERPLATE_CONTAINER_TAGS.has(el.tagName)) {\n continue;\n }\n if (!signatureMatches(el)) {\n continue;\n }\n let ancestor: Element | null = el.parentElement;\n while (ancestor && !signatureMatches(ancestor)) {\n ancestor = ancestor.parentElement;\n }\n if (ancestor) {\n continue;\n }\n if (el.textContent.length >= limit) {\n continue;\n }\n el.remove();\n removed++;\n }\n return removed;\n}\n\nexport interface SelectorScope {\n readonly exclude?: readonly string[];\n readonly include?: string;\n}\n\n// Scope the document by CSS selector: drop every `exclude` match, then (if\n// `include` matches) replace the body with the first matching subtree.\nexport function applySelectors(\n document: Document,\n selectors: SelectorScope | undefined,\n): void {\n if (!selectors) {\n return;\n }\n if (selectors.exclude) {\n for (const selector of selectors.exclude) {\n document.querySelectorAll(selector).forEach(el => {\n el.remove();\n });\n }\n }\n if (selectors.include) {\n const body = document.body;\n const root = body.querySelector(selectors.include);\n if (root && root !== body) {\n body.innerHTML = '';\n body.appendChild(root);\n }\n }\n}\n\n// Matched anywhere in a src (case-insensitive) to flag a lazy-load placeholder.\nconst PLACEHOLDER_TOKENS = [\n 'placeholder',\n 'blank',\n 'spacer',\n 'lazy',\n 'loading',\n '1x1',\n 'transparent',\n 'pixel',\n 'dummy',\n];\n\nexport function isPlaceholderSrc(src: string): boolean {\n if (!src) {\n return true;\n }\n if (src.startsWith('data:')) {\n return true;\n }\n const lowered = src.toLowerCase();\n return PLACEHOLDER_TOKENS.some(token => lowered.includes(token));\n}\n\nfunction usableAttr(el: Element, names: readonly string[]): string | undefined {\n for (const name of names) {\n const value = el.getAttribute(name);\n if (value) {\n return value;\n }\n }\n return undefined;\n}\n\n// Largest-descriptor candidate wins (`Nw`/`Nx`, never mixed in valid srcset);\n// first candidate wins when none carry a descriptor. Malformed entries are skipped.\nfunction pickLargestSrcset(srcset: string): string | undefined {\n let bestUrl: string | undefined;\n let bestValue = -1;\n let firstUrl: string | undefined;\n let sawDescriptor = false;\n for (const raw of srcset.split(',')) {\n const entry = raw.trim();\n if (!entry) {\n continue;\n }\n const splitAt = entry.search(/\\s/);\n const rawUrl = splitAt === -1 ? entry : entry.slice(0, splitAt);\n const descriptor = splitAt === -1 ? '' : entry.slice(splitAt).trim();\n const url =\n rawUrl.length >= 2 &&\n ((rawUrl.startsWith('\"') && rawUrl.endsWith('\"')) ||\n (rawUrl.startsWith(\"'\") && rawUrl.endsWith(\"'\")))\n ? rawUrl.slice(1, -1)\n : rawUrl;\n if (!url) {\n continue;\n }\n if (firstUrl === undefined) {\n firstUrl = url;\n }\n if (!descriptor) {\n continue;\n }\n const value = /^(\\d+(?:\\.\\d+)?)[wx]$/.exec(descriptor);\n if (!value) {\n continue;\n }\n sawDescriptor = true;\n const numeric = Number.parseFloat(value[1]);\n if (numeric > bestValue) {\n bestValue = numeric;\n bestUrl = url;\n }\n }\n return sawDescriptor ? bestUrl : firstUrl;\n}\n\n// Skip media-constrained <source>: art-direction crops for specific viewports,\n// not the default image the page shows.\nfunction resolveFromPicture(img: HTMLImageElement): string | undefined {\n const picture = img.closest('picture');\n if (!picture) {\n return undefined;\n }\n for (const source of picture.querySelectorAll('source')) {\n if (source.hasAttribute('media')) {\n continue;\n }\n const srcset = source.getAttribute('srcset');\n if (srcset) {\n const url = pickLargestSrcset(srcset);\n if (url) {\n return url;\n }\n }\n }\n return undefined;\n}\n\n// Precedence (first usable wins): data-src → <picture><source srcset> → own\n// srcset (largest) → data-original → data-lazy-src.\nfunction resolveRealSource(img: HTMLImageElement): string | undefined {\n const dataSrc = usableAttr(img, ['data-src']);\n if (dataSrc) {\n return dataSrc;\n }\n const fromPicture = resolveFromPicture(img);\n if (fromPicture) {\n return fromPicture;\n }\n const ownSrcset = img.getAttribute('srcset');\n if (ownSrcset) {\n const url = pickLargestSrcset(ownSrcset);\n if (url) {\n return url;\n }\n }\n return usableAttr(img, ['data-original', 'data-lazy-src']);\n}\n\n// Swap placeholder src values for the real source before Readability clones.\n// Only a placeholder-like current src is rewritten, so a real src is left alone\n// even if a data-src also exists. Idempotent.\nexport function resolveLazyImages(document: Document): number {\n let resolved = 0;\n for (const img of document.querySelectorAll('img')) {\n const currentSrc = img.getAttribute('src') ?? '';\n if (!isPlaceholderSrc(currentSrc)) {\n continue;\n }\n const real = resolveRealSource(img);\n if (real && real !== currentSrc) {\n img.setAttribute('src', real);\n resolved++;\n }\n }\n return resolved;\n}\n\n// Readability keeps a class only by literal `classesToPreserve.includes(cls)`\n// equality, so non-canonical code-block conventions — GitHub's\n// `highlight-source-js`, sandpack's `sp-javascript`, SyntaxHighlighter's\n// `brush: js` — are stripped before turndown runs and the fence loses its\n// language tag. Rewrite each convention to the canonical\n// `<code class=\"language-X\">` form before Readability clones the document.\nconst HIGHLIGHT_SOURCE_PREFIX = 'highlight-source-';\nconst LANGUAGE_PREFIX = 'language-';\nconst LANG_PREFIX = 'lang-';\nconst SP_PREFIX = 'sp-';\nconst BRUSH_RE = /brush:\\s*([A-Za-z][\\w-]*)/;\nconst BOGUS_TOKENS = new Set(['', 'highlight', 'source', 'sp']);\n\ninterface CodeToken {\n readonly fromHighlightSource: boolean;\n readonly token: string;\n}\n\nfunction isValidToken(token: string): boolean {\n return !BOGUS_TOKENS.has(token);\n}\n\nfunction hasLanguageClass(el: Element): boolean {\n for (const cls of el.classList) {\n if (cls !== LANGUAGE_PREFIX && cls.startsWith(LANGUAGE_PREFIX)) {\n return true;\n }\n }\n return false;\n}\n\ninterface ClassSource {\n readonly classes: readonly string[];\n readonly raw: string;\n}\n\n// Pool the <pre>'s own classes, then ancestors up to the highlight wrapper\n// (capped at two levels), then any descendant <code>'s classes — in that order.\nfunction collectClassSources(\n pre: Element,\n code: Element | null,\n): ClassSource[] {\n const sources: ClassSource[] = [\n { classes: [...pre.classList], raw: pre.getAttribute('class') ?? '' },\n ];\n let depth = 0;\n let el: Element | null = pre.parentElement;\n while (el && depth < 2) {\n sources.push({\n classes: [...el.classList],\n raw: el.getAttribute('class') ?? '',\n });\n depth++;\n if (\n el.tagName === 'DIV' &&\n [...el.classList].some(c => c.startsWith('highlight'))\n ) {\n break;\n }\n el = el.parentElement;\n }\n if (code) {\n sources.push({\n classes: [...code.classList],\n raw: code.getAttribute('class') ?? '',\n });\n }\n return sources;\n}\n\nfunction resolveCodeToken(\n pre: Element,\n code: Element | null,\n): CodeToken | null {\n const sources = collectClassSources(pre, code);\n\n function findByPrefix(prefix: string): null | string {\n for (const src of sources) {\n for (const cls of src.classes) {\n if (cls.startsWith(prefix)) {\n const token = cls.slice(prefix.length).toLowerCase();\n if (isValidToken(token)) {\n return token;\n }\n }\n }\n }\n return null;\n }\n\n // Convention priority: highlight-source > language > lang > sp > brush.\n // `brush:` is a class-attribute value, not a single class token, so it cannot\n // be matched by prefix and is parsed from the raw attribute string instead.\n const highlightToken = findByPrefix(HIGHLIGHT_SOURCE_PREFIX);\n if (highlightToken) {\n return { token: highlightToken, fromHighlightSource: true };\n }\n const languageToken = findByPrefix(LANGUAGE_PREFIX);\n if (languageToken) {\n return { token: languageToken, fromHighlightSource: false };\n }\n const langToken = findByPrefix(LANG_PREFIX);\n if (langToken) {\n return { token: langToken, fromHighlightSource: false };\n }\n // Sandpack emits infra classes alongside the language class (sp-cm,\n // sp-pristine, sp-pre-placeholder); unlike the single-token highlight-source\n // / language / lang conventions, SP must pick the class whose token is a\n // recognized language or it grabs an infra string the preserve list strips.\n for (const src of sources) {\n for (const cls of src.classes) {\n if (!cls.startsWith(SP_PREFIX)) {\n continue;\n }\n const token = cls.slice(SP_PREFIX.length).toLowerCase();\n if (KNOWN_LANGUAGE_TOKENS.has(token)) {\n return { token, fromHighlightSource: false };\n }\n }\n }\n for (const src of sources) {\n const match = BRUSH_RE.exec(src.raw);\n if (match) {\n const token = match[1].toLowerCase();\n if (isValidToken(token)) {\n return { token, fromHighlightSource: false };\n }\n }\n }\n return null;\n}\n\nfunction canonicalizePre(pre: Element): boolean {\n const code = pre.querySelector('code');\n // turndown reads the language from the <code> class; if one is present the\n // block is already canonical and rewriting it can only drop information.\n if (code && hasLanguageClass(code)) {\n return false;\n }\n const match = resolveCodeToken(pre, code);\n if (!match) {\n return false;\n }\n\n let target = code;\n if (!target) {\n target = pre.ownerDocument.createElement('code');\n while (pre.firstChild) {\n target.appendChild(pre.firstChild);\n }\n pre.appendChild(target);\n }\n\n const classes = [`language-${match.token}`];\n if (target.classList.contains('hljs')) {\n classes.push('hljs');\n }\n target.setAttribute('class', classes.join(' '));\n\n // GitHub carries the language on a <div class=\"highlight\"> wrapper rather than\n // the <pre>; once that hint is moved onto <code>, hoist the <pre> out so the\n // block stands alone instead of inside a div Readability scores separately.\n if (match.fromHighlightSource) {\n const parent = pre.parentElement;\n if (\n parent?.tagName === 'DIV' &&\n [...parent.classList].some(c => c.startsWith('highlight'))\n ) {\n parent.replaceWith(pre);\n }\n }\n return true;\n}\n\nexport function canonicalizeCodeBlocks(document: Document): number {\n let count = 0;\n for (const pre of document.querySelectorAll('pre')) {\n if (!pre.isConnected) {\n continue;\n }\n try {\n if (canonicalizePre(pre)) {\n count++;\n }\n } catch {\n // Defensive: malformed markup must not break the extract pipeline.\n }\n }\n return count;\n}\n","import { isProbablyReaderable, Readability } from '@mozilla/readability';\n\nexport interface ReadabilityParseResult {\n readonly byline?: null | string;\n readonly content?: null | string;\n readonly dir?: null | string;\n readonly excerpt?: null | string;\n readonly lang?: null | string;\n readonly length?: null | number;\n readonly publishedTime?: null | string;\n readonly siteName?: null | string;\n readonly textContent?: null | string;\n readonly title?: null | string;\n}\n\nexport interface ReadabilityOptions {\n readonly charThreshold?: number;\n readonly keepClasses?: boolean;\n readonly maxElemsToParse?: number;\n readonly nbTopCandidates?: number;\n readonly [key: string]: unknown;\n}\n\nexport function isReaderable(document: Document): boolean {\n return isProbablyReaderable(document);\n}\n\nexport function parseArticle(\n document: Document,\n options?: Readonly<ReadabilityOptions>,\n): null | ReadabilityParseResult {\n // Readability mutates its input document; clone so the pipeline's doc is untouched.\n const clone = document.cloneNode(true) as Document;\n const reader = new Readability(clone, options);\n return reader.parse();\n}\n","import type {\n CacheSignal,\n Diagnostics,\n SanitizationDiagnostics,\n TraceStage,\n} from '../pipeline/context.js';\nimport type { GatingSignal } from './gating.js';\nimport type { PaginationSignal } from './pagination.js';\n\nexport interface DiagnosticsInput {\n readonly articleHtml?: string;\n readonly boilerplateRemoved?: number;\n readonly cache?: CacheSignal;\n readonly chromeRemoved?: number;\n readonly document?: Document;\n readonly documentElementCount?: number;\n readonly extractedNode?: string;\n readonly fallbackUsed?: boolean;\n readonly gated?: GatingSignal;\n readonly imagesResolved?: number;\n readonly pagination?: PaginationSignal;\n readonly readerable?: boolean;\n readonly sanitization?: SanitizationDiagnostics;\n readonly trace?: readonly TraceStage[];\n readonly truncated?: boolean;\n readonly window?: Window;\n}\n\nfunction countElements(html: string, window?: Window): number {\n if (!window || !html) {\n return 0;\n }\n const template = window.document.createElement('div');\n template.innerHTML = html;\n return template.querySelectorAll('*').length;\n}\n\nexport function assembleDiagnostics(\n input: Readonly<DiagnosticsInput>,\n): Diagnostics {\n const articleElementCount = countElements(\n input.articleHtml ?? '',\n input.window,\n );\n const removedNodes = Math.max(\n 0,\n (input.documentElementCount ?? 0) - articleElementCount,\n );\n\n return {\n readerable: input.readerable,\n extractedNode: input.extractedNode,\n fallbackUsed: input.fallbackUsed ?? false,\n gated: input.gated,\n imagesResolved: input.imagesResolved,\n pagination: input.pagination,\n removedNodes,\n boilerplateRemoved: input.boilerplateRemoved,\n chromeRemoved: input.chromeRemoved,\n sanitization: input.sanitization,\n trace: input.trace,\n truncated: input.truncated ?? false,\n ...(input.cache ? { cache: input.cache } : {}),\n };\n}\n\n// Emits trace entries only when enabled, so the debug-off hot path allocates\n// nothing (the fn runs bare). Used by the orchestrator tools to time pipeline\n// stages without threading timers into lower layers.\nexport class TraceCollector {\n private readonly enabled: boolean;\n private readonly entries: TraceStage[] = [];\n\n constructor(enabled: boolean) {\n this.enabled = enabled;\n }\n\n collect(): readonly TraceStage[] | undefined {\n return this.enabled ? this.entries : undefined;\n }\n\n run<T>(stage: string, fn: () => T): T {\n if (!this.enabled) {\n return fn();\n }\n const start = performance.now();\n try {\n return fn();\n } finally {\n this.entries.push({ ms: performance.now() - start, stage });\n }\n }\n}\n","export interface GatingSignal {\n readonly likely: boolean;\n readonly reason: string;\n}\n\n// Curated vendor/structural paywall selectors — intentionally NOT a greedy\n// `[class*=\"subscribe\"]` (that catches newsletter CTAs on clean articles and\n// false-positives). Each entry names a known paywall surface.\nconst PAYWALL_SELECTORS = [\n '[class*=\"paywall\"]',\n '[id*=\"paywall\"]',\n '.piano',\n '#piano',\n '.tp-modal',\n '.tp-active',\n '[id*=\"piano\"]',\n '[class*=\"piano\"]',\n '[class*=\"subscribe-wall\"]',\n '[id*=\"subscribe-wall\"]',\n '[class*=\"metered-wall\"]',\n '[id*=\"metered-wall\"]',\n '.leaky-paywall',\n] as const;\n\n// Phrases that essentially never appear on a fully-unlocked article. Bare\n// \"Subscribe\" nav links / newsletter CTAs are intentionally excluded — they\n// are ubiquitous and would mislead the host into discarding complete content.\nconst METERED_TEXT_RE =\n /(\\d+)\\s*(?:free\\s*)?(?:articles?|stories?)\\s*(?:left|remaining)|you\\s+have\\s+reached\\s+(?:your\\s+)?(?:free\\s+)?(?:article\\s+|story\\s+)?limit|subscribe\\s+to\\s+(?:continue\\s+)?reading|read\\s+the\\s+full\\s+(?:article|story)|unlock\\s+(?:this|full|all)\\s+(?:article|story|content)|keep\\s+reading\\s+with/i;\n\nfunction findPaywallOverlay(document: Document): GatingSignal | undefined {\n for (const selector of PAYWALL_SELECTORS) {\n const el = document.querySelector(selector);\n if (el?.isConnected) {\n return { likely: true, reason: 'paywall overlay' };\n }\n }\n return undefined;\n}\n\n// One textContent read is cheaper than a per-element scan of a large doc and\n// avoids ordering a query per selector. jsdom computes body.textContent once.\nfunction findMeteredMessage(document: Document): GatingSignal | undefined {\n const text = document.body.textContent;\n if (METERED_TEXT_RE.test(text)) {\n return { likely: true, reason: 'metered paywall message' };\n }\n return undefined;\n}\n\n// Reads the DOM only — never fetches, authenticates, or mutates the document.\n// Conservative by design: a false positive misleads the host into treating\n// complete content as truncated, which is worse than a miss.\nexport function detectGating(document: Document): GatingSignal | undefined {\n return findPaywallOverlay(document) ?? findMeteredMessage(document);\n}\n","// Shared by the turndown rules (image/anchor absolutization) and the\n// extract_links tool. Returns the input unchanged when either side is missing\n// or the URL constructor rejects the pair, so callers never throw on bad input.\nexport function absolutize(src: string, baseUrl: string | undefined): string {\n if (!src || !baseUrl) {\n return src;\n }\n try {\n return new URL(src, baseUrl).href;\n } catch {\n return src;\n }\n}\n","import { absolutize } from '../pipeline/urls.js';\n\nexport interface PaginationSignal {\n readonly nextUrl?: string;\n readonly selector?: string;\n readonly type: 'infinite' | 'paginated';\n}\n\n// Short pagination-style link text only; matching prose like \"next, we cover\n// rendering\" would burn an article's internal link as a next-page signal.\n// `next ›/»/→` and bare arrows cover the common next-page glyphs in one place.\nconst NEXT_LINK_TEXT_RE =\n /^(next(\\s+page)?|older(\\s+posts?)?|[›»→]|next\\s*[›»→]|older\\s*[›»→])$/i;\n\nfunction usableHref(href: null | string): string | undefined {\n if (!href || href === '#') {\n return undefined;\n }\n return href;\n}\n\nfunction findPaginated(\n document: Document,\n baseUrl: string | undefined,\n): PaginationSignal | undefined {\n const linkNext = document.querySelector('link[rel=\"next\"][href]');\n if (linkNext) {\n const href = usableHref(linkNext.getAttribute('href'));\n if (href) {\n return { type: 'paginated', nextUrl: absolutize(href, baseUrl) };\n }\n }\n\n const aRelNext = document.querySelector('a[rel=\"next\"][href]');\n if (aRelNext) {\n const href = usableHref(aRelNext.getAttribute('href'));\n if (href) {\n return { type: 'paginated', nextUrl: absolutize(href, baseUrl) };\n }\n }\n\n for (const anchor of document.querySelectorAll('a[href]')) {\n const text = anchor.textContent.trim();\n if (!text || !NEXT_LINK_TEXT_RE.test(text)) {\n continue;\n }\n const href = usableHref(anchor.getAttribute('href'));\n if (!href) {\n continue;\n }\n return { type: 'paginated', nextUrl: absolutize(href, baseUrl) };\n }\n\n return undefined;\n}\n\nconst INFINITE_ATTR_SELECTORS = [\n '[data-load-more]',\n '[data-infinite-scroll]',\n '[data-pagination]',\n '[infinite-scroll]',\n] as const;\n\nconst INFINITE_SUBSTRING_SELECTORS = [\n '[class*=\"load-more\"]',\n '[class*=\"loadmore\"]',\n '[class*=\"infinite\"]',\n '[id*=\"load-more\"]',\n '[class*=\"sentinel\"]',\n] as const;\n\nconst LOAD_MORE_BUTTON_RE =\n /^(load more|show more|view more|more results|load more comments)$/i;\n\nfunction findInfinite(document: Document): PaginationSignal | undefined {\n for (const selector of INFINITE_ATTR_SELECTORS) {\n const el = document.querySelector(selector);\n if (el?.isConnected) {\n return { selector, type: 'infinite' };\n }\n }\n for (const selector of INFINITE_SUBSTRING_SELECTORS) {\n const el = document.querySelector(selector);\n if (el?.isConnected) {\n return { selector, type: 'infinite' };\n }\n }\n for (const button of document.querySelectorAll('button')) {\n const text = button.textContent.trim();\n if (text && LOAD_MORE_BUTTON_RE.test(text)) {\n return { selector: 'button', type: 'infinite' };\n }\n }\n return undefined;\n}\n\n// Paginated beats infinite: an explicit \"next\" link is a stronger signal than\n// a load-more sentinel. Read-only — never follows or fetches.\nexport function detectPagination(\n document: Document,\n baseUrl?: string,\n): PaginationSignal | undefined {\n return findPaginated(document, baseUrl) ?? findInfinite(document);\n}\n","import { readFileSync } from 'node:fs';\n\nexport function readHtmlFile(localPath: string): string {\n const raw = readFileSync(localPath, 'utf8');\n const trimmed = raw.trim();\n // A top-level JSON string scalar is a chrome-devtools capture (its JSON-encoded\n // return value written to disk), not raw markup — unwrap it before parsing.\n if (trimmed.length < 2 || trimmed.at(0) !== '\"' || trimmed.at(-1) !== '\"') {\n return raw;\n }\n try {\n const parsed: unknown = JSON.parse(trimmed);\n return typeof parsed === 'string' ? parsed : raw;\n } catch {\n return raw;\n }\n}\n","// Resolve a table/grid cell element to the string that represents it in the\n// matrix. Shared by policy/tables.ts (the <table> serializer) and\n// policy/grid-detector.ts (the div-grid detector) so both paths project a cell\n// the same way: whitespace-normalized visible text, with two fallbacks.\n//\n// 1. Tooltip/badge/aria wrappers carry chrome, not data — they are stripped\n// from a clone so a label cell reads \"Market Capitalization\" rather than\n// concatenating every descendant. But stripping must never empty a cell: a\n// badge/aria element that holds a cell's only text is data, not chrome, so\n// when stripping would blank the cell the full text is kept.\n// 2. A cell whose only content is an icon link has no visible text at all; its\n// value lives in the anchor's href, which is surfaced as a last resort.\nconst CELL_CHROME_SELECTOR =\n '[aria-label], [data-tooltip], [data-toggle=\"tooltip\"], .tooltip, .badge';\n\nfunction normalize(text: string): string {\n return text.replace(/\\s+/g, ' ').trim();\n}\n\n// All non-empty hrefs reachable from the cell, including the cell itself when it\n// is an anchor (a div-grid \"cell\" can be an <a>). Descendant-only search would\n// miss a link-cell whose element IS the anchor.\nfunction nonEmptyHrefs(cell: Element): string[] {\n const anchors: Element[] =\n cell.tagName === 'A'\n ? [cell, ...Array.from(cell.querySelectorAll('a[href]'))]\n : Array.from(cell.querySelectorAll('a[href]'));\n const hrefs: string[] = [];\n for (const a of anchors) {\n const href = (a.getAttribute('href') ?? '').trim();\n if (href !== '') {\n hrefs.push(href);\n }\n }\n return hrefs;\n}\n\nexport function resolveCellText(cell: Element): string {\n const full = normalize(cell.textContent);\n let resolved = full;\n if (full !== '') {\n const clone = cell.cloneNode(true) as Element;\n clone.querySelectorAll(CELL_CHROME_SELECTOR).forEach(el => {\n el.remove();\n });\n const stripped = normalize(clone.textContent);\n resolved = stripped !== '' ? stripped : full;\n }\n if (resolved === '') {\n const hrefs = nonEmptyHrefs(cell);\n if (hrefs.length > 0) {\n return hrefs.join(' ');\n }\n }\n return resolved;\n}\n","import { resolveCellText } from './cell-text.js';\n\nexport type TableFormat = 'csv' | 'gfm' | 'json';\n\n// `<thead>`/`<tbody>`/`<tfoot>` group `<tr>` rows; a `<table>` may also hold `<tr>`\n// directly. Walking these levels (and only these) keeps nested `<table>`s out of\n// the matrix — those are emitted as their own replacement by turndown.\nconst SECTION_TAGS = new Set(['TBODY', 'TFOOT', 'THEAD']);\nconst CELL_TAGS = new Set(['TD', 'TH']);\n\nfunction spanOf(cell: Element, attr: 'colspan' | 'rowspan'): number {\n const raw = cell.getAttribute(attr);\n if (raw === null) {\n return 1;\n }\n const parsed = Number.parseInt(raw, 10);\n // HTML treats 0 (and any non-positive / unparseable value) as 1.\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return 1;\n }\n return parsed;\n}\n\nfunction collectRows(table: Element): readonly Element[] {\n const rows: Element[] = [];\n for (const child of Array.from(table.children)) {\n if (child.tagName === 'TR') {\n rows.push(child);\n } else if (SECTION_TAGS.has(child.tagName)) {\n for (const tr of Array.from(child.children)) {\n if (tr.tagName === 'TR') {\n rows.push(tr);\n }\n }\n }\n }\n return rows;\n}\n\nfunction cellsOf(tr: Element): readonly Element[] {\n return Array.from(tr.children).filter(child => CELL_TAGS.has(child.tagName));\n}\n\n// Text projection of buildCellGrid: origin cells → resolveCellText, span/pad slots → ''.\nexport function parseTableMatrix(table: Element): string[][] {\n return buildCellGrid(table).map(row =>\n row.map(cell => (cell === null ? '' : resolveCellText(cell))),\n );\n}\n\nfunction escapeGfmCell(text: string): string {\n return text.replace(/\\\\/g, '\\\\\\\\').replace(/\\|/g, '\\\\|');\n}\n\nexport function renderTableGfm(matrix: string[][]): string {\n if (matrix.length === 0) {\n return '';\n }\n const cols = matrix[0].length;\n const lines: string[] = [];\n lines.push(`| ${matrix[0].map(escapeGfmCell).join(' | ')} |`);\n lines.push(`| ${Array.from({ length: cols }, () => '---').join(' | ')} |`);\n for (let r = 1; r < matrix.length; r++) {\n lines.push(`| ${matrix[r].map(escapeGfmCell).join(' | ')} |`);\n }\n return lines.join('\\n');\n}\n\nfunction escapeCsvField(text: string): string {\n if (/[\",\\r\\n]/.test(text)) {\n return `\"${text.replace(/\"/g, '\"\"')}\"`;\n }\n return text;\n}\n\nexport function renderTableCsv(matrix: string[][]): string {\n if (matrix.length === 0) {\n return '';\n }\n return matrix.map(row => row.map(escapeCsvField).join(',')).join('\\n');\n}\n\nfunction headerKeys(header: readonly string[]): string[] {\n return header.map((cell, i) => (cell === '' ? `column_${i}` : cell));\n}\n\n// Tracks the originating cell element at each grid position after resolving\n// rowspan/colspan, so resolveHeaderKeys can read td/th attributes the text\n// matrix discards. parseTableMatrix is the text projection of this grid; `null`\n// marks positions filled by a span (no origin cell).\nfunction buildCellGrid(table: Element): (Element | null)[][] {\n const rows = collectRows(table);\n if (rows.length === 0) {\n return [];\n }\n const grid: (Element | null)[][] = [];\n const occupied: boolean[][] = [];\n let maxCols = 0;\n\n for (let r = 0; r < rows.length; r++) {\n while (grid.length <= r) {\n grid.push([]);\n occupied.push([]);\n }\n const rowCells = grid[r];\n const rowOccupied = occupied[r];\n\n let col = 0;\n for (const cell of cellsOf(rows[r])) {\n while (rowOccupied[col]) {\n col++;\n }\n const rowspan = spanOf(cell, 'rowspan');\n const colspan = spanOf(cell, 'colspan');\n rowCells[col] = cell;\n rowOccupied[col] = true;\n for (let dr = 0; dr < rowspan; dr++) {\n for (let dc = 0; dc < colspan; dc++) {\n if (dr === 0 && dc === 0) {\n continue;\n }\n const rr = r + dr;\n while (grid.length <= rr) {\n grid.push([]);\n occupied.push([]);\n }\n const occ = occupied[rr];\n while (occ.length <= col + dc) {\n occ.push(false);\n grid[rr].push(null);\n }\n occ[col + dc] = true;\n }\n }\n col += colspan;\n if (col > maxCols) {\n maxCols = col;\n }\n }\n }\n\n const dense: (Element | null)[][] = [];\n for (const row of grid) {\n dense.push(Array.from({ length: maxCols }, (_, i) => row[i] ?? null));\n }\n return dense;\n}\n\nfunction isHeaderRow(tr: Element): boolean {\n const cells = cellsOf(tr);\n return cells.length > 0 && cells.every(c => c.tagName === 'TH');\n}\n\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '');\n}\n\nfunction readCellLabel(cell: Element): null | string {\n return (\n cell.getAttribute('aria-label') ??\n cell.getAttribute('title') ??\n cell.getAttribute('data-label')\n );\n}\n\nexport function resolveHeaderKeys(\n table: Element,\n matrix: string[][],\n): string[] {\n if (matrix.length === 0) {\n return [];\n }\n const width = matrix[0].length;\n const keys = headerKeys(matrix[0]);\n\n const rows = collectRows(table);\n if (rows.length === 0) {\n return keys;\n }\n\n const cellGrid = buildCellGrid(table);\n\n // Headerless columns lose their name without a th; recover from a colspan\n // parent in row 0, falling back to the position under that parent.\n const parents: string[] = Array.from({ length: width }, () => '');\n const parentChildren = new Map<string, number[]>();\n {\n let col = 0;\n for (const cell of cellsOf(rows[0])) {\n const colspan = spanOf(cell, 'colspan');\n const text = resolveCellText(cell);\n if (colspan > 1 && text) {\n const slug = slugify(text);\n const kids: number[] = [];\n for (let dc = 0; dc < colspan && col + dc < width; dc++) {\n parents[col + dc] = slug;\n kids.push(col + dc);\n }\n parentChildren.set(slug, kids);\n }\n col += colspan;\n }\n }\n\n const hasSubRow = rows.length > 1 && isHeaderRow(rows[1]);\n\n for (let c = 0; c < width; c++) {\n const dataCell = cellGrid[1]?.[c];\n if (dataCell) {\n const label = readCellLabel(dataCell);\n if (label) {\n keys[c] = label;\n continue;\n }\n }\n\n const parent = parents[c];\n if (!parent) {\n continue;\n }\n\n if (hasSubRow) {\n const sub = matrix[1][c] ? slugify(matrix[1][c]) : '';\n if (sub) {\n keys[c] = `${parent}_${sub}`;\n continue;\n }\n }\n\n if (matrix[0][c] === '') {\n const kids = parentChildren.get(parent);\n const idx = kids ? kids.indexOf(c) + 1 : c;\n keys[c] = `${parent}_${idx}`;\n }\n }\n return keys;\n}\n\nfunction resolveJsonKeys(\n matrix: string[][],\n keys: readonly string[] | undefined,\n): string[] {\n const width = matrix[0].length;\n if (!keys) {\n return headerKeys(matrix[0]);\n }\n return Array.from({ length: width }, (_, i) => keys[i] ?? `column_${i}`);\n}\n\nexport function renderTableJson(\n matrix: string[][],\n keys?: readonly string[],\n): string {\n if (matrix.length < 2) {\n return '[]';\n }\n const resolved = resolveJsonKeys(matrix, keys);\n const records: Record<string, string>[] = [];\n for (let r = 1; r < matrix.length; r++) {\n const row = matrix[r];\n const record: Record<string, string> = {};\n for (let c = 0; c < resolved.length; c++) {\n record[resolved[c]] = row[c] ?? '';\n }\n records.push(record);\n }\n return JSON.stringify(records, null, 2);\n}\n\nexport function renderTable(\n matrix: string[][],\n format: TableFormat,\n keys?: readonly string[],\n): string {\n switch (format) {\n case 'csv':\n return renderTableCsv(matrix);\n case 'gfm':\n return renderTableGfm(matrix);\n case 'json':\n return renderTableJson(matrix, keys);\n }\n}\n","export const TOKEN_ESTIMATOR = 'chars/4';\n\nexport function estimateTokens(textContent: string): {\n estimator: string;\n tokenEstimate: number;\n} {\n return {\n tokenEstimate: Math.round(textContent.length / 4),\n estimator: TOKEN_ESTIMATOR,\n };\n}\n\nexport function countWords(text: string): number {\n return (text.match(/\\S+/g) ?? []).length;\n}\n\nexport function nonEmpty(value: string | undefined): string | undefined {\n return value?.trim() ? value : undefined;\n}\n\nexport interface TextMetrics {\n readonly estimator: string;\n readonly readingTimeMin: number;\n readonly tokenEstimate: number;\n readonly wordCount: number;\n}\n\nexport function computeTextMetrics(\n text: string,\n wordsPerMinute: number,\n): TextMetrics {\n const wordCount = countWords(text);\n const readingTimeMin =\n wordCount === 0 ? 0 : Math.max(1, Math.round(wordCount / wordsPerMinute));\n return { wordCount, readingTimeMin, ...estimateTokens(text) };\n}\n","import type { Metadata, StructuredData } from '../pipeline/context.js';\nimport type { ReadabilityParseResult } from '../pipeline/readability.js';\n\nimport { estimateTokens, nonEmpty } from './text.js';\n\nconst ARTICLE_TYPES = new Set([\n 'Article',\n 'BlogPosting',\n 'NewsArticle',\n 'Report',\n 'ScholarlyArticle',\n 'SocialMediaPosting',\n 'TechArticle',\n 'WebPage',\n]);\n\ntype JsonLdValue =\n boolean | JsonLdObject | JsonLdValue[] | null | number | string;\ninterface JsonLdObject {\n readonly [key: string]: JsonLdValue;\n}\n\nfunction first(...values: readonly (string | undefined)[]): string | undefined {\n for (const value of values) {\n const picked = nonEmpty(value);\n if (picked) {\n return picked;\n }\n }\n return undefined;\n}\n\nfunction metaProperty(\n document: Document,\n property: string,\n): string | undefined {\n return nonEmpty(\n document\n .querySelector(`meta[property=\"${property}\"]`)\n ?.getAttribute('content') ?? undefined,\n );\n}\n\nfunction metaName(document: Document, name: string): string | undefined {\n return nonEmpty(\n document.querySelector(`meta[name=\"${name}\"]`)?.getAttribute('content') ??\n undefined,\n );\n}\n\n// Flatten ld+json scripts (a script may hold an array or a @graph array).\n// Invalid JSON is skipped — never crash extraction over malformed metadata.\nfunction parseJsonLd(document: Document): JsonLdObject[] {\n const nodes = document.querySelectorAll('script[type=\"application/ld+json\"]');\n const out: JsonLdObject[] = [];\n nodes.forEach(node => {\n const raw = node.textContent;\n if (!raw.trim()) {\n return;\n }\n let parsed: JsonLdValue;\n try {\n parsed = JSON.parse(raw) as JsonLdValue;\n } catch {\n return;\n }\n collectObjects(parsed, out);\n });\n return out;\n}\n\nfunction collectObjects(value: JsonLdValue, out: JsonLdObject[]): void {\n if (!value || typeof value !== 'object') {\n return;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n collectObjects(item, out);\n }\n return;\n }\n // `in` guard: the index signature types values as JsonLdValue (no undefined),\n // so a `!== undefined` check has no overlap.\n if ('@graph' in value) {\n collectObjects(value['@graph'], out);\n }\n out.push(value);\n}\n\nfunction typeMatches(type: JsonLdValue): boolean {\n const types = Array.isArray(type) ? type : [type];\n return types.some(t => typeof t === 'string' && ARTICLE_TYPES.has(t));\n}\n\nfunction pickArticleNode(\n candidates: readonly JsonLdObject[],\n): JsonLdObject | undefined {\n if (candidates.length === 0) {\n return undefined;\n }\n return candidates.find(node => typeMatches(node['@type'])) ?? candidates[0];\n}\n\n// Preference order for the primary structured object: richest schema.org\n// main-entity first. Article types are the tail fallback — Readability already\n// captured the article body, so they are low-value-but-recognizable. Generic\n// graph scaffolding (WebSite/Organization/BreadcrumbList) is intentionally NOT\n// listed: emitting it as `structured` would be noise, and we prefer undefined\n// over a misleading trivial node.\nconst STRUCTURED_PRIORITY = [\n 'Recipe',\n 'Product',\n 'Event',\n 'HowTo',\n 'Course',\n 'Movie',\n 'Book',\n 'MusicRecording',\n 'JobPosting',\n 'FAQPage',\n ...ARTICLE_TYPES,\n] as const;\n\nfunction nodeTypeList(node: JsonLdObject): readonly string[] {\n const type = node['@type'];\n if (Array.isArray(type)) {\n return type.filter((t): t is string => typeof t === 'string');\n }\n return typeof type === 'string' ? [type] : [];\n}\n\n// Iterate priority types in order so a Recipe wins over an earlier Article\n// node, and document order wins among candidates of the same type.\nfunction pickStructuredObject(\n candidates: readonly JsonLdObject[],\n): JsonLdObject | undefined {\n for (const priorityType of STRUCTURED_PRIORITY) {\n const hit = candidates.find(node =>\n nodeTypeList(node).includes(priorityType),\n );\n if (hit) {\n return hit;\n }\n }\n return undefined;\n}\n\n// Strip @context (always https://schema.org, pure noise) and normalize a\n// multi-typed node to a \"+\"-joined string so the host sees a single type label.\n// Data fields are kept verbatim — the host wants the real schema.org object.\nfunction cleanStructured(obj: JsonLdObject): StructuredData {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (key === '@context') {\n continue;\n }\n if (key === '@type') {\n const types = Array.isArray(value)\n ? value.filter((t): t is string => typeof t === 'string')\n : typeof value === 'string'\n ? [value]\n : [];\n out[key] = types.join('+');\n continue;\n }\n out[key] = value;\n }\n return out;\n}\n\n// Author may be a string, a Person/Organization object, an array, or a\n// {@list: [...]} container; reduce to a comma-joined byline.\nfunction resolveJsonLdAuthor(author: JsonLdValue): string | undefined {\n const names: string[] = [];\n function visit(value: JsonLdValue): void {\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed) {\n names.push(trimmed);\n }\n return;\n }\n if (!value || typeof value !== 'object') {\n return;\n }\n if (Array.isArray(value)) {\n value.forEach(visit);\n return;\n }\n // {@list: [...]} is an ordering wrapper; unwrap it.\n const list = value['@list'];\n if (Array.isArray(list)) {\n list.forEach(visit);\n return;\n }\n const name = value.name;\n if (typeof name === 'string') {\n const trimmed = name.trim();\n if (trimmed) {\n names.push(trimmed);\n }\n }\n }\n visit(author);\n return names.length > 0 ? names.join(', ') : undefined;\n}\n\nfunction asString(value: JsonLdValue | undefined): string | undefined {\n return typeof value === 'string' ? nonEmpty(value) : undefined;\n}\n\nfunction field(\n obj: JsonLdValue | undefined,\n key: string,\n): JsonLdValue | undefined {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n return undefined;\n }\n return obj[key];\n}\n\nexport interface MetadataInput {\n readonly baseUrl?: string;\n readonly document: Document;\n readonly readability?: null | ReadabilityParseResult;\n readonly readingTimeMin: number;\n readonly textContent: string;\n readonly wordCount: number;\n}\n\nexport function resolveMetadata(input: Readonly<MetadataInput>): Metadata {\n const { document, readability } = input;\n\n const jsonLdObjects = parseJsonLd(document);\n const jsonLd = pickArticleNode(jsonLdObjects);\n const structuredRaw = pickStructuredObject(jsonLdObjects);\n const htmlLang = nonEmpty(\n document.documentElement.getAttribute('lang') ?? undefined,\n );\n const titleFromTitleTag = nonEmpty(document.title);\n\n const title = first(\n asString(jsonLd?.headline),\n metaProperty(document, 'og:title'),\n metaName(document, 'twitter:title'),\n readability?.title ?? undefined,\n titleFromTitleTag,\n );\n\n const byline = first(\n jsonLd ? resolveJsonLdAuthor(jsonLd.author) : undefined,\n metaProperty(document, 'article:author'),\n metaName(document, 'author'),\n readability?.byline ?? undefined,\n );\n\n const siteName = first(\n asString(field(field(jsonLd, 'publisher'), 'name')),\n metaProperty(document, 'og:site_name'),\n readability?.siteName ?? undefined,\n );\n\n const lang = first(\n asString(jsonLd?.inLanguage),\n htmlLang,\n readability?.lang ?? undefined,\n );\n\n const publishedTime = first(\n asString(jsonLd?.datePublished),\n metaProperty(document, 'article:published_time'),\n // First <time datetime> is the canonical publish time when rendered inline.\n nonEmpty(\n document.querySelector('time[datetime]')?.getAttribute('datetime') ??\n undefined,\n ),\n readability?.publishedTime ?? undefined,\n );\n\n const excerpt = first(\n asString(jsonLd?.description),\n metaProperty(document, 'og:description'),\n metaName(document, 'twitter:description'),\n metaName(document, 'description'),\n readability?.excerpt ?? undefined,\n );\n\n // canonical/og:url are absolute by spec; returned raw so callers can tell\n // the declared canonical apart from the input baseUrl (origin context only).\n const canonical = first(\n nonEmpty(\n document.querySelector('link[rel=\"canonical\"]')?.getAttribute('href') ??\n undefined,\n ),\n metaProperty(document, 'og:url'),\n );\n\n return {\n title,\n byline,\n siteName,\n lang,\n publishedTime,\n excerpt,\n canonical,\n baseUrl: input.baseUrl,\n wordCount: input.wordCount,\n readingTimeMin: input.readingTimeMin,\n ...estimateTokens(input.textContent),\n ...(structuredRaw ? { structured: cleanStructured(structuredRaw) } : {}),\n };\n}\n","import { stringify as stringifyYaml } from 'yaml';\n\nimport type { Diagnostics, Metadata } from '../pipeline/context.js';\n\nimport { headingText, parseBlocks } from '../policy/markdown.js';\n\nexport type Format = 'html' | 'json' | 'markdown' | 'text';\nexport type MetadataMode = 'json' | 'none' | 'yaml';\n\nexport interface FormatInput {\n readonly diagnostics: Diagnostics;\n readonly format: Format;\n readonly markdown: string;\n readonly metadata: Metadata;\n readonly metadataMode: MetadataMode;\n readonly sanitizedHtml: string;\n readonly textContent: string;\n}\n\ninterface JsonObject {\n readonly content: string;\n readonly diagnostics: Diagnostics;\n readonly metadata: Metadata;\n}\n\n// Readability demotes the article <h1> to <h2> inside `content` and mirrors its\n// text into the title; drop the echoed sub-heading so the title prints once.\nfunction dropEchoedTitle(body: string, title: string): string {\n const blocks = parseBlocks(body);\n if (blocks.length === 0) {\n return body;\n }\n const first = blocks[0];\n if (\n first.kind === 'heading' &&\n headingText(body.slice(first.start, first.end)) === title.trim()\n ) {\n return body.slice(first.end).replace(/^\\n+/, '');\n }\n return body;\n}\n\nfunction renderMarkdown(input: Readonly<FormatInput>): string {\n const title = input.metadata.title?.trim();\n let body = input.markdown;\n if (title) {\n body = dropEchoedTitle(body, title);\n return `# ${title}\\n\\n${body}`.replace(/\\n+$/, '\\n');\n }\n return body.replace(/\\n+$/, '\\n');\n}\n\n// Stable key order for deterministic frontmatter. `structured` is deliberately\n// absent: it is a nested object, not a scalar, and would dump a whole JSON-LD\n// graph into frontmatter. It reaches the host via format=json and\n// structuredContent.metadata, which serialize the full metadata object.\nconst METADATA_KEYS = [\n 'title',\n 'byline',\n 'siteName',\n 'lang',\n 'publishedTime',\n 'excerpt',\n 'canonical',\n 'baseUrl',\n 'wordCount',\n 'readingTimeMin',\n 'tokenEstimate',\n 'estimator',\n] as const satisfies readonly (keyof Metadata)[];\n\nfunction pickMetadata(\n metadata: Readonly<Metadata>,\n): Record<string, number | string> {\n const picked: Record<string, number | string> = {};\n for (const key of METADATA_KEYS) {\n const value = metadata[key];\n if (value !== undefined) {\n picked[key] = value;\n }\n }\n return picked;\n}\n\n// lineWidth: 0 disables folding so each field stays on one line.\nfunction yamlFrontmatter(metadata: Readonly<Metadata>): string {\n return `---\\n${stringifyYaml(pickMetadata(metadata), { lineWidth: 0 })}---\\n`;\n}\n\nfunction jsonFrontmatter(metadata: Readonly<Metadata>): string {\n return (\n '```json\\n' + JSON.stringify(pickMetadata(metadata), null, 2) + '\\n```\\n'\n );\n}\n\nfunction withFrontmatter(\n payload: string,\n mode: MetadataMode,\n metadata: Readonly<Metadata>,\n): string {\n if (mode === 'yaml') {\n return `${yamlFrontmatter(metadata)}${payload}`;\n }\n if (mode === 'json') {\n return `${jsonFrontmatter(metadata)}\\n${payload}`;\n }\n return payload;\n}\n\nexport function formatPayload(input: Readonly<FormatInput>): string {\n let payload: string;\n switch (input.format) {\n case 'html':\n return input.sanitizedHtml;\n case 'json': {\n const body: JsonObject = {\n metadata: input.metadata,\n content: input.markdown,\n diagnostics: input.diagnostics,\n };\n return JSON.stringify(body, null, 2);\n }\n case 'text':\n payload = input.textContent;\n break;\n case 'markdown':\n default:\n payload = renderMarkdown(input);\n break;\n }\n return withFrontmatter(payload, input.metadataMode, input.metadata);\n}\n","import DOMPurify, {\n type DOMPurify as DOMPurifyInstance,\n type WindowLike as DOMPurifyWindow,\n} from 'dompurify';\n\nexport interface SanitizeResult {\n readonly html: string;\n readonly iframesRemoved: number;\n readonly scriptsRemoved: number;\n}\n\nfunction countRemoved(\n removed: DOMPurifyInstance['removed'],\n tagName: string,\n): number {\n let count = 0;\n for (const entry of removed) {\n // RemovedAttribute entries have no `element`; count element removals only.\n if (\n 'element' in entry &&\n entry.element.nodeName.toUpperCase() === tagName\n ) {\n count += 1;\n }\n }\n return count;\n}\n\n// DOMPurify scrubs Readability's output (scripts, inline event handlers,\n// iframes) before Turndown converts it to Markdown — it is a pipeline stage,\n// not a product. A sanitized-HTML output mode was rejected: the server emits\n// Markdown, and callers wanting sanitized HTML should run DOMPurify directly.\nexport function sanitizeHtml(dirty: string, window: Window): SanitizeResult {\n // DOMPurify's WindowLike is structurally compatible at runtime but not under TS.\n const purify = DOMPurify(window as unknown as DOMPurifyWindow);\n const html = purify.sanitize(dirty);\n return {\n html,\n iframesRemoved: countRemoved(purify.removed, 'IFRAME'),\n scriptsRemoved: countRemoved(purify.removed, 'SCRIPT'),\n };\n}\n","import { buildDocument } from '../pipeline/dom.js';\n\nexport interface FootnoteResult {\n readonly footnoteDefs: readonly string[];\n readonly html: string;\n}\n\n// jsdom-parsing every turndown call would dominate hot paths; this pre-check\n// gates on a string scan so footnote-less articles pay nothing.\nconst FOOTNOTE_SIGNAL_RE =\n /cite_note|cite_ref|class=\"footnotes\"|class=\"references\"|data-footnote|role=\"doc-endnote|<sup\\b[^>]*>\\s*<a\\s[^>]*href=\"#/i;\n\n// First match wins per group; an `<ol class=\"footnotes\">` is the common case.\nconst DEFINITION_CONTAINER_SELECTORS = [\n 'ol.footnotes',\n 'ol[class*=\"footnotes\"]',\n 'ol.references',\n 'ol[class*=\"references\"]',\n 'section[class*=\"footnote\"]',\n 'div[class*=\"footnote\"]',\n '[role=\"doc-endnotes\"]',\n '[role=\"doc-bibliography\"]',\n] as const;\n\nconst STANDALONE_DEF_ID_RE = /^(?:fn|cite_note|footnote|note)[:_-]/i;\n\ninterface RefHit {\n readonly defId: string;\n readonly n: number;\n readonly sup: Element;\n}\n\n// Some sites prepend a back-to-text caret or \"Jump to\" label to each definition.\n// Stripping only a leading occurrence keeps accidental mid-text carets intact.\nconst BACKREF_LEADING_RE = /^(?:↑\\s?|↩\\s?|\\^\\s|Jump to\\s*)/;\n\nfunction cleanDefText(text: string): string {\n return text.replace(/\\s+/g, ' ').trim().replace(BACKREF_LEADING_RE, '');\n}\n\nfunction collectDefinitions(document: Document): {\n readonly containers: ReadonlySet<Element>;\n readonly defs: ReadonlyMap<string, string>;\n readonly standaloneIds: ReadonlySet<string>;\n} {\n const containers = new Set<Element>();\n const defs = new Map<string, string>();\n const standaloneIds = new Set<string>();\n\n for (const selector of DEFINITION_CONTAINER_SELECTORS) {\n let matched: NodeListOf<Element>;\n try {\n matched = document.querySelectorAll(selector);\n } catch {\n continue;\n }\n for (const container of Array.from(matched)) {\n if (!containers.has(container)) {\n containers.add(container);\n }\n for (const item of Array.from(\n container.querySelectorAll('li, [role=\"doc-endnote\"]'),\n )) {\n const id = item.id;\n if (!id || defs.has(id)) {\n continue;\n }\n const text = cleanDefText(item.textContent);\n if (text) {\n defs.set(id, text);\n }\n }\n }\n }\n\n // Some sites scatter `<li id=\"fn1\">` outside any container; collect those too.\n for (const li of Array.from(document.querySelectorAll('li[id]'))) {\n const id = li.id;\n if (!id || defs.has(id) || !STANDALONE_DEF_ID_RE.test(id)) {\n continue;\n }\n const text = cleanDefText(li.textContent);\n if (text) {\n defs.set(id, text);\n standaloneIds.add(id);\n }\n }\n\n return { containers, defs, standaloneIds };\n}\n\nexport function processFootnotes(html: string): FootnoteResult | null {\n if (!html || !FOOTNOTE_SIGNAL_RE.test(html)) {\n return null;\n }\n\n let document: Document;\n try {\n document = buildDocument(html).document;\n } catch {\n return null;\n }\n\n const { containers, defs, standaloneIds } = collectDefinitions(document);\n if (defs.size === 0) {\n return null;\n }\n\n const refHits: RefHit[] = [];\n const defIdToNumber = new Map<string, number>();\n for (const sup of Array.from(document.querySelectorAll('sup'))) {\n if (!sup.isConnected) {\n continue;\n }\n try {\n const anchor = sup.querySelector('a[href^=\"#\"]');\n if (!anchor) {\n continue;\n }\n const frag = (anchor.getAttribute('href') ?? '').slice(1);\n if (!frag || !defs.has(frag)) {\n continue;\n }\n let n = defIdToNumber.get(frag);\n if (n === undefined) {\n n = defIdToNumber.size + 1;\n defIdToNumber.set(frag, n);\n }\n refHits.push({ defId: frag, n, sup });\n } catch {\n // Defensive: malformed <sup> must not abort the whole pass.\n }\n }\n\n if (refHits.length === 0) {\n return null;\n }\n\n for (const { n, sup } of refHits) {\n try {\n sup.replaceWith(document.createTextNode(`[^${n}]`));\n } catch {\n // skip\n }\n }\n\n // Drop the rendered references list so it doesn't duplicate the appended\n // `[^N]:` block. Removing containers also takes care of the standalone <li>\n // elements that lived inside them; only out-of-container standalones need\n // individual removal here.\n for (const container of containers) {\n if (container.isConnected) {\n container.remove();\n }\n }\n for (const id of standaloneIds) {\n const el = document.getElementById(id);\n if (el?.isConnected) {\n el.remove();\n }\n }\n\n const numberToDefId = new Map<number, string>();\n for (const [id, n] of defIdToNumber) {\n numberToDefId.set(n, id);\n }\n const footnoteDefs: string[] = [];\n for (let n = 1; n <= numberToDefId.size; n++) {\n const id = numberToDefId.get(n);\n if (id === undefined) {\n break;\n }\n const text = defs.get(id);\n if (text !== undefined) {\n footnoteDefs.push(text);\n }\n }\n\n // buildDocument wraps both full documents and bare fragments in a <body>;\n // serializing its innerHTML returns the equivalent of the input region.\n return {\n footnoteDefs,\n html: document.body.innerHTML,\n };\n}\n","import TurndownService from 'turndown';\nimport { gfm } from 'turndown-plugin-gfm';\n\nimport type { TableFormat } from '../policy/tables.js';\n\nimport { processFootnotes } from '../policy/footnotes.js';\nimport { parseTableMatrix, renderTable } from '../policy/tables.js';\nimport { absolutize } from './urls.js';\n\nexport type CodeBlockStyle = 'fenced' | 'indented';\nexport type HeadingStyle = 'atx' | 'setext';\nexport type ImageMode = 'drop' | 'keep' | 'reference' | 'src-only';\nexport type { TableFormat };\n\nexport interface TurndownOptions {\n readonly baseUrl?: string;\n readonly codeBlockStyle?: CodeBlockStyle;\n readonly gfm?: boolean;\n readonly headingStyle?: HeadingStyle;\n readonly images?: ImageMode;\n readonly tables?: TableFormat;\n}\n\nexport function toMarkdown(\n html: string,\n options?: Readonly<TurndownOptions>,\n): string {\n const service = new TurndownService({\n bulletListMarker: '-',\n // Code renders inline in the Markdown as fenced blocks; normalize rewrites\n // source conventions to `<code class=\"language-X\">` first, so the fence\n // carries a language tag. A code-only extraction tool was rejected as\n // redundant surface for a niche the main path already covers.\n codeBlockStyle: options?.codeBlockStyle ?? 'fenced',\n emDelimiter: '_',\n fence: '```',\n headingStyle: options?.headingStyle ?? 'atx',\n strongDelimiter: '**',\n });\n\n if (options?.gfm !== false) {\n service.use(gfm);\n }\n\n const tableFormat = options?.tables;\n if (tableFormat !== undefined) {\n // addRule prepends, so this shadows the gfm plugin's table rule AND its\n // headerless-table keep filter (which lives in a separate _keep list, only\n // consulted when no rule in the array matches). Matching every TABLE here\n // means the keep list is never reached for tables.\n service.addRule('tableMatrix', {\n filter: node => node.nodeName === 'TABLE',\n replacement: (_content, node) => {\n const matrix = parseTableMatrix(node);\n if (matrix.length === 0) {\n return '';\n }\n const body = renderTable(matrix, tableFormat);\n // GFM rows are already markdown table syntax; csv/json need a fenced block.\n if (tableFormat === 'gfm') {\n return `\\n\\n${body}\\n\\n`;\n }\n return `\\n\\n\\`\\`\\`${tableFormat}\\n${body}\\n\\`\\`\\`\\n\\n`;\n },\n });\n }\n\n const imageMode = options?.images;\n const baseUrl = options?.baseUrl;\n // `reference` mode collects definitions as images render; append them after the pass.\n const references: string[] = [];\n applyImagePolicy(service, imageMode, baseUrl, references);\n // Readability pre-absolutizes anchors on the main path, but html_to_markdown\n // and the extract fallback bypass it; turndown is shared, so absolutize here\n // for every path. Empty/absent href falls through to bare content to match\n // turndown's default link rule (which never matches when href is falsy).\n service.addRule('anchorAbsolutize', {\n filter: 'a',\n replacement: (content, node) => {\n const rawHref = node.getAttribute('href');\n if (!rawHref) {\n return content;\n }\n const href = absolutize(rawHref, baseUrl);\n const title = node.getAttribute('title');\n const titlePart = title ? ` \"${title.replace(/\"/g, '\\\\\"')}\"` : '';\n return `[${content}](${href}${titlePart})`;\n },\n });\n // Math markers are emitted by `policy/math.ts` and survive Readability via the\n // `rdrm-math` preserve entry. A rule's replacement is inserted verbatim, so\n // the raw LaTeX backslashes survive turndown untouched.\n service.addRule('mathMarker', {\n filter: node =>\n node.nodeName === 'SPAN' && node.classList.contains('rdrm-math'),\n replacement: (_content, node) => {\n const tex = node.textContent.trim();\n if (!tex) {\n return '';\n }\n const display = node.getAttribute('data-display') === 'true';\n return display ? `$$${tex}$$` : `$${tex}$`;\n },\n });\n\n const fnResult = processFootnotes(html);\n const sourceHtml = fnResult?.html ?? html;\n let body = service.turndown(sourceHtml);\n\n if (fnResult) {\n // Turndown sees `[^N]` as a shortcut-reference link and backslash-escapes\n // the brackets; reverse that for the markers we emitted so they render as\n // footnote refs. Code blocks pass through verbatim and never carry the\n // escape, so they are unaffected.\n for (let n = 1; n <= fnResult.footnoteDefs.length; n++) {\n body = body.replaceAll(`\\\\[^${n}\\\\]`, `[^${n}]`);\n }\n }\n\n const trailingBlocks: string[] = [];\n if (imageMode === 'reference' && references.length > 0) {\n trailingBlocks.push(\n references.map((ref, i) => `[img-${i + 1}]: ${ref}`).join('\\n'),\n );\n }\n if (fnResult && fnResult.footnoteDefs.length > 0) {\n trailingBlocks.push(\n fnResult.footnoteDefs.map((def, i) => `[^${i + 1}]: ${def}`).join('\\n'),\n );\n }\n if (trailingBlocks.length === 0) {\n return body;\n }\n return `${body.replace(/\\n+$/, '')}\\n\\n${trailingBlocks.join('\\n')}`;\n}\n\nfunction applyImagePolicy(\n service: TurndownService,\n mode: ImageMode | undefined,\n baseUrl: string | undefined,\n references: string[],\n): void {\n switch (mode) {\n case 'drop': {\n // `.remove('img')` doesn't reliably drop void elements in turndown 7.x.\n service.addRule('dropImage', {\n filter: 'img',\n replacement: () => '',\n });\n break;\n }\n case 'keep':\n case undefined: {\n service.addRule('imageKeep', {\n filter: 'img',\n replacement: (_content, node) => {\n const src = absolutize(node.getAttribute('src') ?? '', baseUrl);\n const alt = node.getAttribute('alt') ?? '';\n return src ? `![${alt}](${src})` : '';\n },\n });\n break;\n }\n case 'reference': {\n service.addRule('imageReference', {\n filter: 'img',\n replacement: (_content, node) => {\n const src = absolutize(node.getAttribute('src') ?? '', baseUrl);\n if (!src) {\n return '';\n }\n references.push(src);\n const id = references.length;\n const alt = node.getAttribute('alt') ?? '';\n return `![${alt}][img-${id}]`;\n },\n });\n break;\n }\n case 'src-only': {\n service.addRule('imageSrcOnly', {\n filter: 'img',\n replacement: (_content, node) => {\n const src = absolutize(node.getAttribute('src') ?? '', baseUrl);\n return src ? `\\n\\n${src}\\n\\n` : '';\n },\n });\n break;\n }\n }\n}\n","import type { SanitizationDiagnostics } from '../pipeline/context.js';\nimport type { ImageMode, TableFormat } from '../pipeline/turndown.js';\n\nimport { sanitizeHtml } from '../pipeline/sanitize.js';\nimport { toMarkdown } from '../pipeline/turndown.js';\n\nexport interface FallbackOptions {\n readonly baseUrl?: string;\n readonly codeBlockStyle?: 'fenced' | 'indented';\n readonly gfm?: boolean;\n readonly headingStyle?: 'atx' | 'setext';\n readonly images?: ImageMode;\n readonly sanitize: boolean;\n readonly tables?: TableFormat;\n readonly window: Window;\n}\n\nexport interface FallbackResult {\n readonly markdown: string;\n readonly rootSelector: string;\n readonly sanitization: SanitizationDiagnostics;\n readonly sanitizedHtml: string;\n readonly textContent: string;\n}\n\n// Below this a block is treated as not text-dense (nav/boilerplate-ish).\nconst MIN_DENSE_BLOCK_CHARS = 200;\n\nfunction convert(\n element: Element,\n options: Readonly<FallbackOptions>,\n):\n | undefined\n | {\n markdown: string;\n sanitization: SanitizationDiagnostics;\n sanitizedHtml: string;\n textContent: string;\n } {\n const rawHtml = element.outerHTML;\n let sanitizedHtml = rawHtml;\n let sanitization: SanitizationDiagnostics = { iframes: 0, scripts: 0 };\n if (options.sanitize) {\n const res = sanitizeHtml(rawHtml, options.window);\n sanitizedHtml = res.html;\n sanitization = { iframes: res.iframesRemoved, scripts: res.scriptsRemoved };\n }\n const markdown = toMarkdown(sanitizedHtml, {\n codeBlockStyle: options.codeBlockStyle,\n gfm: options.gfm,\n headingStyle: options.headingStyle,\n images: options.images,\n tables: options.tables,\n baseUrl: options.baseUrl,\n });\n const textContent = element.textContent;\n if (markdown.trim().length === 0) {\n return undefined;\n }\n return { markdown, sanitization, sanitizedHtml, textContent };\n}\n\nfunction largestTextDenseBlock(document: Document): Element | undefined {\n let best: Element | undefined;\n let bestLen = 0;\n const candidates = document.querySelectorAll('div, section');\n candidates.forEach(el => {\n const len = el.textContent.trim().length;\n if (len > bestLen && len >= MIN_DENSE_BLOCK_CHARS) {\n best = el;\n bestLen = len;\n }\n });\n return best;\n}\n\nexport function extractViaFallback(\n document: Document,\n options: Readonly<FallbackOptions>,\n): FallbackResult | null {\n const orderedSelectors: readonly string[] = [\n 'article',\n 'main',\n '[role=main]',\n ];\n for (const selector of orderedSelectors) {\n const root = document.querySelector(selector);\n if (root) {\n const converted = convert(root, options);\n if (converted) {\n return { ...converted, rootSelector: selector };\n }\n }\n }\n\n const dense = largestTextDenseBlock(document);\n if (dense) {\n const converted = convert(dense, options);\n if (converted) {\n return { ...converted, rootSelector: 'largest-block' };\n }\n }\n\n const converted = convert(document.body, options);\n if (converted) {\n return { ...converted, rootSelector: 'body' };\n }\n\n return null;\n}\n","import { isPlaceholderSrc } from '../pipeline/normalize.js';\nimport { absolutize } from '../pipeline/urls.js';\n\nexport interface ImageEntry {\n readonly alt: string;\n readonly caption: string;\n readonly height?: number;\n readonly src: string;\n readonly width?: number;\n}\n\nfunction positiveInt(value: null | string): number | undefined {\n if (value === null) {\n return undefined;\n }\n const n = Number.parseInt(value, 10);\n return Number.isInteger(n) && n > 0 ? n : undefined;\n}\n\n// resolveLazyImages has already mutated placeholder srcs to real ones in the\n// article HTML by the time this runs — reading `src` here gets the resolved\n// value, so this walker does NOT re-run source resolution. isPlaceholderSrc\n// skips any remaining data:/placeholder srcs the lazy resolver left alone.\nexport function collectImageInventory(\n html: string,\n window: Window,\n baseUrl?: string,\n): ImageEntry[] {\n if (!html) {\n return [];\n }\n const probe = window.document.createElement('div');\n probe.innerHTML = html;\n\n const entries: ImageEntry[] = [];\n for (const img of probe.querySelectorAll('img')) {\n const rawSrc = img.getAttribute('src') ?? '';\n if (isPlaceholderSrc(rawSrc)) {\n continue;\n }\n const src = absolutize(rawSrc, baseUrl);\n if (!src) {\n continue;\n }\n const alt = img.getAttribute('alt') ?? '';\n const width = positiveInt(img.getAttribute('width'));\n const height = positiveInt(img.getAttribute('height'));\n // <figcaption> may precede OR follow the <img> inside the <figure>, so\n // closest('figure') + querySelector('figcaption') covers both orderings.\n const figure = img.closest('figure');\n const figcaption = figure?.querySelector('figcaption');\n const caption = figcaption\n ? figcaption.textContent.replace(/\\s+/g, ' ').trim()\n : alt;\n entries.push({\n src,\n alt,\n ...(width !== undefined ? { width } : {}),\n ...(height !== undefined ? { height } : {}),\n caption,\n });\n }\n return entries;\n}\n","import { hardSplitLines, parseBlocks } from './markdown.js';\n\nexport interface TruncateResult {\n readonly text: string;\n readonly truncated: boolean;\n}\n\nconst TRUNCATION_MARKER = '\\n\\n…[truncated]';\n\n// Cuts at a top-level block boundary. A fenced code block is a single mdast\n// node, so a boundary cut never lands inside one — no open-fence rollback is\n// needed. The kept span is sliced from the original string so the payload is a\n// byte-for-byte prefix of the Turndown output.\nexport function truncateMarkdown(\n markdown: string,\n maxChars: number,\n): TruncateResult {\n if (markdown.length <= maxChars) {\n return { text: markdown, truncated: false };\n }\n\n const blocks = parseBlocks(markdown);\n let start = -1;\n let end = -1;\n let truncated = false;\n for (const block of blocks) {\n const from = start === -1 ? block.start : start;\n if (block.end - from > maxChars) {\n // First non-code block alone overflows: take a line-aware prefix instead\n // of dropping everything. A code block is never split — a half-open fence\n // is unrecoverable — so an oversized first code block stays dropped.\n if (start === -1 && block.kind !== 'code') {\n const firstPiece =\n hardSplitLines(markdown.slice(block.start, block.end), maxChars)[0] ??\n '';\n return {\n text: firstPiece.replace(/\\s+$/, '') + TRUNCATION_MARKER,\n truncated: true,\n };\n }\n truncated = true;\n break;\n }\n if (start === -1) {\n start = block.start;\n }\n end = block.end;\n }\n\n if (!truncated) {\n return { text: markdown, truncated: false };\n }\n\n const kept = start === -1 ? '' : markdown.slice(start, end);\n return {\n text: kept.replace(/\\s+$/, '') + TRUNCATION_MARKER,\n truncated: true,\n };\n}\n","import type { SanitizationDiagnostics } from '../pipeline/context.js';\nimport type { ToolHandle } from '../server.js';\n\nimport { ExtractionError, toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { formatPayload } from '../output/format.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport {\n applySelectors,\n canonicalizeCodeBlocks,\n normalizeDocument,\n resolveLazyImages,\n} from '../pipeline/normalize.js';\nimport { isReaderable, parseArticle } from '../pipeline/readability.js';\nimport { sanitizeHtml } from '../pipeline/sanitize.js';\nimport { toMarkdown } from '../pipeline/turndown.js';\nimport { chunkMarkdown } from '../policy/chunk.js';\nimport { assembleDiagnostics, TraceCollector } from '../policy/diagnostics.js';\nimport { extractViaFallback } from '../policy/fallback.js';\nimport { detectGating } from '../policy/gating.js';\nimport { collectImageInventory } from '../policy/images.js';\nimport { resolveMetadata } from '../policy/metadata.js';\nimport { detectPagination } from '../policy/pagination.js';\nimport { resolveReadabilityOptions } from '../policy/resolver.js';\nimport { computeTextMetrics } from '../policy/text.js';\nimport { truncateMarkdown } from '../policy/truncate.js';\nimport * as cache from '../resources.js';\nimport { readHtmlFile } from './html-source.js';\nimport { outputSchemaShape } from './output-schema.js';\nimport {\n type ExtractFromHtmlInput,\n type ExtractInput,\n extractInputSchema,\n extractInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\n// Names the extraction path, not a DOM tag — Readability doesn't expose the source node.\nconst EXTRACTED_NODE = 'readability';\n\nexport function extractArticle(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractInputSchema.parse(rawArgs);\n return extractArticleFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\n// Schema defaults materialized once. Callers that pass only a subset of the\n// knobs (extract_section's scoped delegation, the CLI, tests) get the rest\n// filled from here, so the worker always sees a complete input. `localPath` is\n// required only to anchor the source; a sentinel parse materializes every\n// `.default()` and the type narrows it back out.\nconst DEFAULTS: Omit<ExtractInput, 'localPath'> = extractInputSchema.parse({\n localPath: '',\n});\n\nexport function extractArticleFromHtml(\n input: Readonly<ExtractFromHtmlInput>,\n): CallToolResult {\n const merged = { ...DEFAULTS, ...input };\n const {\n html,\n baseUrl,\n cache: useCache,\n selectors,\n extraction,\n minArticleLength,\n maxNodes,\n keepClasses,\n readabilityOverrides,\n format,\n metadataMode,\n gfm,\n headingStyle,\n codeBlockStyle,\n images,\n sanitize: shouldSanitize,\n maxChars,\n wordsPerMinute,\n cleanChrome,\n tables,\n chunk,\n imageInventory,\n debug,\n } = merged;\n\n // Cache hit short-circuits the pipeline. A clone is returned so the cache\n // field can be overwritten to reflect *this* call's hashes (normalizedHash is\n // stable across re-renders, but originalHash differs by nonce/CSP/…).\n if (useCache) {\n const hit = cache.lookup(html, merged);\n if (hit) {\n const cloned = JSON.parse(\n JSON.stringify(hit.entry.structuredContent),\n ) as {\n [key: string]: unknown;\n diagnostics: Record<string, unknown>;\n };\n const structuredContent = {\n ...cloned,\n diagnostics: {\n ...cloned.diagnostics,\n cache: {\n hit: true,\n normalizedHash: hit.normalizedHash,\n originalHash: hit.originalHash,\n },\n },\n };\n return {\n content: [{ text: hit.entry.contentText, type: 'text' }],\n structuredContent,\n };\n }\n }\n\n // Stages are timed at the orchestrator boundary so they stay non-overlapping\n // and sum to the pipeline's wall-clock — stripConsent/absolutize live inside\n // normalize/turndown respectively and aren't carved out as their own stages.\n const trace = new TraceCollector(debug);\n\n const { document, window } = buildDocument(html, baseUrl);\n // Paywall overlays are stripped by normalizeDocument's stripChrome (a Piano\n // modal is role=\"dialog\" + full-viewport fixed), so gating must be detected\n // before normalization. Pagination chrome survives stripChrome and is detected after.\n const {\n gating,\n documentElementCount,\n normalizeCounts,\n imagesResolved,\n pagination,\n } = trace.run('normalize', () => {\n const gating = detectGating(document);\n const documentElementCount = document.querySelectorAll('*').length;\n const normalizeCounts = normalizeDocument(document, { cleanChrome });\n const imagesResolved = resolveLazyImages(document);\n // Detect before applySelectors: a caller's selectors.include could scope the\n // body and hide pagination chrome, but \"more content exists\" is still true.\n const pagination = detectPagination(document, baseUrl);\n applySelectors(document, selectors);\n const codeBlocksCanonicalized = canonicalizeCodeBlocks(document);\n if (codeBlocksCanonicalized > 0) {\n logger.debug(\n `canonicalized ${codeBlocksCanonicalized} code-block language tag(s)`,\n );\n }\n return {\n documentElementCount,\n gating,\n imagesResolved,\n normalizeCounts,\n pagination,\n };\n });\n\n const { readerable, article } = trace.run('readability', () => {\n const readerable = isReaderable(document);\n const readabilityOptions = resolveReadabilityOptions({\n extraction,\n keepClasses,\n maxNodes,\n minArticleLength,\n readabilityOverrides,\n });\n const article = parseArticle(document, readabilityOptions);\n return { article, readerable };\n });\n\n let markdown: string;\n let sanitizedHtml: string;\n let textContent: string;\n let extractedNode: string;\n let fallbackUsed: boolean;\n let sanitizeCounts: SanitizationDiagnostics;\n\n if (article?.content) {\n extractedNode = EXTRACTED_NODE;\n fallbackUsed = false;\n textContent = article.textContent ?? '';\n const articleHtml = article.content;\n const sanitized = trace.run(\n 'sanitize',\n (): {\n counts: SanitizationDiagnostics;\n html: string;\n } => {\n if (!shouldSanitize) {\n return { counts: { iframes: 0, scripts: 0 }, html: articleHtml };\n }\n const res = sanitizeHtml(articleHtml, window);\n return {\n counts: { iframes: res.iframesRemoved, scripts: res.scriptsRemoved },\n html: res.html,\n };\n },\n );\n sanitizedHtml = sanitized.html;\n sanitizeCounts = sanitized.counts;\n markdown = trace.run('turndown', () =>\n toMarkdown(sanitizedHtml, {\n codeBlockStyle,\n gfm,\n headingStyle,\n images,\n tables,\n baseUrl,\n }),\n );\n } else {\n // Cascade only on parse failure, not on isProbablyReaderable.\n // Fallback owns sanitize + turndown internally, so the two stages collapse\n // into one timed block here — keeping the trace non-overlapping.\n const fallback = trace.run('fallback', () =>\n extractViaFallback(document, {\n codeBlockStyle,\n gfm,\n headingStyle,\n images,\n sanitize: shouldSanitize,\n tables,\n baseUrl,\n window,\n }),\n );\n if (!fallback) {\n throw new ExtractionError(\n 'Readability returned no article and the selector cascade yielded no usable content.',\n );\n }\n extractedNode = fallback.rootSelector;\n fallbackUsed = true;\n markdown = fallback.markdown;\n sanitizedHtml = fallback.sanitizedHtml;\n textContent = fallback.textContent;\n sanitizeCounts = fallback.sanitization;\n }\n\n const { metadata } = trace.run('metadata', () => {\n const { wordCount, readingTimeMin } = computeTextMetrics(\n textContent,\n wordsPerMinute,\n );\n const metadata = resolveMetadata({\n document,\n readability: article,\n readingTimeMin,\n textContent,\n baseUrl,\n wordCount,\n });\n return { metadata };\n });\n\n const sanitization: SanitizationDiagnostics = {\n iframes: normalizeCounts.iframes + sanitizeCounts.iframes,\n scripts: normalizeCounts.scripts + sanitizeCounts.scripts,\n };\n const baseDiagnostics = assembleDiagnostics({\n articleHtml: sanitizedHtml,\n boilerplateRemoved: normalizeCounts.boilerplateRemoved,\n chromeRemoved: normalizeCounts.chromeRemoved,\n documentElementCount,\n extractedNode,\n fallbackUsed,\n gated: gating,\n imagesResolved,\n pagination,\n readerable,\n sanitization,\n trace: trace.collect(),\n truncated: false,\n window,\n });\n\n let payload = formatPayload({\n diagnostics: baseDiagnostics,\n format,\n markdown,\n metadata,\n metadataMode,\n sanitizedHtml,\n textContent,\n });\n\n let truncated = false;\n if (maxChars !== undefined && (format === 'markdown' || format === 'text')) {\n const res = truncateMarkdown(payload, maxChars);\n payload = res.text;\n truncated = res.truncated;\n }\n const diagnostics = truncated\n ? { ...baseDiagnostics, truncated }\n : baseDiagnostics;\n\n // Chunk the final payload (post-format, post-truncation) so token counts\n // reflect what the host sees in content[0].text. HTML/JSON formats carry no\n // markdown body to slice, so `chunks` stays unset for them.\n const chunks =\n chunk && (format === 'markdown' || format === 'text')\n ? chunkMarkdown(payload, chunk)\n : undefined;\n\n // Runs against sanitizedHtml so the inventory reflects exactly what the host\n // sees in content[0].text — post-sanitization, post-lazy-resolution.\n const imageInventoryEntries = imageInventory\n ? collectImageInventory(sanitizedHtml, window, baseUrl)\n : undefined;\n\n const baseStructuredContent = {\n schemaVersion: 1 as const,\n content: payload,\n metadata,\n diagnostics,\n ...(chunks ? { chunks } : {}),\n ...(imageInventoryEntries ? { images: imageInventoryEntries } : {}),\n };\n\n // On cache miss (when cache:true), persist a cache-agnostic copy so the\n // stored entry never carries a stale hit/miss verdict, then surface the\n // signal on the returned object only.\n let structuredContent = baseStructuredContent;\n if (useCache) {\n const stored = cache.storeResult(html, merged, {\n contentText: payload,\n structuredContent: baseStructuredContent,\n });\n structuredContent = {\n ...baseStructuredContent,\n diagnostics: {\n ...diagnostics,\n cache: {\n hit: false,\n normalizedHash: stored.normalizedHash,\n originalHash: stored.originalHash,\n },\n },\n };\n }\n\n return {\n content: [{ text: payload, type: 'text' }],\n structuredContent,\n };\n}\n\nexport const EXTRACT_TOOL_DESCRIPTION = `Extract the main article from already-rendered (post-JavaScript) HTML and return clean Markdown plus metadata and diagnostics. The server fetches nothing: \\`localPath\\` (a file holding the rendered HTML, e.g. \\`document.documentElement.outerHTML\\` written to disk by a browser/devtools capture) is the only source, and \\`baseUrl\\` (optional) is used solely to absolutize relative links.`;\n\nexport function extractHandler(args: unknown): CallToolResult {\n try {\n return extractArticle(args);\n } catch (err) {\n logger.error(\n `extract failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract',\n {\n title: 'Extract article to Markdown',\n description: EXTRACT_TOOL_DESCRIPTION,\n inputSchema: extractInputShape,\n outputSchema: outputSchemaShape,\n },\n extractHandler,\n );\n}\n"],"mappings":"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"}