@vakra-dev/reader 0.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/scraper.ts","../src/cloudflare/detector.ts","../src/cloudflare/handler.ts","../src/formatters/markdown.ts","../src/formatters/html.ts","../src/formatters/json.ts","../src/formatters/text.ts","../src/utils/content-cleaner.ts","../src/utils/metadata-extractor.ts","../src/utils/url-helpers.ts","../src/utils/logger.ts","../src/utils/robots-parser.ts","../src/types.ts","../src/crawler.ts","../src/utils/rate-limiter.ts","../src/browser/pool.ts","../src/proxy/config.ts","../src/browser/hero-config.ts","../src/daemon/server.ts","../src/daemon/client.ts"],"sourcesContent":["/**\n * ReaderClient\n *\n * A client wrapper that manages HeroCore lifecycle and provides\n * a simple interface for scraping and crawling.\n *\n * @example\n * const reader = new ReaderClient();\n *\n * const result = await reader.scrape({\n * urls: ['https://example.com'],\n * formats: ['markdown'],\n * });\n *\n * console.log(result.data[0].markdown);\n *\n * // When done (optional - auto-closes on process exit)\n * await reader.close();\n */\n\nimport HeroCore from \"@ulixee/hero-core\";\nimport { TransportBridge } from \"@ulixee/net\";\nimport { ConnectionToHeroCore } from \"@ulixee/hero\";\nimport { scrape } from \"./scraper\";\nimport { crawl } from \"./crawler\";\nimport { HeroBrowserPool } from \"./browser/pool\";\nimport type { ScrapeOptions, ScrapeResult, ProxyConfig, BrowserPoolConfig } from \"./types\";\nimport type { CrawlOptions, CrawlResult } from \"./crawl-types\";\nimport { createLogger } from \"./utils/logger\";\n\nconst logger = createLogger(\"client\");\n\n/**\n * Proxy rotation strategy\n */\nexport type ProxyRotation = \"round-robin\" | \"random\";\n\n/**\n * Configuration options for ReaderClient\n */\nexport interface ReaderClientOptions {\n /** Enable verbose logging (default: false) */\n verbose?: boolean;\n /** Show Chrome browser window (default: false) */\n showChrome?: boolean;\n\n /** Browser pool configuration */\n browserPool?: BrowserPoolConfig;\n\n /** List of proxies to rotate through */\n proxies?: ProxyConfig[];\n\n /** Proxy rotation strategy (default: \"round-robin\") */\n proxyRotation?: ProxyRotation;\n\n /** Skip TLS/SSL certificate verification (default: true) */\n skipTLSVerification?: boolean;\n}\n\n/**\n * ReaderClient manages the HeroCore lifecycle and provides\n * scrape/crawl methods with automatic initialization.\n */\nexport class ReaderClient {\n private heroCore: HeroCore | null = null;\n private pool: HeroBrowserPool | null = null;\n private initialized = false;\n private initializing: Promise<void> | null = null;\n private closed = false;\n private options: ReaderClientOptions;\n private proxyIndex = 0;\n private cleanupHandler: (() => Promise<void>) | null = null;\n\n constructor(options: ReaderClientOptions = {}) {\n this.options = options;\n\n // Configure TLS verification\n // Hero uses MITM_ALLOW_INSECURE env var to skip certificate verification\n // Default is true (skip verification) for compatibility with various sites\n const skipTLS = options.skipTLSVerification ?? true;\n if (skipTLS) {\n process.env.MITM_ALLOW_INSECURE = \"true\";\n }\n\n // Register cleanup on process exit\n this.registerCleanup();\n }\n\n /**\n * Get the next proxy from the rotation pool\n */\n private getNextProxy(): ProxyConfig | undefined {\n const { proxies, proxyRotation = \"round-robin\" } = this.options;\n\n if (!proxies || proxies.length === 0) {\n return undefined;\n }\n\n if (proxyRotation === \"random\") {\n return proxies[Math.floor(Math.random() * proxies.length)];\n }\n\n // Round-robin (default)\n const proxy = proxies[this.proxyIndex % proxies.length];\n this.proxyIndex++;\n return proxy;\n }\n\n /**\n * Initialize HeroCore. Called automatically on first scrape/crawl.\n * Can be called explicitly if you want to pre-warm the client.\n */\n async start(): Promise<void> {\n if (this.closed) {\n throw new Error(\"ReaderClient has been closed. Create a new instance.\");\n }\n\n if (this.initialized) {\n return;\n }\n\n // Prevent concurrent initialization\n if (this.initializing) {\n await this.initializing;\n return;\n }\n\n this.initializing = this.initializeCore();\n await this.initializing;\n this.initializing = null;\n }\n\n /**\n * Internal initialization logic\n */\n private async initializeCore(): Promise<void> {\n try {\n if (this.options.verbose) {\n logger.info(\"Starting HeroCore...\");\n }\n\n this.heroCore = new HeroCore();\n await this.heroCore.start();\n\n if (this.options.verbose) {\n logger.info(\"HeroCore started successfully\");\n }\n\n // Initialize browser pool\n if (this.options.verbose) {\n logger.info(\"Initializing browser pool...\");\n }\n\n const browserPoolConfig = this.options.browserPool;\n const poolConfig = {\n size: browserPoolConfig?.size ?? 2,\n retireAfterPageCount: browserPoolConfig?.retireAfterPages ?? 100,\n retireAfterAgeMs: (browserPoolConfig?.retireAfterMinutes ?? 30) * 60 * 1000,\n maxQueueSize: browserPoolConfig?.maxQueueSize ?? 100,\n };\n\n this.pool = new HeroBrowserPool(\n poolConfig,\n undefined, // proxy set per-request\n this.options.showChrome,\n this.createConnection(),\n undefined, // userAgent\n this.options.verbose\n );\n await this.pool.initialize();\n\n this.initialized = true;\n\n if (this.options.verbose) {\n logger.info(\"Browser pool initialized successfully\");\n }\n } catch (error: any) {\n // Clean up on failure\n if (this.pool) {\n await this.pool.shutdown().catch(() => {});\n this.pool = null;\n }\n if (this.heroCore) {\n await this.heroCore.close().catch(() => {});\n this.heroCore = null;\n }\n this.initialized = false;\n\n // Provide helpful error messages\n const message = error.message || String(error);\n\n if (message.includes(\"EADDRINUSE\")) {\n throw new Error(\n \"Failed to start HeroCore: Port already in use. \" +\n \"Another instance may be running. \" +\n \"Close it or use a different port.\"\n );\n }\n\n if (message.includes(\"chrome\") || message.includes(\"Chrome\")) {\n throw new Error(\n \"Failed to start HeroCore: Chrome/Chromium not found. \" +\n \"Please install Chrome or set CHROME_PATH environment variable.\"\n );\n }\n\n throw new Error(`Failed to start HeroCore: ${message}`);\n }\n }\n\n /**\n * Create a connection to the HeroCore instance\n */\n private createConnection(): ConnectionToHeroCore {\n if (!this.heroCore) {\n throw new Error(\"HeroCore not initialized. This should not happen.\");\n }\n\n const bridge = new TransportBridge();\n this.heroCore.addConnection(bridge.transportToClient);\n return new ConnectionToHeroCore(bridge.transportToCore);\n }\n\n /**\n * Ensure client is initialized before operation\n */\n private async ensureInitialized(): Promise<void> {\n if (this.closed) {\n throw new Error(\"ReaderClient has been closed. Create a new instance.\");\n }\n\n if (!this.initialized) {\n await this.start();\n }\n }\n\n /**\n * Scrape one or more URLs\n *\n * @param options - Scrape options (urls, formats, etc.)\n * @returns Scrape result with data and metadata\n *\n * @example\n * const result = await reader.scrape({\n * urls: ['https://example.com'],\n * formats: ['markdown', 'html'],\n * });\n */\n async scrape(options: Omit<ScrapeOptions, \"connectionToCore\" | \"pool\">): Promise<ScrapeResult> {\n await this.ensureInitialized();\n\n if (!this.pool) {\n throw new Error(\"Browser pool not initialized. This should not happen.\");\n }\n\n // Use proxy rotation if proxies are configured and no specific proxy is provided\n const proxy = options.proxy ?? this.getNextProxy();\n\n return await scrape({\n ...options,\n proxy,\n showChrome: options.showChrome ?? this.options.showChrome,\n verbose: options.verbose ?? this.options.verbose,\n pool: this.pool,\n });\n }\n\n /**\n * Crawl a website to discover URLs\n *\n * @param options - Crawl options (url, depth, maxPages, etc.)\n * @returns Crawl result with discovered URLs and optional scraped content\n *\n * @example\n * const result = await reader.crawl({\n * url: 'https://example.com',\n * depth: 2,\n * maxPages: 50,\n * scrape: true,\n * });\n */\n async crawl(options: Omit<CrawlOptions, \"connectionToCore\" | \"pool\">): Promise<CrawlResult> {\n await this.ensureInitialized();\n\n if (!this.pool) {\n throw new Error(\"Browser pool not initialized. This should not happen.\");\n }\n\n // Use proxy rotation if proxies are configured and no specific proxy is provided\n const proxy = options.proxy ?? this.getNextProxy();\n\n return await crawl({\n ...options,\n proxy,\n pool: this.pool,\n });\n }\n\n /**\n * Check if the client is initialized and ready\n */\n isReady(): boolean {\n return this.initialized && !this.closed;\n }\n\n /**\n * Close the client and release resources\n *\n * Note: This is optional - the client will auto-close on process exit.\n */\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n\n this.closed = true;\n\n // Remove process event handlers to allow clean exit\n this.removeCleanupHandlers();\n\n // Shutdown pool first (closes browser instances)\n if (this.pool) {\n if (this.options.verbose) {\n logger.info(\"Shutting down browser pool...\");\n }\n\n try {\n await this.pool.shutdown();\n } catch (error: any) {\n if (this.options.verbose) {\n logger.warn(`Error shutting down pool: ${error.message}`);\n }\n }\n\n this.pool = null;\n }\n\n // Then close HeroCore\n if (this.heroCore) {\n if (this.options.verbose) {\n logger.info(\"Closing HeroCore...\");\n }\n\n try {\n await this.heroCore.close();\n // Also call static shutdown to clean up any remaining resources\n await HeroCore.shutdown();\n } catch (error: any) {\n // Ignore close errors\n if (this.options.verbose) {\n logger.warn(`Error closing HeroCore: ${error.message}`);\n }\n }\n\n this.heroCore = null;\n }\n\n this.initialized = false;\n\n if (this.options.verbose) {\n logger.info(\"ReaderClient closed\");\n }\n }\n\n /**\n * Register cleanup handlers for process exit\n */\n private registerCleanup(): void {\n this.cleanupHandler = async () => {\n await this.close();\n };\n\n // Handle various exit signals\n process.once(\"beforeExit\", this.cleanupHandler);\n process.once(\"SIGINT\", async () => {\n await this.cleanupHandler?.();\n process.exit(0);\n });\n process.once(\"SIGTERM\", async () => {\n await this.cleanupHandler?.();\n process.exit(0);\n });\n }\n\n /**\n * Remove process cleanup handlers\n */\n private removeCleanupHandlers(): void {\n if (this.cleanupHandler) {\n process.removeListener(\"beforeExit\", this.cleanupHandler);\n this.cleanupHandler = null;\n }\n }\n}\n","import Hero from \"@ulixee/hero\";\nimport pLimit from \"p-limit\";\nimport type { IBrowserPool } from \"./browser/types\";\nimport { detectChallenge } from \"./cloudflare/detector\";\nimport { waitForChallengeResolution } from \"./cloudflare/handler\";\nimport { formatToMarkdown } from \"./formatters/markdown\";\nimport { formatToHTML } from \"./formatters/html\";\nimport { formatToJson } from \"./formatters/json\";\nimport { formatToText } from \"./formatters/text\";\nimport { cleanContent } from \"./utils/content-cleaner\";\nimport { extractMetadata } from \"./utils/metadata-extractor\";\nimport { createLogger } from \"./utils/logger\";\nimport { fetchRobotsTxt, isUrlAllowed, type RobotsRules } from \"./utils/robots-parser\";\nimport {\n DEFAULT_OPTIONS,\n type ScrapeOptions,\n type ScrapeResult,\n type WebsiteScrapeResult,\n type BatchMetadata,\n type Page,\n type ProxyMetadata,\n} from \"./types\";\n\n/**\n * Scraper class with built-in concurrency support\n *\n * Features:\n * - Hero-based browser automation\n * - Automatic Cloudflare challenge detection and bypass\n * - Built-in concurrency via browser pool\n * - Progress tracking\n * - Error handling per URL\n *\n * @example\n * const scraper = new Scraper({\n * urls: ['https://example.com', 'https://example.org'],\n * formats: ['markdown', 'html'],\n * batchConcurrency: 2,\n * proxy: { type: 'residential', ... }\n * });\n *\n * const result = await scraper.scrape();\n * console.log(`Scraped ${result.batchMetadata.successfulUrls} URLs`);\n */\nexport class Scraper {\n private options: Required<ScrapeOptions>;\n private pool: IBrowserPool;\n private logger = createLogger(\"scraper\");\n private robotsCache: Map<string, RobotsRules | null> = new Map();\n\n constructor(options: ScrapeOptions) {\n // Merge with defaults\n this.options = {\n ...DEFAULT_OPTIONS,\n ...options,\n } as Required<ScrapeOptions>;\n\n // Pool must be provided by client\n if (!options.pool) {\n throw new Error(\"Browser pool must be provided. Use ReaderClient for automatic pool management.\");\n }\n this.pool = options.pool;\n }\n\n /**\n * Get robots.txt rules for a URL, cached per domain\n */\n private async getRobotsRules(url: string): Promise<RobotsRules | null> {\n const origin = new URL(url).origin;\n if (!this.robotsCache.has(origin)) {\n const rules = await fetchRobotsTxt(origin);\n this.robotsCache.set(origin, rules);\n }\n return this.robotsCache.get(origin) ?? null;\n }\n\n /**\n * Scrape all URLs\n *\n * @returns Scrape result with pages and metadata\n */\n async scrape(): Promise<ScrapeResult> {\n const startTime = Date.now();\n\n // Pool is managed by ReaderClient - just use it\n // Scrape URLs with concurrency control\n const results = await this.scrapeWithConcurrency();\n\n // Build response\n return this.buildScrapeResult(results, startTime);\n }\n\n /**\n * Scrape URLs with concurrency control\n */\n private async scrapeWithConcurrency(): Promise<\n Array<{ result: WebsiteScrapeResult | null; error?: string }>\n > {\n const limit = pLimit(this.options.batchConcurrency || 1);\n const tasks = this.options.urls.map((url, index) =>\n limit(() => this.scrapeSingleUrlWithRetry(url, index))\n );\n\n const batchPromise = Promise.all(tasks);\n\n // Apply batch timeout if specified\n if (this.options.batchTimeoutMs && this.options.batchTimeoutMs > 0) {\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(new Error(`Batch operation timed out after ${this.options.batchTimeoutMs}ms`));\n }, this.options.batchTimeoutMs);\n });\n\n return Promise.race([batchPromise, timeoutPromise]);\n }\n\n return batchPromise;\n }\n\n /**\n * Scrape a single URL with retry logic\n */\n private async scrapeSingleUrlWithRetry(\n url: string,\n index: number\n ): Promise<{ result: WebsiteScrapeResult | null; error?: string }> {\n const maxRetries = this.options.maxRetries || 2;\n let lastError: string | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const result = await this.scrapeSingleUrl(url, index);\n if (result) {\n return { result };\n }\n // Result is null but no exception - unexpected state\n lastError = `Failed to scrape ${url}: No content returned`;\n } catch (error: any) {\n lastError = error.message;\n if (attempt < maxRetries) {\n // Exponential backoff: 1s, 2s, 4s...\n const delay = Math.pow(2, attempt) * 1000;\n this.logger.warn(`Retry ${attempt + 1}/${maxRetries} for ${url} in ${delay}ms`);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n this.logger.error(`Failed to scrape ${url} after ${maxRetries + 1} attempts: ${lastError}`);\n return { result: null, error: lastError };\n }\n\n /**\n * Wait for the final page to load after any Cloudflare redirects\n * Cloudflare often does silent redirects even when bypassed, we need to ensure\n * we're on the actual content page before scraping.\n */\n private async waitForFinalPage(hero: Hero, originalUrl: string, verbose: boolean): Promise<void> {\n const maxWaitMs = 15000;\n const startTime = Date.now();\n const log = (msg: string) => verbose && this.logger.info(msg);\n\n // Wait for any pending navigation to complete\n try {\n await hero.waitForLoad(\"AllContentLoaded\", { timeoutMs: maxWaitMs });\n } catch {\n // Timeout is OK\n }\n\n // Check if URL changed (Cloudflare redirect)\n // Normalize URLs by removing trailing slashes for comparison\n let currentUrl = await hero.url;\n const normalizeUrl = (url: string) => url.replace(/\\/+$/, \"\");\n const urlChanged = normalizeUrl(currentUrl) !== normalizeUrl(originalUrl);\n\n if (urlChanged || currentUrl.includes(\"__cf_chl\")) {\n log(`Cloudflare redirect detected: ${originalUrl} → ${currentUrl}`);\n\n // Wait for the redirect to complete and new page to load\n // Poll until URL stabilizes and page is loaded\n let lastUrl = currentUrl;\n let stableCount = 0;\n\n while (Date.now() - startTime < maxWaitMs) {\n await new Promise((resolve) => setTimeout(resolve, 500));\n\n try {\n currentUrl = await hero.url;\n\n // URL is stable if it hasn't changed for 2 consecutive checks\n if (currentUrl === lastUrl) {\n stableCount++;\n if (stableCount >= 2) {\n break;\n }\n } else {\n stableCount = 0;\n lastUrl = currentUrl;\n log(`URL changed to: ${currentUrl}`);\n }\n } catch {\n // Error getting URL, continue waiting\n }\n }\n\n // Final wait for page content to render\n try {\n await hero.waitForLoad(\"AllContentLoaded\", { timeoutMs: 10000 });\n } catch {\n // Timeout OK\n }\n }\n\n // Final stabilization\n await hero.waitForPaintingStable();\n\n // Buffer for JS execution and dynamic content loading\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n\n /**\n * Scrape a single URL\n */\n private async scrapeSingleUrl(url: string, index: number): Promise<WebsiteScrapeResult | null> {\n const startTime = Date.now();\n\n // Check robots.txt before scraping\n const robotsRules = await this.getRobotsRules(url);\n if (!isUrlAllowed(url, robotsRules)) {\n throw new Error(`URL blocked by robots.txt: ${url}`);\n }\n\n try {\n return await this.pool.withBrowser(async (hero: Hero) => {\n // Navigate to URL\n await hero.goto(url, { timeoutMs: this.options.timeoutMs });\n\n // Wait for initial page load\n try {\n await hero.waitForLoad(\"DomContentLoaded\", { timeoutMs: this.options.timeoutMs });\n } catch {\n // Timeout is OK, continue anyway\n }\n await hero.waitForPaintingStable();\n\n // Detect and handle Cloudflare challenge\n let hadChallenge = false;\n let challengeType = \"none\";\n let waitTimeMs = 0;\n\n const initialUrl = await hero.url;\n const detection = await detectChallenge(hero);\n\n if (detection.isChallenge) {\n hadChallenge = true;\n challengeType = detection.type;\n\n if (this.options.verbose) {\n this.logger.info(`Challenge detected on ${url}: ${detection.type}`);\n }\n\n // Wait for resolution\n const result = await waitForChallengeResolution(hero, {\n maxWaitMs: 45000,\n pollIntervalMs: 500,\n verbose: this.options.verbose,\n initialUrl,\n });\n\n waitTimeMs = result.waitedMs;\n\n if (!result.resolved) {\n throw new Error(`Challenge not resolved: ${detection.type}`);\n }\n\n if (this.options.verbose) {\n this.logger.info(`Challenge resolved via ${result.method} in ${waitTimeMs}ms`);\n }\n }\n\n // Wait for any Cloudflare redirects to complete\n // Cloudflare often does a silent redirect after bypass, we need to wait for the final page\n await this.waitForFinalPage(hero, url, this.options.verbose);\n\n // Wait for selector if specified\n if (this.options.waitForSelector) {\n try {\n await hero.waitForElement(hero.document.querySelector(this.options.waitForSelector), {\n timeoutMs: this.options.timeoutMs,\n });\n } catch (error) {\n this.logger.warn(`Selector not found: ${this.options.waitForSelector}`);\n }\n }\n\n // Extract content\n const pageTitle = await hero.document.title;\n const html = await hero.document.documentElement.outerHTML;\n\n // Clean content with configurable options\n const cleanedHtml = cleanContent(html, url, {\n removeAds: this.options.removeAds,\n removeBase64Images: this.options.removeBase64Images,\n });\n\n // Extract metadata\n const websiteMetadata = extractMetadata(cleanedHtml, url);\n\n const duration = Date.now() - startTime;\n const scrapedAt = new Date().toISOString();\n\n // Create Page object for formatters\n const page: Page = {\n url,\n title: pageTitle,\n markdown: \"\", // Will be set by formatter\n html: cleanedHtml,\n fetchedAt: scrapedAt,\n depth: 0,\n hadChallenge,\n challengeType,\n waitTimeMs,\n };\n\n // Convert to formats\n const markdown = this.options.formats.includes(\"markdown\")\n ? formatToMarkdown(\n [page],\n url,\n scrapedAt,\n duration,\n websiteMetadata,\n this.options.includeMetadata\n )\n : undefined;\n\n const htmlOutput = this.options.formats.includes(\"html\")\n ? formatToHTML([page], url, scrapedAt, duration, websiteMetadata)\n : undefined;\n\n const json = this.options.formats.includes(\"json\")\n ? formatToJson([page], url, scrapedAt, duration, websiteMetadata)\n : undefined;\n\n const text = this.options.formats.includes(\"text\")\n ? formatToText(\n [page],\n url,\n scrapedAt,\n duration,\n websiteMetadata,\n this.options.includeMetadata\n )\n : undefined;\n\n // Report progress\n if (this.options.onProgress) {\n this.options.onProgress({\n completed: index + 1,\n total: this.options.urls.length,\n currentUrl: url,\n });\n }\n\n // Build proxy metadata if proxy was used\n let proxyMetadata: ProxyMetadata | undefined;\n if (this.options.proxy) {\n const proxy = this.options.proxy;\n // Extract host and port from either url or direct config\n if (proxy.url) {\n try {\n const proxyUrl = new URL(proxy.url);\n proxyMetadata = {\n host: proxyUrl.hostname,\n port: parseInt(proxyUrl.port, 10) || 80,\n country: proxy.country,\n };\n } catch {\n // Invalid URL, skip proxy metadata\n }\n } else if (proxy.host && proxy.port) {\n proxyMetadata = {\n host: proxy.host,\n port: proxy.port,\n country: proxy.country,\n };\n }\n }\n\n // Build result\n const result: WebsiteScrapeResult = {\n markdown,\n html: htmlOutput,\n json,\n text,\n metadata: {\n baseUrl: url,\n totalPages: 1,\n scrapedAt: new Date().toISOString(),\n duration,\n website: websiteMetadata,\n proxy: proxyMetadata,\n },\n };\n\n return result;\n });\n } catch (error: any) {\n this.logger.error(`Failed to scrape ${url}: ${error.message}`);\n\n // Report progress (failed)\n if (this.options.onProgress) {\n this.options.onProgress({\n completed: index + 1,\n total: this.options.urls.length,\n currentUrl: url,\n });\n }\n\n return null; // Return null for failed URLs\n }\n }\n\n /**\n * Build final scrape result\n */\n private buildScrapeResult(\n results: Array<{ result: WebsiteScrapeResult | null; error?: string }>,\n startTime: number\n ): ScrapeResult {\n const successful = results\n .filter((r) => r.result !== null)\n .map((r) => r.result as WebsiteScrapeResult);\n\n const errors: Array<{ url: string; error: string }> = [];\n results.forEach((r, index) => {\n if (r.result === null && r.error) {\n errors.push({ url: this.options.urls[index], error: r.error });\n }\n });\n\n const batchMetadata: BatchMetadata = {\n totalUrls: this.options.urls.length,\n successfulUrls: successful.length,\n failedUrls: results.filter((r) => r.result === null).length,\n scrapedAt: new Date().toISOString(),\n totalDuration: Date.now() - startTime,\n errors,\n };\n\n return {\n data: successful,\n batchMetadata,\n };\n }\n}\n\n/**\n * Convenience function to scrape URLs\n *\n * @param options - Scrape options\n * @returns Scrape result\n *\n * @example\n * const result = await scrape({\n * urls: ['https://example.com'],\n * formats: ['markdown']\n * });\n */\nexport async function scrape(options: ScrapeOptions): Promise<ScrapeResult> {\n const scraper = new Scraper(options);\n return scraper.scrape();\n}\n","import type Hero from \"@ulixee/hero\";\nimport type { ChallengeDetection } from \"./types\";\n\n/**\n * CHALLENGE-SPECIFIC DOM SELECTORS\n *\n * These are ONLY present during active challenges and disappear when complete.\n * No false positives - never appear on real content pages.\n */\nconst CHALLENGE_DOM_SELECTORS = [\n \"#challenge-running\",\n \"#challenge-stage\",\n \"#challenge-form\",\n \".cf-browser-verification\",\n];\n\n/**\n * CHALLENGE-SPECIFIC TEXT PATTERNS\n *\n * These phrases only appear during active challenges.\n * They disappear completely when the challenge resolves.\n */\nconst CHALLENGE_TEXT_PATTERNS = [\n \"verifying you are human\",\n \"checking if the site connection is secure\",\n \"this process is automatic. your browser will redirect\",\n];\n\n/**\n * BLOCKED/403 SIGNALS\n *\n * Detect when access is explicitly denied\n */\nconst BLOCKED_SIGNALS = [\n \"you have been blocked\",\n \"access to this page has been denied\",\n \"sorry, you have been blocked\",\n \"access denied\",\n \"403 forbidden\",\n];\n\n/**\n * Detect if current page is a Cloudflare challenge\n *\n * Uses multi-signal approach with ONLY challenge-specific indicators.\n * No content length heuristics to avoid false positives.\n *\n * @param hero - Hero instance with loaded page\n * @returns Detection result with confidence score and signals\n *\n * @example\n * const detection = await detectChallenge(hero);\n * if (detection.isChallenge) {\n * console.log(`Challenge detected: ${detection.type}`);\n * console.log(`Signals: ${detection.signals.join(', ')}`);\n * }\n */\nexport async function detectChallenge(hero: Hero): Promise<ChallengeDetection> {\n const signals: string[] = [];\n let type: ChallengeDetection[\"type\"] = \"none\";\n\n try {\n // Ensure we have access to document\n if (!hero.document) {\n return {\n isChallenge: false,\n type: \"none\",\n confidence: 0,\n signals: [\"No document available\"],\n };\n }\n\n const html = await hero.document.documentElement.outerHTML;\n const htmlLower = html.toLowerCase();\n\n // =========================================================================\n // CHECK 1: ACTIVE CHALLENGE DOM ELEMENTS\n // =========================================================================\n // These only exist during active challenges\n for (const selector of CHALLENGE_DOM_SELECTORS) {\n if (htmlLower.includes(selector.toLowerCase())) {\n signals.push(`Challenge element: ${selector}`);\n type = \"js_challenge\";\n }\n }\n\n // =========================================================================\n // CHECK 2: CHALLENGE-SPECIFIC TEXT\n // =========================================================================\n // These phrases only appear during active challenges\n for (const pattern of CHALLENGE_TEXT_PATTERNS) {\n if (htmlLower.includes(pattern)) {\n signals.push(`Challenge text: \"${pattern}\"`);\n type = type === \"none\" ? \"js_challenge\" : type;\n }\n }\n\n // =========================================================================\n // CHECK 3: \"WAITING FOR\" + \"TO RESPOND\"\n // =========================================================================\n // This specific combination only appears during challenges\n if (htmlLower.includes(\"waiting for\") && htmlLower.includes(\"to respond\")) {\n signals.push('Challenge text: \"waiting for...to respond\"');\n type = type === \"none\" ? \"js_challenge\" : type;\n }\n\n // =========================================================================\n // CHECK 4: BLOCKED/403 DETECTION\n // =========================================================================\n for (const pattern of BLOCKED_SIGNALS) {\n if (htmlLower.includes(pattern)) {\n signals.push(`Blocked: \"${pattern}\"`);\n type = \"blocked\";\n break; // One blocked signal is enough\n }\n }\n\n // Simple logic: If any signals found, it's a challenge\n const isChallenge = signals.length > 0;\n const confidence = isChallenge ? 100 : 0;\n\n return {\n isChallenge,\n type: isChallenge ? type : \"none\",\n confidence,\n signals,\n };\n } catch (error: any) {\n return {\n isChallenge: false,\n type: \"none\",\n confidence: 0,\n signals: [`Error during detection: ${error.message}`],\n };\n }\n}\n\n/**\n * Quick check - just returns boolean\n *\n * @param hero - Hero instance\n * @returns True if challenge page detected\n */\nexport async function isChallengePage(hero: Hero): Promise<boolean> {\n const detection = await detectChallenge(hero);\n return detection.isChallenge;\n}\n","import type Hero from \"@ulixee/hero\";\nimport { detectChallenge } from \"./detector\";\nimport type { ChallengeResolutionResult, ChallengeWaitOptions } from \"./types\";\n\n/**\n * Wait for Cloudflare challenge to resolve\n *\n * Uses multiple detection strategies:\n * 1. URL redirect detection (page redirects after challenge)\n * 2. Signal polling (challenge-specific elements/text disappear)\n *\n * @param hero - Hero instance with challenge page loaded\n * @param options - Waiting options\n * @returns Resolution result with method and time waited\n *\n * @example\n * const result = await waitForChallengeResolution(hero, {\n * maxWaitMs: 45000,\n * pollIntervalMs: 500,\n * verbose: true,\n * initialUrl: 'https://example.com'\n * });\n *\n * if (result.resolved) {\n * console.log(`Challenge resolved via ${result.method} in ${result.waitedMs}ms`);\n * }\n */\nexport async function waitForChallengeResolution(\n hero: Hero,\n options: ChallengeWaitOptions\n): Promise<ChallengeResolutionResult> {\n const { maxWaitMs = 45000, pollIntervalMs = 500, verbose = false, initialUrl } = options;\n\n const startTime = Date.now();\n const log = (msg: string) => verbose && console.log(` ${msg}`);\n\n while (Date.now() - startTime < maxWaitMs) {\n const elapsed = Date.now() - startTime;\n\n // =========================================================================\n // STRATEGY 1: Check for URL change (redirect after challenge)\n // =========================================================================\n try {\n const currentUrl = await hero.url;\n if (currentUrl !== initialUrl) {\n log(`✓ URL changed: ${initialUrl} → ${currentUrl}`);\n // Wait for the new page to fully load after redirect\n log(` Waiting for new page to load...`);\n try {\n await hero.waitForLoad(\"DomContentLoaded\", { timeoutMs: 30000 });\n log(` DOMContentLoaded`);\n } catch {\n log(` DOMContentLoaded timeout, continuing...`);\n }\n // Additional wait for JS to execute and render\n await hero.waitForPaintingStable().catch(() => {});\n log(` Page stabilized`);\n return { resolved: true, method: \"url_redirect\", waitedMs: elapsed };\n }\n } catch {\n // URL check failed, continue with other strategies\n }\n\n // =========================================================================\n // STRATEGY 2: Check if challenge signals are gone\n // =========================================================================\n const detection = await detectChallenge(hero);\n\n if (!detection.isChallenge) {\n log(`✓ Challenge signals cleared (confidence dropped to ${detection.confidence})`);\n // Wait for page to fully load after challenge clears\n log(` Waiting for page to load...`);\n try {\n await hero.waitForLoad(\"DomContentLoaded\", { timeoutMs: 30000 });\n log(` DOMContentLoaded`);\n } catch {\n log(` DOMContentLoaded timeout, continuing...`);\n }\n await hero.waitForPaintingStable().catch(() => {});\n log(` Page stabilized`);\n return { resolved: true, method: \"signals_cleared\", waitedMs: elapsed };\n }\n\n // Log progress\n log(\n `⏳ ${(elapsed / 1000).toFixed(1)}s - Still challenge (confidence: ${detection.confidence})`\n );\n\n // Wait before next poll\n await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));\n }\n\n // Timeout reached\n return {\n resolved: false,\n method: \"timeout\",\n waitedMs: Date.now() - startTime,\n };\n}\n\n/**\n * Wait for a specific CSS selector to appear\n *\n * Useful when you know exactly what element should appear after challenge.\n *\n * @param hero - Hero instance\n * @param selector - CSS selector to wait for\n * @param maxWaitMs - Maximum time to wait\n * @param verbose - Enable logging\n * @returns Whether selector was found and time waited\n *\n * @example\n * const result = await waitForSelector(hero, '.content', 30000, true);\n * if (result.found) {\n * console.log(`Content appeared after ${result.waitedMs}ms`);\n * }\n */\nexport async function waitForSelector(\n hero: Hero,\n selector: string,\n maxWaitMs: number,\n verbose: boolean = false\n): Promise<{ found: boolean; waitedMs: number }> {\n const startTime = Date.now();\n const log = (msg: string) => verbose && console.log(` ${msg}`);\n\n log(`Waiting for selector: \"${selector}\"`);\n\n while (Date.now() - startTime < maxWaitMs) {\n try {\n const element = await hero.document.querySelector(selector);\n if (element) {\n const elapsed = Date.now() - startTime;\n log(`✓ Selector found after ${(elapsed / 1000).toFixed(1)}s`);\n return { found: true, waitedMs: elapsed };\n }\n } catch {\n // Selector not found yet, continue\n }\n\n await new Promise((resolve) => setTimeout(resolve, 300));\n }\n\n log(`✗ Selector not found within timeout`);\n return { found: false, waitedMs: Date.now() - startTime };\n}\n\n/**\n * Handle Cloudflare challenge with automatic detection and waiting\n *\n * High-level function that combines detection and resolution.\n *\n * @param hero - Hero instance\n * @param options - Wait options (without initialUrl)\n * @returns Resolution result\n *\n * @example\n * await hero.goto('https://example.com');\n * const result = await handleChallenge(hero, { verbose: true });\n * if (result.resolved) {\n * // Challenge passed, continue scraping\n * }\n */\nexport async function handleChallenge(\n hero: Hero,\n options: Omit<ChallengeWaitOptions, \"initialUrl\"> = {}\n): Promise<ChallengeResolutionResult> {\n // Get current URL\n const initialUrl = await hero.url;\n\n // Detect challenge\n const detection = await detectChallenge(hero);\n\n if (!detection.isChallenge) {\n // No challenge, return immediately\n return { resolved: true, method: \"signals_cleared\", waitedMs: 0 };\n }\n\n // Challenge detected, wait for resolution\n return waitForChallengeResolution(hero, {\n ...options,\n initialUrl,\n });\n}\n","import TurndownService from \"turndown\";\nimport type { Page, WebsiteMetadata } from \"../types\";\n\n// Initialize Turndown service\nconst turndownService = new TurndownService({\n headingStyle: \"atx\",\n hr: \"---\",\n bulletListMarker: \"-\",\n codeBlockStyle: \"fenced\",\n fence: \"```\",\n emDelimiter: \"*\",\n strongDelimiter: \"**\",\n linkStyle: \"inlined\",\n linkReferenceStyle: \"full\",\n});\n\n/**\n * Convert pages to consolidated Markdown format\n */\nexport function formatToMarkdown(\n pages: Page[],\n baseUrl: string,\n scrapedAt: string,\n duration: number,\n website: WebsiteMetadata,\n includeMetadata: boolean = true\n): string {\n const sections: string[] = [];\n\n // Add header if metadata is included\n if (includeMetadata) {\n sections.push(createMarkdownHeader(baseUrl, scrapedAt, duration, website, pages.length));\n }\n\n // Add table of contents if multiple pages\n if (pages.length > 1) {\n sections.push(createMarkdownTOC(pages));\n }\n\n // Add page content\n sections.push(...pages.map((page, index) => createMarkdownPage(page, index + 1)));\n\n return sections.join(\"\\n\\n\");\n}\n\n/**\n * Create Markdown header with metadata\n */\nfunction createMarkdownHeader(\n baseUrl: string,\n scrapedAt: string,\n duration: number,\n website: WebsiteMetadata,\n totalPages: number\n): string {\n const title = website.title || extractDomainFromUrl(baseUrl);\n const description = website.description || \"\";\n\n let header = `# Website Scrape: ${title}\\n\\n`;\n\n header += `**Base URL:** ${baseUrl}\\n`;\n header += `**Scraped at:** ${new Date(scrapedAt).toLocaleString()}\\n`;\n header += `**Duration:** ${duration}ms\\n`;\n header += `**Total pages:** ${totalPages}\\n`;\n\n if (description) {\n header += `**Description:** ${description}\\n`;\n }\n\n if (website.author) {\n header += `**Author:** ${website.author}\\n`;\n }\n\n if (website.language) {\n header += `**Language:** ${website.language}\\n`;\n }\n\n return header;\n}\n\n/**\n * Create table of contents in Markdown\n */\nfunction createMarkdownTOC(pages: Page[]): string {\n let toc = \"## Table of Contents\\n\\n\";\n\n pages.forEach((page, index) => {\n const depth = \" \".repeat(page.depth);\n const pageNumber = index + 1;\n const title = page.title || `Page ${pageNumber}`;\n const cleanTitle = title.replace(/[#[\\]/\\\\:*?\"<>|]/g, \"\").trim();\n\n // Create anchor link\n const anchor = cleanTitle\n .toLowerCase()\n .replace(/\\s+/g, \"-\")\n .replace(/[^a-z0-9-]/g, \"\");\n toc += `${depth}${pageNumber}. [${title}](#page-${pageNumber}-${anchor})\\n`;\n });\n\n return toc;\n}\n\n/**\n * Create individual page content in Markdown\n */\nfunction createMarkdownPage(page: Page, pageNumber: number): string {\n const title = page.title || `Page ${pageNumber}`;\n const cleanTitle = title.replace(/[#[\\]/\\\\:*?\"<>|]/g, \"\").trim();\n const anchor = cleanTitle\n .toLowerCase()\n .replace(/\\s+/g, \"-\")\n .replace(/[^a-z0-9-]/g, \"\");\n\n let pageContent = `---\\n\\n`;\n pageContent += `## Page ${pageNumber}: ${title} {#page-${pageNumber}-${anchor}}\\n\\n`;\n\n pageContent += `**URL:** ${page.url}\\n`;\n pageContent += `**Title:** ${page.title}\\n`;\n pageContent += `**Depth:** ${page.depth}\\n`;\n pageContent += `**Fetched at:** ${new Date(page.fetchedAt).toLocaleString()}\\n\\n`;\n\n // Add horizontal rule\n pageContent += `---\\n\\n`;\n\n // Convert HTML to Markdown\n const markdown = htmlToMarkdown(page.html);\n pageContent += markdown;\n\n return pageContent;\n}\n\n/**\n * Convert HTML to Markdown using Turndown\n */\nfunction htmlToMarkdown(html: string): string {\n try {\n return turndownService.turndown(html);\n } catch (error) {\n console.warn(\"Error converting HTML to Markdown:\", error);\n // Fallback: extract text content\n return html.replace(/<[^>]*>/g, \"\").trim();\n }\n}\n\n/**\n * Create compact Markdown format for LLM consumption\n */\nexport function formatToCompactMarkdown(pages: Page[], website: WebsiteMetadata): string {\n const sections: string[] = [];\n\n // Add minimal header\n sections.push(`# ${website.title || extractDomainFromUrl(pages[0].url)}\\n`);\n\n if (website.description) {\n sections.push(`*${website.description}*\\n`);\n }\n\n // Add pages without metadata\n pages.forEach((page) => {\n const title = page.title || \"Untitled\";\n sections.push(`## ${title}\\n`);\n sections.push(`Source: ${page.url}\\n`);\n\n const markdown = htmlToMarkdown(page.html);\n sections.push(markdown);\n sections.push(\"\\n---\\n\");\n });\n\n return sections.join(\"\\n\");\n}\n\n/**\n * Create Markdown for individual pages\n */\nexport function formatPageToMarkdown(page: Page): string {\n const title = page.title || \"Untitled\";\n let markdown = `# ${title}\\n\\n`;\n\n markdown += `**URL:** ${page.url}\\n`;\n markdown += `**Fetched:** ${new Date(page.fetchedAt).toLocaleString()}\\n`;\n markdown += `**Depth:** ${page.depth}\\n\\n`;\n\n markdown += `---\\n\\n`;\n\n markdown += htmlToMarkdown(page.html);\n\n return markdown;\n}\n\n/**\n * Extract domain from URL for fallback title\n */\nfunction extractDomainFromUrl(url: string): string {\n try {\n return new URL(url).hostname;\n } catch {\n return \"Unknown\";\n }\n}\n\n/**\n * Create Markdown summary statistics\n */\nexport function createMarkdownStats(pages: Page[]): string {\n const totalWords = pages.reduce((sum, page) => sum + countWords(page.markdown), 0);\n const avgWords = Math.round(totalWords / pages.length);\n const totalReadingTime = Math.ceil(totalWords / 200); // 200 words per minute\n\n let stats = \"## Statistics\\n\\n\";\n stats += `- **Total pages:** ${pages.length}\\n`;\n stats += `- **Total words:** ${totalWords.toLocaleString()}\\n`;\n stats += `- **Average words per page:** ${avgWords.toLocaleString()}\\n`;\n stats += `- **Estimated reading time:** ${totalReadingTime} minutes\\n`;\n stats += `- **Crawl depth range:** ${Math.min(\n ...pages.map((p) => p.depth)\n )} - ${Math.max(...pages.map((p) => p.depth))}\\n`;\n\n return stats;\n}\n\n/**\n * Count words in markdown text\n */\nfunction countWords(markdown: string): number {\n // Remove markdown syntax\n const plainText = markdown\n .replace(/#{1,6}\\s+/g, \"\") // Headers\n .replace(/\\*\\*(.*?)\\*\\*/g, \"$1\") // Bold\n .replace(/\\*(.*?)\\*/g, \"$1\") // Italic\n .replace(/`(.*?)`/g, \"$1\") // Inline code\n .replace(/```[\\s\\S]*?```/g, \"\") // Code blocks\n .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\") // Links\n .replace(/!\\[([^\\]]*)\\]\\([^)]+\\)/g, \"$1\") // Images\n .replace(/^\\s*[-*+]\\s+/gm, \"\") // List items\n .replace(/^\\s*\\d+\\.\\s+/gm, \"\") // Numbered lists\n .replace(/^\\s*>\\s+/gm, \"\") // Blockquotes\n .replace(/\\n{3,}/g, \"\\n\\n\") // Multiple newlines\n .trim();\n\n // Split by whitespace and filter out empty strings\n return plainText.split(/\\s+/).filter((word) => word.length > 0).length;\n}\n","import type { Page, WebsiteMetadata } from \"../types\";\n\n/**\n * Convert pages to HTML format with metadata\n */\nexport function formatToHTML(\n pages: Page[],\n baseUrl: string,\n scrapedAt: string,\n duration: number,\n website: WebsiteMetadata\n): string {\n const html = `<!DOCTYPE html>\n<html lang=\"${website.language || \"en\"}\">\n<head>\n <meta charset=\"${website.charset || \"UTF-8\"}\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Scrape: ${website.title || extractDomainFromUrl(baseUrl)}</title>\n ${generateMetaTags(website)}\n <style>\n ${generateCSS()}\n </style>\n</head>\n<body>\n <header class=\"header\">\n <h1>Website Scrape: ${escapeHtml(website.title || extractDomainFromUrl(baseUrl))}</h1>\n <div class=\"meta-info\">\n <p><strong>Base URL:</strong> <a href=\"${escapeHtml(\n baseUrl\n )}\" target=\"_blank\">${escapeHtml(baseUrl)}</a></p>\n <p><strong>Scraped at:</strong> ${new Date(scrapedAt).toLocaleString()}</p>\n <p><strong>Duration:</strong> ${duration}ms</p>\n <p><strong>Total pages:</strong> ${pages.length}</p>\n ${\n website.description\n ? `<p><strong>Description:</strong> ${escapeHtml(website.description)}</p>`\n : \"\"\n }\n ${website.author ? `<p><strong>Author:</strong> ${escapeHtml(website.author)}</p>` : \"\"}\n ${\n website.language\n ? `<p><strong>Language:</strong> ${escapeHtml(website.language)}</p>`\n : \"\"\n }\n </div>\n </header>\n\n ${pages.length > 1 ? generateTOC(pages) : \"\"}\n\n <main class=\"content\">\n ${pages.map((page, index) => generatePageHTML(page, index + 1)).join(\"\\n\")}\n </main>\n\n <footer class=\"footer\">\n <p>Generated by Reader JS/TS SDK</p>\n </footer>\n\n <script>\n ${generateJavaScript()}\n </script>\n</body>\n</html>`;\n\n return html;\n}\n\n/**\n * Generate meta tags for HTML head\n */\nfunction generateMetaTags(website: WebsiteMetadata): string {\n const tags: string[] = [];\n\n if (website.description) {\n tags.push(`<meta name=\"description\" content=\"${escapeHtml(website.description)}\">`);\n }\n\n if (website.author) {\n tags.push(`<meta name=\"author\" content=\"${escapeHtml(website.author)}\">`);\n }\n\n if (website.keywords) {\n tags.push(`<meta name=\"keywords\" content=\"${escapeHtml(website.keywords.join(\", \"))}\">`);\n }\n\n if (website.robots) {\n tags.push(`<meta name=\"robots\" content=\"${escapeHtml(website.robots)}\">`);\n }\n\n if (website.themeColor) {\n tags.push(`<meta name=\"theme-color\" content=\"${escapeHtml(website.themeColor)}\">`);\n }\n\n if (website.favicon) {\n tags.push(`<link rel=\"icon\" href=\"${escapeHtml(website.favicon)}\">`);\n }\n\n if (website.canonical) {\n tags.push(`<link rel=\"canonical\" href=\"${escapeHtml(website.canonical)}\">`);\n }\n\n // Open Graph tags\n if (website.openGraph) {\n const og = website.openGraph;\n if (og.title) tags.push(`<meta property=\"og:title\" content=\"${escapeHtml(og.title)}\">`);\n if (og.description)\n tags.push(`<meta property=\"og:description\" content=\"${escapeHtml(og.description)}\">`);\n if (og.type) tags.push(`<meta property=\"og:type\" content=\"${escapeHtml(og.type)}\">`);\n if (og.url) tags.push(`<meta property=\"og:url\" content=\"${escapeHtml(og.url)}\">`);\n if (og.image) tags.push(`<meta property=\"og:image\" content=\"${escapeHtml(og.image)}\">`);\n if (og.siteName)\n tags.push(`<meta property=\"og:site_name\" content=\"${escapeHtml(og.siteName)}\">`);\n if (og.locale) tags.push(`<meta property=\"og:locale\" content=\"${escapeHtml(og.locale)}\">`);\n }\n\n // Twitter Card tags\n if (website.twitter) {\n const twitter = website.twitter;\n if (twitter.card) tags.push(`<meta name=\"twitter:card\" content=\"${escapeHtml(twitter.card)}\">`);\n if (twitter.site) tags.push(`<meta name=\"twitter:site\" content=\"${escapeHtml(twitter.site)}\">`);\n if (twitter.creator)\n tags.push(`<meta name=\"twitter:creator\" content=\"${escapeHtml(twitter.creator)}\">`);\n if (twitter.title)\n tags.push(`<meta name=\"twitter:title\" content=\"${escapeHtml(twitter.title)}\">`);\n if (twitter.description)\n tags.push(`<meta name=\"twitter:description\" content=\"${escapeHtml(twitter.description)}\">`);\n if (twitter.image)\n tags.push(`<meta name=\"twitter:image\" content=\"${escapeHtml(twitter.image)}\">`);\n }\n\n return tags.join(\"\\n \");\n}\n\n/**\n * Generate CSS styles for the HTML output\n */\nfunction generateCSS(): string {\n return `\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n line-height: 1.6;\n color: #333;\n background-color: #f8f9fa;\n }\n\n .header {\n background: white;\n padding: 2rem;\n border-bottom: 1px solid #e9ecef;\n margin-bottom: 2rem;\n }\n\n .header h1 {\n color: #2c3e50;\n margin-bottom: 1rem;\n font-size: 2rem;\n }\n\n .meta-info {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 0.5rem;\n }\n\n .meta-info p {\n margin: 0.25rem 0;\n font-size: 0.9rem;\n color: #6c757d;\n }\n\n .toc {\n background: white;\n padding: 1.5rem;\n margin: 2rem 0;\n border-radius: 8px;\n border: 1px solid #e9ecef;\n }\n\n .toc h2 {\n color: #2c3e50;\n margin-bottom: 1rem;\n font-size: 1.25rem;\n }\n\n .toc ul {\n list-style: none;\n }\n\n .toc li {\n margin: 0.5rem 0;\n }\n\n .toc a {\n color: #007bff;\n text-decoration: none;\n transition: color 0.2s;\n }\n\n .toc a:hover {\n color: #0056b3;\n text-decoration: underline;\n }\n\n .content {\n max-width: 800px;\n margin: 0 auto;\n padding: 0 1rem;\n }\n\n .page {\n background: white;\n margin: 2rem 0;\n padding: 2rem;\n border-radius: 8px;\n border: 1px solid #e9ecef;\n box-shadow: 0 2px 4px rgba(0,0,0,0.05);\n }\n\n .page-header {\n border-bottom: 2px solid #e9ecef;\n padding-bottom: 1rem;\n margin-bottom: 2rem;\n }\n\n .page-header h2 {\n color: #2c3e50;\n margin-bottom: 0.5rem;\n font-size: 1.5rem;\n }\n\n .page-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 1rem;\n font-size: 0.9rem;\n color: #6c757d;\n }\n\n .page-content {\n line-height: 1.8;\n }\n\n .page-content h1, .page-content h2, .page-content h3,\n .page-content h4, .page-content h5, .page-content h6 {\n color: #2c3e50;\n margin: 1.5rem 0 0.5rem 0;\n }\n\n .page-content p {\n margin: 1rem 0;\n }\n\n .page-content a {\n color: #007bff;\n text-decoration: none;\n }\n\n .page-content a:hover {\n text-decoration: underline;\n }\n\n .page-content code {\n background: #f8f9fa;\n padding: 0.2rem 0.4rem;\n border-radius: 4px;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 0.9em;\n }\n\n .page-content pre {\n background: #f8f9fa;\n padding: 1rem;\n border-radius: 4px;\n overflow-x: auto;\n margin: 1rem 0;\n }\n\n .page-content blockquote {\n border-left: 4px solid #007bff;\n padding-left: 1rem;\n margin: 1rem 0;\n color: #6c757d;\n }\n\n .footer {\n text-align: center;\n padding: 2rem;\n margin-top: 3rem;\n border-top: 1px solid #e9ecef;\n color: #6c757d;\n font-size: 0.9rem;\n }\n\n @media (max-width: 768px) {\n .header {\n padding: 1rem;\n }\n\n .header h1 {\n font-size: 1.5rem;\n }\n\n .page {\n padding: 1rem;\n }\n\n .page-meta {\n flex-direction: column;\n gap: 0.5rem;\n }\n }\n `.trim();\n}\n\n/**\n * Generate table of contents HTML\n */\nfunction generateTOC(pages: Page[]): string {\n const tocItems = pages\n .map((page, index) => {\n const pageNumber = index + 1;\n const title = page.title || `Page ${pageNumber}`;\n const id = `page-${pageNumber}`;\n return `<li><a href=\"#${id}\">${pageNumber}. ${escapeHtml(title)}</a></li>`;\n })\n .join(\"\\n\");\n\n return `\n <nav class=\"toc\">\n <h2>Table of Contents</h2>\n <ul>\n ${tocItems}\n </ul>\n </nav>`;\n}\n\n/**\n * Generate individual page HTML\n */\nfunction generatePageHTML(page: Page, pageNumber: number): string {\n const id = `page-${pageNumber}`;\n const title = page.title || `Page ${pageNumber}`;\n\n return `\n <article class=\"page\" id=\"${id}\">\n <div class=\"page-header\">\n <h2>${pageNumber}. ${escapeHtml(title)}</h2>\n <div class=\"page-meta\">\n <span><strong>URL:</strong> <a href=\"${escapeHtml(\n page.url\n )}\" target=\"_blank\">${escapeHtml(page.url)}</a></span>\n <span><strong>Depth:</strong> ${page.depth}</span>\n <span><strong>Fetched:</strong> ${new Date(page.fetchedAt).toLocaleString()}</span>\n </div>\n </div>\n <div class=\"page-content\">\n ${page.html}\n </div>\n </article>`;\n}\n\n/**\n * Generate JavaScript for interactive features\n */\nfunction generateJavaScript(): string {\n return `\n // Smooth scrolling for TOC links\n document.querySelectorAll('a[href^=\"#\"]').forEach(anchor => {\n anchor.addEventListener('click', function (e) {\n e.preventDefault();\n const target = document.querySelector(this.getAttribute('href'));\n if (target) {\n target.scrollIntoView({\n behavior: 'smooth',\n block: 'start'\n });\n }\n });\n });\n\n // Highlight current section in TOC\n window.addEventListener('scroll', function() {\n const pages = document.querySelectorAll('.page');\n const tocLinks = document.querySelectorAll('.toc a');\n \n let currentPage = null;\n pages.forEach(page => {\n const rect = page.getBoundingClientRect();\n if (rect.top <= 100) {\n currentPage = page;\n }\n });\n\n tocLinks.forEach(link => {\n link.style.fontWeight = 'normal';\n const target = document.querySelector(link.getAttribute('href'));\n if (target === currentPage) {\n link.style.fontWeight = 'bold';\n }\n });\n });\n `;\n}\n\n/**\n * Escape HTML characters to prevent XSS\n */\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\")\n .replace(/\\//g, \"&#x2F;\");\n}\n\n/**\n * Extract domain from URL for fallback title\n */\nfunction extractDomainFromUrl(url: string): string {\n try {\n return new URL(url).hostname;\n } catch {\n return \"Unknown\";\n }\n}\n\n/**\n * Generate a print-friendly version\n */\nexport function formatToPrintHTML(\n pages: Page[],\n baseUrl: string,\n scrapedAt: string,\n website: WebsiteMetadata\n): string {\n const printStyles = `\n @media print {\n .header, .toc, .footer {\n display: none;\n }\n \n .page {\n page-break-after: always;\n box-shadow: none;\n border: none;\n margin: 0;\n padding: 0;\n }\n \n body {\n background: white;\n font-size: 12pt;\n }\n }\n `;\n\n return formatToHTML(pages, baseUrl, scrapedAt, 0, website).replace(\n \"</style>\",\n `${printStyles}\\n </style>`\n );\n}\n","import type { Page, WebsiteMetadata } from \"../types\";\n\n/**\n * Convert pages to JSON format with metadata\n */\nexport function formatToJson(\n pages: Page[],\n baseUrl: string,\n scrapedAt: string,\n duration: number,\n website: WebsiteMetadata\n): string {\n const jsonResult = {\n metadata: {\n baseUrl,\n totalPages: pages.length,\n scrapedAt,\n duration,\n website,\n },\n pages: pages.map((page, index) => ({\n index: index + 1,\n url: page.url,\n title: page.title,\n markdown: page.markdown,\n html: page.html,\n fetchedAt: page.fetchedAt,\n depth: page.depth,\n wordCount: countWords(page.markdown),\n readingTime: estimateReadingTime(page.markdown),\n })),\n };\n\n return JSON.stringify(jsonResult, null, 2);\n}\n\n/**\n * Convert pages to JSON format without HTML (lighter version)\n */\nexport function formatToJsonLite(\n pages: Page[],\n baseUrl: string,\n scrapedAt: string,\n duration: number,\n website: WebsiteMetadata\n): string {\n const jsonResult = {\n metadata: {\n baseUrl,\n totalPages: pages.length,\n scrapedAt,\n duration,\n website,\n },\n pages: pages.map((page, index) => ({\n index: index + 1,\n url: page.url,\n title: page.title,\n markdown: page.markdown,\n fetchedAt: page.fetchedAt,\n depth: page.depth,\n wordCount: countWords(page.markdown),\n readingTime: estimateReadingTime(page.markdown),\n })),\n };\n\n return JSON.stringify(jsonResult, null, 2);\n}\n\n/**\n * Count words in markdown text\n */\nfunction countWords(markdown: string): number {\n // Remove markdown syntax\n const plainText = markdown\n .replace(/#{1,6}\\s+/g, \"\") // Headers\n .replace(/\\*\\*(.*?)\\*\\*/g, \"$1\") // Bold\n .replace(/\\*(.*?)\\*/g, \"$1\") // Italic\n .replace(/`(.*?)`/g, \"$1\") // Inline code\n .replace(/```[\\s\\S]*?```/g, \"\") // Code blocks\n .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\") // Links\n .replace(/!\\[([^\\]]*)\\]\\([^)]+\\)/g, \"$1\") // Images\n .replace(/^\\s*[-*+]\\s+/gm, \"\") // List items\n .replace(/^\\s*\\d+\\.\\s+/gm, \"\") // Numbered lists\n .replace(/^\\s*>\\s+/gm, \"\") // Blockquotes\n .replace(/\\n{3,}/g, \"\\n\\n\") // Multiple newlines\n .trim();\n\n // Split by whitespace and filter out empty strings\n return plainText.split(/\\s+/).filter((word) => word.length > 0).length;\n}\n\n/**\n * Estimate reading time in minutes (average 200 words per minute)\n */\nfunction estimateReadingTime(markdown: string): number {\n const wordCount = countWords(markdown);\n return Math.ceil(wordCount / 200);\n}\n\n/**\n * Create a table of contents for JSON output\n */\nexport function createJsonTOC(pages: Page[]): string {\n return JSON.stringify(\n {\n tableOfContents: pages.map((page, index) => ({\n index: index + 1,\n title: page.title,\n url: page.url,\n depth: page.depth,\n })),\n },\n null,\n 2\n );\n}\n","import { parseHTML } from \"linkedom\";\nimport type { Page, WebsiteMetadata } from \"../types\";\n\n/**\n * Convert pages to plain text format\n *\n * Strips all HTML tags and formatting, preserving only readable text content.\n * Useful for LLM consumption where markdown formatting is not needed.\n */\nexport function formatToText(\n pages: Page[],\n baseUrl: string,\n scrapedAt: string,\n duration: number,\n website: WebsiteMetadata,\n includeMetadata: boolean = true\n): string {\n const sections: string[] = [];\n\n // Add header if metadata is included\n if (includeMetadata) {\n sections.push(createTextHeader(baseUrl, scrapedAt, duration, website, pages.length));\n }\n\n // Add page content\n sections.push(...pages.map((page, index) => createTextPage(page, index + 1, pages.length > 1)));\n\n return sections.join(\"\\n\\n\");\n}\n\n/**\n * Create plain text header with metadata\n */\nfunction createTextHeader(\n baseUrl: string,\n scrapedAt: string,\n duration: number,\n website: WebsiteMetadata,\n totalPages: number\n): string {\n const title = website.title || extractDomainFromUrl(baseUrl);\n const lines: string[] = [];\n\n lines.push(`=== ${title} ===`);\n lines.push(\"\");\n lines.push(`URL: ${baseUrl}`);\n lines.push(`Scraped: ${new Date(scrapedAt).toLocaleString()}`);\n lines.push(`Duration: ${duration}ms`);\n lines.push(`Pages: ${totalPages}`);\n\n if (website.description) {\n lines.push(`Description: ${website.description}`);\n }\n\n if (website.author) {\n lines.push(`Author: ${website.author}`);\n }\n\n if (website.language) {\n lines.push(`Language: ${website.language}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Create individual page content in plain text\n */\nfunction createTextPage(page: Page, pageNumber: number, showSeparator: boolean): string {\n const lines: string[] = [];\n\n if (showSeparator) {\n lines.push(\"─\".repeat(60));\n lines.push(`Page ${pageNumber}: ${page.title || \"Untitled\"}`);\n lines.push(`URL: ${page.url}`);\n lines.push(\"─\".repeat(60));\n }\n\n // Convert HTML to plain text\n const plainText = htmlToPlainText(page.html);\n lines.push(plainText);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Convert HTML to plain text using DOM parsing\n *\n * - Removes scripts, styles, and other non-content elements\n * - Uses textContent for clean text extraction\n * - Handles HTML entities automatically\n */\nfunction htmlToPlainText(html: string): string {\n const { document } = parseHTML(html);\n\n // Remove non-content elements\n const elementsToRemove = [\"script\", \"style\", \"noscript\", \"svg\", \"canvas\", \"template\"];\n elementsToRemove.forEach((tag) => {\n document.querySelectorAll(tag).forEach((el: Element) => el.remove());\n });\n\n // Get text content - this automatically:\n // - Strips all HTML tags\n // - Decodes HTML entities\n // - Preserves text structure\n let text = document.body?.textContent || document.documentElement?.textContent || \"\";\n\n // Clean up whitespace\n text = text.replace(/[ \\t]+/g, \" \"); // Collapse horizontal whitespace\n text = text.replace(/\\n[ \\t]+/g, \"\\n\"); // Remove leading whitespace on lines\n text = text.replace(/[ \\t]+\\n/g, \"\\n\"); // Remove trailing whitespace on lines\n text = text.replace(/\\n{3,}/g, \"\\n\\n\"); // Collapse multiple newlines to max 2\n text = text.trim();\n\n return text;\n}\n\n/**\n * Extract domain from URL for fallback title\n */\nfunction extractDomainFromUrl(url: string): string {\n try {\n return new URL(url).hostname;\n } catch {\n return \"Unknown\";\n }\n}\n\n/**\n * Format a single page to plain text (utility export)\n */\nexport function formatPageToText(page: Page): string {\n const lines: string[] = [];\n\n lines.push(`=== ${page.title || \"Untitled\"} ===`);\n lines.push(`URL: ${page.url}`);\n lines.push(`Fetched: ${new Date(page.fetchedAt).toLocaleString()}`);\n lines.push(\"\");\n lines.push(htmlToPlainText(page.html));\n\n return lines.join(\"\\n\");\n}\n","import { parseHTML } from \"linkedom\";\nimport type { Page } from \"../types\";\n\n/**\n * HTML content cleaning utilities using DOM parsing\n */\n\n/**\n * Content cleaning options\n */\nexport interface CleaningOptions {\n /** Remove ads and tracking elements (default: true) */\n removeAds?: boolean;\n /** Remove base64-encoded images (default: true) */\n removeBase64Images?: boolean;\n}\n\n/**\n * Selectors for elements to always remove from content\n */\nconst ALWAYS_REMOVE_SELECTORS = [\n // Navigation and menus\n \"nav\",\n \"header nav\",\n \"footer nav\",\n \".nav\",\n \".navigation\",\n \".menu\",\n \".navbar\",\n \".sidebar\",\n \".aside\",\n\n // Header and footer elements\n \"header\",\n \"footer\",\n \".site-header\",\n \".page-header\",\n \".site-footer\",\n \".page-footer\",\n\n // Social media and sharing\n \".social\",\n \".share\",\n \".sharing\",\n \".twitter\",\n \".facebook\",\n \".linkedin\",\n \".instagram\",\n\n // Comments and discussions\n \".comments\",\n \".comment\",\n \".discussion\",\n \".disqus\",\n\n // Forms and interactive elements\n \"form\",\n \"input\",\n \"button:not([type='submit'])\",\n \"select\",\n \"textarea\",\n\n // Scripts and styles\n \"script\",\n \"style\",\n \"noscript\",\n\n // Hidden elements\n \"[hidden]\",\n \"[style*='display: none']\",\n \"[style*='display:none']\",\n\n // Common utility classes\n \".cookie\",\n \".cookie-banner\",\n \".popup\",\n \".modal\",\n \".overlay\",\n \".notification\",\n\n // Breadcrumbs\n \".breadcrumb\",\n \".breadcrumbs\",\n \".breadcrumb-trail\",\n];\n\n/**\n * Selectors for ad-related elements (only removed when removeAds is true)\n */\nconst AD_SELECTORS = [\n // Ads and promotions\n \".ad\",\n \".ads\",\n \".advertisement\",\n \".promotion\",\n \".sponsored\",\n \"[class*='ad-']\",\n \"[id*='ad-']\",\n \"[class*='advert']\",\n \"[id*='advert']\",\n \"[class*='banner']\",\n \"[id*='banner']\",\n \".google-ad\",\n \".adsense\",\n \"[data-ad]\",\n \"[data-ads]\",\n \"ins.adsbygoogle\",\n // Tracking\n \"[class*='tracking']\",\n \"[id*='tracking']\",\n \"[class*='analytics']\",\n \"[id*='analytics']\",\n];\n\n/**\n * Selectors for main content areas (in priority order)\n */\nconst CONTENT_SELECTORS = [\n \"main\",\n \"article\",\n \".content\",\n \".main-content\",\n \".post-content\",\n \".entry-content\",\n \".article-content\",\n \"[role='main']\",\n \".container\",\n \".wrapper\",\n];\n\n/**\n * Clean HTML content by removing unwanted elements\n * Uses proper DOM parsing instead of regex for reliable element removal\n */\nexport function cleanHtml(\n html: string,\n baseUrl: string,\n options: CleaningOptions = {}\n): string {\n const { removeAds = true, removeBase64Images = true } = options;\n const { document } = parseHTML(html);\n\n // Remove elements that are always unwanted\n for (const selector of ALWAYS_REMOVE_SELECTORS) {\n try {\n document.querySelectorAll(selector).forEach((el: Element) => el.remove());\n } catch {\n // Some selectors may not be supported, skip them\n }\n }\n\n // Remove ad-related elements if removeAds is true\n if (removeAds) {\n for (const selector of AD_SELECTORS) {\n try {\n document.querySelectorAll(selector).forEach((el: Element) => el.remove());\n } catch {\n // Some selectors may not be supported, skip them\n }\n }\n }\n\n // Remove base64 images if removeBase64Images is true\n if (removeBase64Images) {\n removeBase64ImagesFromDocument(document);\n }\n\n // Remove HTML comments by iterating through comment nodes\n const walker = document.createTreeWalker(document, 128 /* NodeFilter.SHOW_COMMENT */);\n const comments: Node[] = [];\n while (walker.nextNode()) {\n comments.push(walker.currentNode);\n }\n comments.forEach((comment) => comment.parentNode?.removeChild(comment));\n\n // Convert relative URLs to absolute\n convertRelativeUrls(document, baseUrl);\n\n return document.documentElement?.outerHTML || html;\n}\n\n/**\n * Remove base64-encoded images from the document\n */\nfunction removeBase64ImagesFromDocument(document: Document): void {\n // Remove img elements with base64 src\n document.querySelectorAll(\"img[src^='data:']\").forEach((el: Element) => {\n el.remove();\n });\n\n // Remove elements with base64 background images in style attribute\n document.querySelectorAll(\"[style*='data:image']\").forEach((el: Element) => {\n const style = el.getAttribute(\"style\");\n if (style) {\n // Remove just the background-image property, not the whole element\n const cleanedStyle = style.replace(/background(-image)?:\\s*url\\([^)]*data:image[^)]*\\)[^;]*;?/gi, \"\");\n if (cleanedStyle.trim()) {\n el.setAttribute(\"style\", cleanedStyle);\n } else {\n el.removeAttribute(\"style\");\n }\n }\n });\n\n // Remove source elements with base64 src/srcset\n document.querySelectorAll(\"source[src^='data:'], source[srcset*='data:']\").forEach((el: Element) => {\n el.remove();\n });\n}\n\n/**\n * Convert relative URLs to absolute URLs in the document\n */\nfunction convertRelativeUrls(document: Document, baseUrl: string): void {\n // Convert src attributes\n document.querySelectorAll(\"[src]\").forEach((el: Element) => {\n const src = el.getAttribute(\"src\");\n if (src && !src.startsWith(\"http\") && !src.startsWith(\"//\") && !src.startsWith(\"data:\")) {\n try {\n el.setAttribute(\"src\", new URL(src, baseUrl).toString());\n } catch {\n // Invalid URL, leave as-is\n }\n }\n });\n\n // Convert href attributes\n document.querySelectorAll(\"[href]\").forEach((el: Element) => {\n const href = el.getAttribute(\"href\");\n if (\n href &&\n !href.startsWith(\"http\") &&\n !href.startsWith(\"//\") &&\n !href.startsWith(\"#\") &&\n !href.startsWith(\"mailto:\") &&\n !href.startsWith(\"tel:\") &&\n !href.startsWith(\"javascript:\")\n ) {\n try {\n el.setAttribute(\"href\", new URL(href, baseUrl).toString());\n } catch {\n // Invalid URL, leave as-is\n }\n }\n });\n}\n\n/**\n * Extract the main content from HTML\n * Tries to find the main content area using common selectors\n */\nexport function extractMainContent(\n html: string,\n baseUrl: string,\n options: CleaningOptions = {}\n): string {\n const cleanedHtml = cleanHtml(html, baseUrl, options);\n const { document } = parseHTML(cleanedHtml);\n\n // Try to find main content areas in priority order\n for (const selector of CONTENT_SELECTORS) {\n try {\n const element = document.querySelector(selector);\n if (element && element.innerHTML.trim().length > 100) {\n // Return the inner HTML of the content area\n return element.innerHTML;\n }\n } catch {\n // Selector not supported, continue\n }\n }\n\n // If no specific content area found, return the body content or full cleaned HTML\n return document.body?.innerHTML || cleanedHtml;\n}\n\n/**\n * Clean HTML content (alias for cleanHtml with options)\n */\nexport function cleanContent(\n html: string,\n baseUrl: string,\n options: CleaningOptions = {}\n): string {\n return cleanHtml(html, baseUrl, options);\n}\n\n/**\n * Clean and process page content\n */\nexport function processPageContent(\n page: Page,\n baseUrl: string,\n options: CleaningOptions = {}\n): Page {\n const cleanedHtml = extractMainContent(page.html, baseUrl, options);\n\n return {\n ...page,\n html: cleanedHtml,\n };\n}\n","import { parseHTML } from \"linkedom\";\nimport type { WebsiteMetadata } from \"../types\";\nimport { normalizeUrl } from \"./url-helpers\";\n\n/**\n * Extract comprehensive website metadata from HTML content\n * Uses proper DOM parsing for reliable attribute extraction\n */\nexport function extractMetadata(html: string, baseUrl: string): WebsiteMetadata {\n return extractWebsiteMetadata(html, baseUrl);\n}\n\n/**\n * Extract comprehensive website metadata from HTML content\n */\nexport function extractWebsiteMetadata(html: string, baseUrl: string): WebsiteMetadata {\n const { document } = parseHTML(html);\n\n const metadata: WebsiteMetadata = {\n title: null,\n description: null,\n author: null,\n language: null,\n charset: null,\n favicon: null,\n canonical: null,\n image: null,\n keywords: null,\n robots: null,\n themeColor: null,\n openGraph: null,\n twitter: null,\n };\n\n // Extract basic meta tags\n metadata.title = extractTitle(document);\n metadata.description = extractMetaContent(document, \"description\");\n metadata.author = extractMetaContent(document, \"author\");\n metadata.language = extractLanguage(document);\n metadata.charset = extractCharset(document);\n\n // Extract links\n metadata.favicon = extractFavicon(document, baseUrl);\n metadata.canonical = extractCanonical(document, baseUrl);\n metadata.image =\n extractMetaContent(document, \"og:image\") || extractMetaContent(document, \"twitter:image\");\n\n // Extract SEO metadata\n metadata.keywords = extractKeywords(document);\n metadata.robots = extractMetaContent(document, \"robots\");\n metadata.themeColor = extractMetaContent(document, \"theme-color\");\n\n // Extract Open Graph metadata\n metadata.openGraph = extractOpenGraph(document);\n\n // Extract Twitter Card metadata\n metadata.twitter = extractTwitterCard(document);\n\n return metadata;\n}\n\n/**\n * Extract page title from HTML\n */\nfunction extractTitle(document: Document): string | null {\n // Try <title> tag first\n const titleElement = document.querySelector(\"title\");\n if (titleElement?.textContent) {\n return titleElement.textContent.trim();\n }\n\n // Fallback to og:title\n return extractMetaContent(document, \"og:title\");\n}\n\n/**\n * Extract content from meta tag by name or property\n * Works regardless of attribute order\n */\nfunction extractMetaContent(document: Document, name: string): string | null {\n // Try name attribute first\n const byName = document.querySelector(`meta[name=\"${name}\"]`);\n if (byName) {\n const content = byName.getAttribute(\"content\");\n if (content) return content.trim();\n }\n\n // Try property attribute (for Open Graph)\n const byProperty = document.querySelector(`meta[property=\"${name}\"]`);\n if (byProperty) {\n const content = byProperty.getAttribute(\"content\");\n if (content) return content.trim();\n }\n\n return null;\n}\n\n/**\n * Extract language from HTML tag\n */\nfunction extractLanguage(document: Document): string | null {\n const lang = document.documentElement?.getAttribute(\"lang\");\n return lang?.trim() || null;\n}\n\n/**\n * Extract character set from meta tag\n */\nfunction extractCharset(document: Document): string | null {\n // Try <meta charset=\"...\">\n const charsetMeta = document.querySelector(\"meta[charset]\");\n if (charsetMeta) {\n const charset = charsetMeta.getAttribute(\"charset\");\n if (charset) return charset.trim();\n }\n\n // Try <meta http-equiv=\"Content-Type\" content=\"...charset=...\">\n const httpEquivMeta = document.querySelector('meta[http-equiv=\"Content-Type\"]');\n if (httpEquivMeta) {\n const content = httpEquivMeta.getAttribute(\"content\");\n if (content) {\n const charsetMatch = content.match(/charset=([^\\s;]+)/i);\n if (charsetMatch) return charsetMatch[1].trim();\n }\n }\n\n return null;\n}\n\n/**\n * Extract favicon URL\n */\nfunction extractFavicon(document: Document, baseUrl: string): string | null {\n // Try various icon link types\n const iconSelectors = [\n 'link[rel=\"icon\"]',\n 'link[rel=\"shortcut icon\"]',\n 'link[rel=\"apple-touch-icon\"]',\n 'link[rel*=\"icon\"]',\n ];\n\n for (const selector of iconSelectors) {\n const iconLink = document.querySelector(selector);\n if (iconLink) {\n const href = iconLink.getAttribute(\"href\");\n if (href) {\n return normalizeUrl(href, baseUrl);\n }\n }\n }\n\n // Fallback to /favicon.ico\n try {\n return normalizeUrl(\"/favicon.ico\", baseUrl);\n } catch {\n return null;\n }\n}\n\n/**\n * Extract canonical URL\n */\nfunction extractCanonical(document: Document, baseUrl: string): string | null {\n const canonicalLink = document.querySelector('link[rel=\"canonical\"]');\n if (canonicalLink) {\n const href = canonicalLink.getAttribute(\"href\");\n if (href) {\n return normalizeUrl(href, baseUrl);\n }\n }\n\n return null;\n}\n\n/**\n * Extract keywords from meta tag\n */\nfunction extractKeywords(document: Document): string[] | null {\n const keywordsContent = extractMetaContent(document, \"keywords\");\n if (!keywordsContent) {\n return null;\n }\n\n return keywordsContent\n .split(\",\")\n .map((keyword) => keyword.trim())\n .filter((keyword) => keyword.length > 0);\n}\n\n/**\n * Extract Open Graph metadata\n */\nfunction extractOpenGraph(document: Document): WebsiteMetadata[\"openGraph\"] {\n const openGraph: WebsiteMetadata[\"openGraph\"] = {\n title: null,\n description: null,\n type: null,\n url: null,\n image: null,\n siteName: null,\n locale: null,\n };\n\n openGraph.title = extractMetaContent(document, \"og:title\");\n openGraph.description = extractMetaContent(document, \"og:description\");\n openGraph.type = extractMetaContent(document, \"og:type\");\n openGraph.url = extractMetaContent(document, \"og:url\");\n openGraph.image = extractMetaContent(document, \"og:image\");\n openGraph.siteName = extractMetaContent(document, \"og:site_name\");\n openGraph.locale = extractMetaContent(document, \"og:locale\");\n\n // Return null if no Open Graph data found\n if (Object.values(openGraph).every((value) => !value)) {\n return null;\n }\n\n return openGraph;\n}\n\n/**\n * Extract Twitter Card metadata\n */\nfunction extractTwitterCard(document: Document): WebsiteMetadata[\"twitter\"] {\n const twitter: WebsiteMetadata[\"twitter\"] = {\n card: null,\n site: null,\n creator: null,\n title: null,\n description: null,\n image: null,\n };\n\n twitter.card = extractMetaContent(document, \"twitter:card\");\n twitter.site = extractMetaContent(document, \"twitter:site\");\n twitter.creator = extractMetaContent(document, \"twitter:creator\");\n twitter.title = extractMetaContent(document, \"twitter:title\");\n twitter.description = extractMetaContent(document, \"twitter:description\");\n twitter.image = extractMetaContent(document, \"twitter:image\");\n\n // Return null if no Twitter Card data found\n if (Object.values(twitter).every((value) => !value)) {\n return null;\n }\n\n return twitter;\n}\n\n/**\n * Extract structured data (JSON-LD) from HTML\n */\nexport function extractStructuredData(html: string): unknown[] {\n const { document } = parseHTML(html);\n const structuredData: unknown[] = [];\n\n document.querySelectorAll('script[type=\"application/ld+json\"]').forEach((script: Element) => {\n try {\n const jsonData = JSON.parse(script.textContent || \"\");\n structuredData.push(jsonData);\n } catch {\n // Invalid JSON, skip\n }\n });\n\n return structuredData;\n}\n\n/**\n * Extract microdata from HTML (basic implementation)\n */\nexport function extractMicrodata(_html: string): unknown[] {\n const microdata: unknown[] = [];\n // This is a simplified implementation\n // In a real-world scenario, you'd want to use a proper microdata parser\n return microdata;\n}\n\n/**\n * Get a summary of the website metadata for debugging\n */\nexport function getMetadataSummary(metadata: WebsiteMetadata): string {\n const parts: string[] = [];\n\n if (metadata.title) parts.push(`Title: ${metadata.title}`);\n if (metadata.description) parts.push(`Description: ${metadata.description.substring(0, 100)}...`);\n if (metadata.author) parts.push(`Author: ${metadata.author}`);\n if (metadata.language) parts.push(`Language: ${metadata.language}`);\n if (metadata.keywords) parts.push(`Keywords: ${metadata.keywords.length} found`);\n if (metadata.openGraph)\n parts.push(`Open Graph: ${Object.keys(metadata.openGraph).length} fields`);\n if (metadata.twitter) parts.push(`Twitter Card: ${Object.keys(metadata.twitter).length} fields`);\n\n return parts.join(\" | \") || \"No metadata found\";\n}\n","import { URL } from \"url\";\nimport RE2 from \"re2\";\n\n/**\n * URL validation and normalization utilities\n */\n\n/**\n * Resolve a relative URL against a base URL\n */\nexport function resolveUrl(relative: string, base: string): string {\n try {\n return new URL(relative, base).toString();\n } catch {\n return relative;\n }\n}\n\n/**\n * Validate if a string is a valid URL\n */\nexport function isValidUrl(string: string): boolean {\n try {\n new URL(string);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Normalize a URL by removing fragments and ensuring proper format\n */\nexport function normalizeUrl(url: string, baseUrl?: string): string {\n try {\n let parsedUrl: URL;\n\n if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) {\n parsedUrl = new URL(url);\n } else if (baseUrl) {\n parsedUrl = new URL(url, baseUrl);\n } else {\n throw new Error(\"Relative URL requires base URL\");\n }\n\n // Remove fragment and search params for consistency\n parsedUrl.hash = \"\";\n\n return parsedUrl.toString();\n } catch {\n throw new Error(`Invalid URL: ${url}`);\n }\n}\n\n/**\n * Extract base domain from a URL\n */\nexport function extractBaseDomain(url: string): string {\n try {\n const parsedUrl = new URL(url);\n return parsedUrl.hostname;\n } catch {\n throw new Error(`Invalid URL for domain extraction: ${url}`);\n }\n}\n\n/**\n * Extract the root domain from a hostname (e.g., \"blog.example.com\" -> \"example.com\")\n */\nfunction getRootDomain(hostname: string): string {\n const parts = hostname.split(\".\");\n\n // Handle edge cases\n if (parts.length <= 2) {\n return hostname;\n }\n\n // Handle common two-part TLDs (co.uk, com.au, etc.)\n const twoPartTLDs = [\"co.uk\", \"com.au\", \"co.nz\", \"com.br\", \"co.jp\", \"co.kr\", \"com.mx\", \"org.uk\"];\n const lastTwo = parts.slice(-2).join(\".\");\n\n if (twoPartTLDs.includes(lastTwo)) {\n // Return last 3 parts for two-part TLDs\n return parts.slice(-3).join(\".\");\n }\n\n // Standard case: return last 2 parts\n return parts.slice(-2).join(\".\");\n}\n\n/**\n * Check if a URL belongs to the same domain as the base URL\n * Supports subdomains: blog.example.com matches example.com\n */\nexport function isSameDomain(url: string, baseUrl: string): boolean {\n try {\n const urlDomain = extractBaseDomain(url);\n const baseDomain = extractBaseDomain(baseUrl);\n\n // Exact match\n if (urlDomain === baseDomain) {\n return true;\n }\n\n // Check if URL is a subdomain of base domain\n // e.g., \"blog.example.com\" should match \"example.com\"\n const urlRoot = getRootDomain(urlDomain);\n const baseRoot = getRootDomain(baseDomain);\n\n return urlRoot === baseRoot;\n } catch {\n return false;\n }\n}\n\n/**\n * Generate a URL key for deduplication\n */\nexport function getUrlKey(url: string): string {\n try {\n const parsedUrl = new URL(url);\n // Remove search params for consistency\n parsedUrl.search = \"\";\n return parsedUrl.toString().toLowerCase();\n } catch {\n return url.toLowerCase();\n }\n}\n\n/**\n * Validate an array of URLs and return validation results\n */\nexport function validateUrls(urls: string[]): {\n isValid: boolean;\n validUrls: string[];\n errors: Array<{ url: string; error: string }>;\n} {\n const validUrls: string[] = [];\n const errors: Array<{ url: string; error: string }> = [];\n\n if (!urls || urls.length === 0) {\n return {\n isValid: false,\n validUrls: [],\n errors: [{ url: \"\", error: \"At least one URL is required\" }],\n };\n }\n\n for (const url of urls) {\n if (!url || typeof url !== \"string\") {\n errors.push({\n url: String(url),\n error: \"URL must be a non-empty string\",\n });\n continue;\n }\n\n const trimmedUrl = url.trim();\n if (trimmedUrl === \"\") {\n errors.push({ url: String(url), error: \"URL cannot be empty\" });\n continue;\n }\n\n if (!isValidUrl(trimmedUrl)) {\n errors.push({ url: trimmedUrl, error: \"Invalid URL format\" });\n continue;\n }\n\n if (!trimmedUrl.startsWith(\"http://\") && !trimmedUrl.startsWith(\"https://\")) {\n errors.push({\n url: trimmedUrl,\n error: \"URL must start with http:// or https://\",\n });\n continue;\n }\n\n validUrls.push(trimmedUrl);\n }\n\n // Remove duplicates while preserving order\n const uniqueValidUrls = Array.from(new Set(validUrls));\n\n return {\n isValid: uniqueValidUrls.length > 0 && errors.length === 0,\n validUrls: uniqueValidUrls,\n errors,\n };\n}\n\n/**\n * Check if a URL matches any of the given regex patterns\n *\n * Uses Google's RE2 engine which guarantees linear time execution,\n * preventing ReDoS attacks from malicious or pathological patterns.\n */\nexport function matchesPatterns(url: string, patterns: string[]): boolean {\n if (!patterns || patterns.length === 0) {\n return false;\n }\n\n return patterns.some((pattern) => {\n try {\n const regex = new RE2(pattern, \"i\");\n return regex.test(url);\n } catch {\n // Invalid regex pattern or unsupported RE2 syntax, skip it\n return false;\n }\n });\n}\n\n/**\n * Check if a URL should be included based on include/exclude patterns\n * - If includePatterns is set, URL must match at least one\n * - If excludePatterns is set, URL must not match any\n */\nexport function shouldIncludeUrl(\n url: string,\n includePatterns?: string[],\n excludePatterns?: string[]\n): boolean {\n // If include patterns are specified, URL must match at least one\n if (includePatterns && includePatterns.length > 0) {\n if (!matchesPatterns(url, includePatterns)) {\n return false;\n }\n }\n\n // If exclude patterns are specified, URL must not match any\n if (excludePatterns && excludePatterns.length > 0) {\n if (matchesPatterns(url, excludePatterns)) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Check if a URL is likely a content page (not legal, policy, or utility page)\n * Used by crawler to filter out non-content pages\n */\nexport function isContentUrl(url: string): boolean {\n const lowerUrl = url.toLowerCase();\n\n // Skip legal and policy pages\n const nonContentPatterns = [\n // Legal and policy pages\n /\\/(privacy|terms|tos|legal|cookie|gdpr|disclaimer|imprint|impressum)\\b/i,\n /\\/(privacy-policy|terms-of-service|terms-of-use|terms-and-conditions)\\b/i,\n /\\/(cookie-policy|data-protection|acceptable-use|user-agreement)\\b/i,\n /\\/(refund|cancellation|shipping|return)-?(policy)?\\b/i,\n // Contact and support pages (usually not main content)\n /\\/(contact|support|help|faq|feedback)\\/?$/i,\n // About pages that are typically boilerplate\n /\\/(about-us|careers|jobs|press|investors|team)\\/?$/i,\n // Authentication and admin areas\n /\\/(admin|login|auth|account|dashboard|profile|settings)\\//i,\n // E-commerce utility pages\n /\\/(cart|checkout|payment|subscription|wishlist)\\//i,\n // File downloads and assets\n /\\/(uploads|assets|files|static|media|resources)\\//i,\n // API endpoints\n /\\/(api|graphql|rest|webhook)\\//i,\n ];\n\n if (nonContentPatterns.some((pattern) => pattern.test(lowerUrl))) {\n return false;\n }\n\n // Skip common non-content file extensions\n const skipExtensions = [\".pdf\", \".doc\", \".docx\", \".xls\", \".xlsx\", \".zip\", \".exe\"];\n if (skipExtensions.some((ext) => lowerUrl.endsWith(ext))) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Check if a URL should be crawled based on various criteria\n */\nexport function shouldCrawlUrl(\n url: string,\n baseUrl: string,\n maxDepth: number,\n currentDepth: number,\n visited: Set<string>\n): boolean {\n // Check depth limit - FIXED: use > instead of >=\n if (currentDepth > maxDepth) {\n return false;\n }\n\n // Check if already visited\n const urlKey = getUrlKey(url);\n if (visited.has(urlKey)) {\n return false;\n }\n\n // Check if same domain\n if (!isSameDomain(url, baseUrl)) {\n return false;\n }\n\n // Enhanced filtering for non-content files and patterns\n const lowerUrl = url.toLowerCase();\n\n // Skip common non-content file extensions\n const skipExtensions = [\n \".pdf\",\n \".doc\",\n \".docx\",\n \".xls\",\n \".xlsx\",\n \".ppt\",\n \".pptx\",\n \".zip\",\n \".rar\",\n \".tar\",\n \".gz\",\n \".exe\",\n \".dmg\",\n \".pkg\",\n \".deb\",\n \".rpm\",\n \".apk\",\n \".ipa\",\n // Image files\n \".jpg\",\n \".jpeg\",\n \".png\",\n \".gif\",\n \".bmp\",\n \".svg\",\n \".webp\",\n \".ico\",\n \".favicon\",\n // Video files\n \".mp4\",\n \".avi\",\n \".mov\",\n \".wmv\",\n \".flv\",\n \".webm\",\n // Audio files\n \".mp3\",\n \".wav\",\n \".ogg\",\n \".m4a\",\n \".aac\",\n // Font files\n \".woff\",\n \".woff2\",\n \".ttf\",\n \".otf\",\n \".eot\",\n // Style and script files\n \".css\",\n \".js\",\n \".mjs\",\n \".ts\",\n \".jsx\",\n \".tsx\",\n // Data and config files\n \".json\",\n \".xml\",\n \".txt\",\n \".md\",\n \".rss\",\n \".atom\",\n \".sitemap\",\n \".robots\",\n \".webmanifest\",\n // Archive files\n \".zip\",\n \".tar\",\n \".gz\",\n \".bz2\",\n \".7z\",\n ];\n\n if (skipExtensions.some((ext) => lowerUrl.includes(ext))) {\n return false;\n }\n\n // Skip common non-content URL patterns\n const skipPatterns = [\n // File downloads and assets\n /\\/(uploads|assets|files|static|media|resources)\\//i,\n // Authentication and admin areas\n /\\/(admin|login|auth|account|dashboard|profile|settings)\\//i,\n // API endpoints\n /\\/(api|graphql|rest|ws:|webhook)\\//i,\n // Common tracking and analytics\n /\\/(analytics|tracking|pixel|beacon|ads)\\//i,\n // Development and testing areas\n /\\/(test|dev|staging|beta|demo)\\//i,\n // Common utility and service pages\n /\\/(search|cart|checkout|payment|subscription)\\//i,\n // Social media and external services\n /\\/(facebook|twitter|instagram|youtube|linkedin|github)\\//i,\n // Legal and policy pages\n /\\/(privacy|terms|tos|legal|cookie|gdpr|disclaimer|imprint|impressum)\\b/i,\n /\\/(privacy-policy|terms-of-service|terms-of-use|terms-and-conditions)\\b/i,\n /\\/(cookie-policy|data-protection|acceptable-use|user-agreement)\\b/i,\n /\\/(refund|cancellation|shipping|return)-?(policy)?\\b/i,\n // Contact and support pages (usually not main content)\n /\\/(contact|support|help|faq|feedback)\\/?$/i,\n // About pages that are typically boilerplate\n /\\/(about-us|careers|jobs|press|investors|team)\\/?$/i,\n ];\n\n if (skipPatterns.some((pattern) => pattern.test(url))) {\n return false;\n }\n\n // Skip URLs with query parameters that indicate non-content\n if (\n url.includes(\"?\") &&\n [\"download\", \"file\", \"attachment\", \"export\", \"print\", \"share\", \"email\"].some((param) =>\n url.toLowerCase().includes(param)\n )\n ) {\n return false;\n }\n\n // Skip very short URLs (likely navigation or utility)\n if (url.split(\"/\").filter(Boolean).length < 2 && url.split(\"?\")[0].split(\"/\").length <= 2) {\n return false;\n }\n\n return true;\n}\n","import pino from \"pino\";\n\n/**\n * Create a logger instance\n *\n * @param name - Logger name\n * @param level - Log level (default: from env or 'info')\n * @returns Pino logger instance\n */\nexport function createLogger(\n name: string = \"reader\",\n level: string = process.env.LOG_LEVEL || \"info\"\n) {\n return pino({\n name,\n level,\n transport:\n process.env.NODE_ENV !== \"production\"\n ? {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"SYS:standard\",\n ignore: \"pid,hostname\",\n },\n }\n : undefined,\n });\n}\n\n/**\n * Default logger instance\n */\nexport const logger = createLogger();\n","/**\n * Simple robots.txt parser for crawler compliance\n */\n\nexport interface RobotsRules {\n disallowedPaths: string[];\n allowedPaths: string[];\n crawlDelay: number | null;\n}\n\n/**\n * Parse robots.txt content and extract rules for a specific user agent\n */\nexport function parseRobotsTxt(content: string, userAgent: string = \"*\"): RobotsRules {\n const rules: RobotsRules = {\n disallowedPaths: [],\n allowedPaths: [],\n crawlDelay: null,\n };\n\n const lines = content.split(\"\\n\").map((line) => line.trim());\n let currentUserAgent = \"\";\n let matchesUserAgent = false;\n\n for (const line of lines) {\n // Skip empty lines and comments\n if (!line || line.startsWith(\"#\")) {\n continue;\n }\n\n const colonIndex = line.indexOf(\":\");\n if (colonIndex === -1) {\n continue;\n }\n\n const directive = line.substring(0, colonIndex).trim().toLowerCase();\n const value = line.substring(colonIndex + 1).trim();\n\n if (directive === \"user-agent\") {\n currentUserAgent = value.toLowerCase();\n // Match specific user agent or wildcard\n matchesUserAgent = currentUserAgent === \"*\" || currentUserAgent === userAgent.toLowerCase();\n } else if (matchesUserAgent) {\n if (directive === \"disallow\" && value) {\n rules.disallowedPaths.push(value);\n } else if (directive === \"allow\" && value) {\n rules.allowedPaths.push(value);\n } else if (directive === \"crawl-delay\") {\n const delay = parseFloat(value);\n if (!isNaN(delay)) {\n rules.crawlDelay = delay * 1000; // Convert to milliseconds\n }\n }\n }\n }\n\n return rules;\n}\n\n/**\n * Check if a URL path is allowed by robots.txt rules\n */\nexport function isPathAllowed(path: string, rules: RobotsRules): boolean {\n // Normalize path\n const normalizedPath = path.startsWith(\"/\") ? path : \"/\" + path;\n\n // Check allow rules first (they take precedence)\n for (const allowedPath of rules.allowedPaths) {\n if (pathMatches(normalizedPath, allowedPath)) {\n return true;\n }\n }\n\n // Check disallow rules\n for (const disallowedPath of rules.disallowedPaths) {\n if (pathMatches(normalizedPath, disallowedPath)) {\n return false;\n }\n }\n\n // Default: allowed\n return true;\n}\n\n/**\n * Check if a path matches a robots.txt pattern\n * Supports * (wildcard) and $ (end anchor)\n */\nfunction pathMatches(path: string, pattern: string): boolean {\n // Empty pattern matches nothing\n if (!pattern) {\n return false;\n }\n\n // Convert robots.txt pattern to regex\n let regexPattern = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\") // Escape regex special chars except * and $\n .replace(/\\*/g, \".*\"); // * becomes .*\n\n // Handle $ end anchor\n if (regexPattern.endsWith(\"\\\\$\")) {\n regexPattern = regexPattern.slice(0, -2) + \"$\";\n } else {\n regexPattern = \"^\" + regexPattern;\n }\n\n try {\n const regex = new RegExp(regexPattern);\n return regex.test(path);\n } catch {\n // Invalid pattern, treat as literal prefix match\n return path.startsWith(pattern);\n }\n}\n\n/**\n * Fetch and parse robots.txt for a given base URL\n */\nexport async function fetchRobotsTxt(baseUrl: string): Promise<RobotsRules | null> {\n try {\n const url = new URL(\"/robots.txt\", baseUrl);\n const response = await fetch(url.toString(), {\n headers: {\n \"User-Agent\": \"ReaderEngine/1.0\",\n },\n });\n\n if (!response.ok) {\n // No robots.txt or error - allow everything\n return null;\n }\n\n const content = await response.text();\n return parseRobotsTxt(content, \"ReaderEngine\");\n } catch {\n // Network error or invalid URL - allow everything\n return null;\n }\n}\n\n/**\n * Check if a URL is allowed by robots.txt\n */\nexport function isUrlAllowed(url: string, rules: RobotsRules | null): boolean {\n if (!rules) {\n return true;\n }\n\n try {\n const parsedUrl = new URL(url);\n return isPathAllowed(parsedUrl.pathname + parsedUrl.search, rules);\n } catch {\n return true;\n }\n}\n","import type { IBrowserPool } from \"./browser/types\";\n\n/**\n * Proxy configuration for Hero\n */\nexport interface ProxyConfig {\n /** Full proxy URL (takes precedence over other fields) */\n url?: string;\n /** Proxy type */\n type?: \"datacenter\" | \"residential\";\n /** Proxy username */\n username?: string;\n /** Proxy password */\n password?: string;\n /** Proxy host */\n host?: string;\n /** Proxy port */\n port?: number;\n /** Country code for residential proxies (e.g., 'us', 'uk') */\n country?: string;\n}\n\n/**\n * Proxy metadata in scrape results\n */\nexport interface ProxyMetadata {\n /** Proxy host that was used */\n host: string;\n /** Proxy port that was used */\n port: number;\n /** Country code if geo-targeting was used */\n country?: string;\n}\n\n/**\n * Browser pool configuration for ReaderClient\n */\nexport interface BrowserPoolConfig {\n /** Number of browser instances (default: 2) */\n size?: number;\n /** Retire browser after this many page loads (default: 100) */\n retireAfterPages?: number;\n /** Retire browser after this many minutes (default: 30) */\n retireAfterMinutes?: number;\n /** Maximum pending requests in queue (default: 100) */\n maxQueueSize?: number;\n}\n\n/**\n * Main scraping options interface\n */\nexport interface ScrapeOptions {\n /** Array of URLs to scrape */\n urls: string[];\n\n /** Output formats (default: ['markdown']) */\n formats?: Array<\"markdown\" | \"html\" | \"json\" | \"text\">;\n\n /** Include URL, title, timestamp (default: true) */\n includeMetadata?: boolean;\n\n /** Custom user agent string */\n userAgent?: string;\n\n /** Request timeout in milliseconds (default: 30000) */\n timeoutMs?: number;\n\n /** URL patterns to include (regex strings) */\n includePatterns?: string[];\n\n /** URL patterns to exclude (regex strings) */\n excludePatterns?: string[];\n\n // ============================================================================\n // Content cleaning options\n // ============================================================================\n\n /** Remove ads and tracking elements (default: true) */\n removeAds?: boolean;\n\n /** Remove base64-encoded images to reduce output size (default: true) */\n removeBase64Images?: boolean;\n\n /** Skip TLS/SSL certificate verification (default: true) */\n skipTLSVerification?: boolean;\n\n // ============================================================================\n // Batch processing options\n // ============================================================================\n\n /** Number of URLs to process in parallel (default: 1 - sequential) */\n batchConcurrency?: number;\n\n /** Total timeout for the entire batch operation in milliseconds (default: 300000) */\n batchTimeoutMs?: number;\n\n /** Maximum retry attempts for failed URLs (default: 2) */\n maxRetries?: number;\n\n /** Progress callback for batch operations */\n onProgress?: (progress: { completed: number; total: number; currentUrl: string }) => void;\n\n // ============================================================================\n // Hero-specific options\n // ============================================================================\n\n /** Proxy configuration for Hero */\n proxy?: ProxyConfig;\n\n /** CSS selector to wait for before considering page loaded */\n waitForSelector?: string;\n\n /** Enable verbose logging (default: false) */\n verbose?: boolean;\n\n /** Show Chrome window (default: false) */\n showChrome?: boolean;\n\n /** Connection to Hero Core (for shared Core usage) */\n connectionToCore?: any;\n\n /** Browser pool configuration (passed from ReaderClient) */\n browserPool?: BrowserPoolConfig;\n\n /** Browser pool instance (internal, provided by ReaderClient) */\n pool?: IBrowserPool;\n}\n\n/**\n * Website metadata extracted from the base page\n */\nexport interface WebsiteMetadata {\n /** Basic meta tags */\n title: string | null /** <title> or <meta property=\"og:title\"> */;\n description: string | null /** <meta name=\"description\"> */;\n author: string | null /** <meta name=\"author\"> */;\n language: string | null /** <html lang=\"...\"> */;\n charset: string | null /** <meta charset=\"...\"> */;\n\n /** Links */\n favicon: string | null /** <link rel=\"icon\"> */;\n image: string | null /** <meta property=\"og:image\"> */;\n canonical: string | null /** <link rel=\"canonical\"> */;\n\n /** SEO */\n keywords: string[] | null /** <meta name=\"keywords\"> */;\n robots: string | null /** <meta name=\"robots\"> */;\n\n /** Branding */\n themeColor: string | null /** <meta name=\"theme-color\"> */;\n\n /** Open Graph */\n openGraph: {\n title: string | null /** <meta property=\"og:title\"> */;\n description: string | null /** <meta property=\"og:description\"> */;\n type: string | null /** <meta property=\"og:type\"> */;\n url: string | null /** <meta property=\"og:url\"> */;\n image: string | null /** <meta property=\"og:image\"> */;\n siteName: string | null /** <meta property=\"og:site_name\"> */;\n locale: string | null /** <meta property=\"og:locale\"> */;\n } | null;\n\n /** Twitter Card */\n twitter: {\n card: string | null /** <meta name=\"twitter:card\"> */;\n site: string | null /** <meta name=\"twitter:site\"> */;\n creator: string | null /** <meta name=\"twitter:creator\"> */;\n title: string | null /** <meta name=\"twitter:title\"> */;\n description: string | null /** <meta name=\"twitter:description\"> */;\n image: string | null /** <meta name=\"twitter:image\"> */;\n } | null;\n}\n\n/**\n * Individual page data\n */\nexport interface Page {\n /** Full URL of the page */\n url: string;\n\n /** Page title */\n title: string;\n\n /** Markdown content */\n markdown: string;\n\n /** HTML content */\n html: string;\n\n /** When the page was fetched */\n fetchedAt: string;\n\n /** Crawl depth from base URL */\n depth: number;\n\n // ============================================================================\n // Hero-specific fields\n // ============================================================================\n\n /** Whether a Cloudflare challenge was detected */\n hadChallenge?: boolean;\n\n /** Type of challenge encountered */\n challengeType?: string;\n\n /** Time spent waiting for challenge resolution (ms) */\n waitTimeMs?: number;\n}\n\n/**\n * Individual website scrape result (for backward compatibility)\n */\nexport interface WebsiteScrapeResult {\n /** Markdown output (present if 'markdown' in formats) */\n markdown?: string;\n\n /** HTML output (present if 'html' in formats) */\n html?: string;\n\n /** JSON output (present if 'json' in formats) */\n json?: string;\n\n /** Plain text output (present if 'text' in formats) */\n text?: string;\n\n /** Metadata about the scraping operation */\n metadata: {\n /** Base URL that was scraped */\n baseUrl: string;\n\n /** Total number of pages scraped */\n totalPages: number;\n\n /** ISO timestamp when scraping started */\n scrapedAt: string;\n\n /** Duration in milliseconds */\n duration: number;\n\n /** Website metadata extracted from base page */\n website: WebsiteMetadata;\n\n /** Proxy used for this request (if proxy pooling was enabled) */\n proxy?: ProxyMetadata;\n };\n}\n\n/**\n * Batch metadata for multi-URL operations\n */\nexport interface BatchMetadata {\n /** Total number of URLs provided */\n totalUrls: number;\n\n /** Number of URLs successfully scraped */\n successfulUrls: number;\n\n /** Number of URLs that failed */\n failedUrls: number;\n\n /** ISO timestamp when the batch operation started */\n scrapedAt: string;\n\n /** Total duration for the entire batch in milliseconds */\n totalDuration: number;\n\n /** Array of errors for failed URLs */\n errors?: Array<{ url: string; error: string }>;\n}\n\n/**\n * Main scrape result interface\n */\nexport interface ScrapeResult {\n /** Array of individual website results */\n data: WebsiteScrapeResult[];\n\n /** Metadata about the batch operation */\n batchMetadata: BatchMetadata;\n}\n\n/**\n * Internal crawler state\n */\nexport interface CrawlerState {\n /** Set of visited URLs to avoid duplicates */\n visited: Set<string>;\n\n /** Queue of URLs to process */\n queue: Array<{ url: string; depth: number }>;\n\n /** Completed pages */\n pages: Page[];\n}\n\n/**\n * Internal scraper configuration\n */\nexport interface ScraperConfig {\n /** Merged options with defaults */\n options: Required<ScrapeOptions>;\n\n /** Parsed base URL */\n baseUrl: URL;\n\n /** Base domain for same-origin checking */\n baseDomain: string;\n}\n\n/**\n * Default scrape options\n */\nexport const DEFAULT_OPTIONS: Omit<\n Required<ScrapeOptions>,\n \"proxy\" | \"waitForSelector\" | \"connectionToCore\" | \"userAgent\" | \"browserPool\" | \"pool\"\n> & {\n proxy?: ProxyConfig;\n waitForSelector?: string;\n connectionToCore?: any;\n userAgent?: string;\n browserPool?: BrowserPoolConfig;\n pool?: IBrowserPool;\n} = {\n urls: [],\n formats: [\"markdown\"],\n includeMetadata: true,\n timeoutMs: 30000,\n includePatterns: [],\n excludePatterns: [],\n // Content cleaning defaults\n removeAds: true,\n removeBase64Images: true,\n skipTLSVerification: true,\n // Batch defaults\n batchConcurrency: 1,\n batchTimeoutMs: 300000,\n maxRetries: 2,\n onProgress: () => {}, // Default no-op progress callback\n // Hero-specific defaults\n verbose: false,\n showChrome: false,\n};\n\n/**\n * Format type guard\n */\nexport function isValidFormat(format: string): format is \"markdown\" | \"html\" | \"json\" | \"text\" {\n return format === \"markdown\" || format === \"html\" || format === \"json\" || format === \"text\";\n}\n\n/**\n * Check if a URL should be crawled based on base domain\n */\nexport function shouldCrawlUrl(url: URL, baseDomain: string): boolean {\n return url.hostname === baseDomain || url.hostname.endsWith(`.${baseDomain}`);\n}\n","import Hero from \"@ulixee/hero\";\nimport { parseHTML } from \"linkedom\";\nimport type { IBrowserPool } from \"./browser/types\";\nimport { detectChallenge } from \"./cloudflare/detector\";\nimport { waitForChallengeResolution } from \"./cloudflare/handler\";\nimport { resolveUrl, isValidUrl, isSameDomain, getUrlKey, isContentUrl, shouldIncludeUrl } from \"./utils/url-helpers\";\nimport { fetchRobotsTxt, isUrlAllowed, type RobotsRules } from \"./utils/robots-parser\";\nimport { rateLimit } from \"./utils/rate-limiter\";\nimport { createLogger } from \"./utils/logger\";\nimport { scrape } from \"./scraper\";\nimport type { CrawlOptions, CrawlResult, CrawlUrl, CrawlMetadata } from \"./crawl-types\";\nimport type { ScrapeResult } from \"./types\";\n\n/**\n * Crawler class for discovering and optionally scraping pages\n *\n * Features:\n * - BFS/DFS crawling with depth control\n * - Automatic Cloudflare challenge handling\n * - Link extraction and filtering\n * - Optional full content scraping\n * - URL deduplication\n *\n * @example\n * const crawler = new Crawler({\n * url: 'https://example.com',\n * depth: 2,\n * maxPages: 20,\n * scrape: true\n * });\n *\n * const result = await crawler.crawl();\n * console.log(`Discovered ${result.urls.length} URLs`);\n */\nexport class Crawler {\n private options: Omit<\n Required<CrawlOptions>,\n \"proxy\" | \"timeoutMs\" | \"userAgent\" | \"includePatterns\" | \"excludePatterns\" | \"pool\" | \"removeAds\" | \"removeBase64Images\"\n > & {\n proxy?: CrawlOptions[\"proxy\"];\n timeoutMs?: CrawlOptions[\"timeoutMs\"];\n userAgent?: CrawlOptions[\"userAgent\"];\n includePatterns?: string[];\n excludePatterns?: string[];\n removeAds?: boolean;\n removeBase64Images?: boolean;\n };\n private visited: Set<string> = new Set();\n private queue: Array<{ url: string; depth: number }> = [];\n private urls: CrawlUrl[] = [];\n private pool: IBrowserPool;\n private logger = createLogger(\"crawler\");\n private robotsRules: RobotsRules | null = null;\n\n constructor(options: CrawlOptions) {\n // Pool must be provided by client\n if (!options.pool) {\n throw new Error(\"Browser pool must be provided. Use ReaderClient for automatic pool management.\");\n }\n this.pool = options.pool;\n\n this.options = {\n url: options.url,\n depth: options.depth || 1,\n maxPages: options.maxPages || 20,\n scrape: options.scrape || false,\n delayMs: options.delayMs || 1000,\n timeoutMs: options.timeoutMs,\n includePatterns: options.includePatterns,\n excludePatterns: options.excludePatterns,\n formats: options.formats || [\"markdown\", \"html\"],\n scrapeConcurrency: options.scrapeConcurrency || 2,\n proxy: options.proxy,\n userAgent: options.userAgent,\n verbose: options.verbose || false,\n showChrome: options.showChrome || false,\n connectionToCore: options.connectionToCore,\n // Content cleaning options\n removeAds: options.removeAds,\n removeBase64Images: options.removeBase64Images,\n };\n }\n\n /**\n * Start crawling\n */\n async crawl(): Promise<CrawlResult> {\n const startTime = Date.now();\n\n // Fetch robots.txt rules before crawling\n this.robotsRules = await fetchRobotsTxt(this.options.url);\n if (this.robotsRules) {\n this.logger.info(\"Loaded robots.txt rules\");\n }\n\n // Pool is managed by ReaderClient - just use it\n // Add seed URL to queue (if allowed by robots.txt)\n if (isUrlAllowed(this.options.url, this.robotsRules)) {\n this.queue.push({ url: this.options.url, depth: 0 });\n } else {\n this.logger.warn(`Seed URL blocked by robots.txt: ${this.options.url}`);\n }\n\n // Crawl URLs\n while (this.queue.length > 0 && this.urls.length < this.options.maxPages) {\n // Check timeout\n if (this.options.timeoutMs && Date.now() - startTime > this.options.timeoutMs) {\n this.logger.warn(`Crawl timed out after ${this.options.timeoutMs}ms`);\n break;\n }\n\n const item = this.queue.shift()!;\n const urlKey = getUrlKey(item.url);\n\n if (this.visited.has(urlKey)) {\n continue;\n }\n\n // Fetch page\n const result = await this.fetchPage(item.url);\n\n if (result) {\n this.urls.push(result.crawlUrl);\n this.visited.add(urlKey);\n\n // Extract links from the fetched HTML if not at max depth\n if (item.depth < this.options.depth) {\n const links = this.extractLinks(result.html, item.url, item.depth + 1);\n this.queue.push(...links);\n }\n }\n\n // Rate limit (use robots.txt crawl-delay if specified, otherwise use configured delay)\n const delay = this.robotsRules?.crawlDelay || this.options.delayMs;\n await rateLimit(delay);\n }\n\n // Build metadata\n const metadata: CrawlMetadata = {\n totalUrls: this.urls.length,\n maxDepth: this.options.depth,\n totalDuration: Date.now() - startTime,\n seedUrl: this.options.url,\n };\n\n // Optionally scrape all discovered URLs\n let scraped: ScrapeResult | undefined;\n if (this.options.scrape) {\n scraped = await this.scrapeDiscoveredUrls();\n }\n\n return {\n urls: this.urls,\n scraped,\n metadata,\n };\n }\n\n /**\n * Fetch a single page and extract basic info\n */\n private async fetchPage(url: string): Promise<{ crawlUrl: CrawlUrl; html: string } | null> {\n try {\n return await this.pool.withBrowser(async (hero: Hero) => {\n // Navigate\n await hero.goto(url, { timeoutMs: 30000 });\n await hero.waitForPaintingStable();\n\n // Handle Cloudflare challenge\n const initialUrl = await hero.url;\n const detection = await detectChallenge(hero);\n\n if (detection.isChallenge) {\n if (this.options.verbose) {\n this.logger.info(`Challenge detected on ${url}`);\n }\n\n const result = await waitForChallengeResolution(hero, {\n maxWaitMs: 45000,\n pollIntervalMs: 500,\n verbose: this.options.verbose,\n initialUrl,\n });\n\n if (!result.resolved) {\n throw new Error(`Challenge not resolved`);\n }\n }\n\n // Extract basic info and HTML\n const title = await hero.document.title;\n const html = await hero.document.documentElement.outerHTML;\n\n // Try to extract description from meta tags\n let description: string | null = null;\n try {\n const metaDesc = await hero.document.querySelector('meta[name=\"description\"]');\n if (metaDesc) {\n description = await metaDesc.getAttribute(\"content\");\n }\n } catch {\n // No description found\n }\n\n return {\n crawlUrl: {\n url,\n title: title || \"Untitled\",\n description,\n },\n html,\n };\n });\n } catch (error: any) {\n this.logger.error(`Failed to fetch ${url}: ${error.message}`);\n return null;\n }\n }\n\n /**\n * Extract links from HTML content using DOM parsing\n * Handles all href formats (single quotes, double quotes, unquoted)\n */\n private extractLinks(\n html: string,\n baseUrl: string,\n depth: number\n ): Array<{ url: string; depth: number }> {\n const links: Array<{ url: string; depth: number }> = [];\n const { document } = parseHTML(html);\n\n // Use proper DOM API to find all anchor elements with href\n document.querySelectorAll(\"a[href]\").forEach((anchor: Element) => {\n const href = anchor.getAttribute(\"href\");\n if (!href) return;\n\n // Resolve relative URLs\n const resolved = resolveUrl(href, baseUrl);\n if (!resolved || !isValidUrl(resolved)) return;\n\n // Check if same domain\n if (!isSameDomain(resolved, this.options.url)) return;\n\n // Check if content page (skip legal, policy, utility pages)\n if (!isContentUrl(resolved)) return;\n\n // Check include/exclude patterns\n if (!shouldIncludeUrl(resolved, this.options.includePatterns, this.options.excludePatterns)) return;\n\n // Check if allowed by robots.txt\n if (!isUrlAllowed(resolved, this.robotsRules)) return;\n\n // Check if already visited or queued\n const urlKey = getUrlKey(resolved);\n if (this.visited.has(urlKey) || this.queue.some((q) => getUrlKey(q.url) === urlKey)) {\n return;\n }\n\n links.push({ url: resolved, depth });\n });\n\n return links;\n }\n\n /**\n * Scrape all discovered URLs\n */\n private async scrapeDiscoveredUrls(): Promise<ScrapeResult> {\n const urls = this.urls.map((u) => u.url);\n\n return scrape({\n urls,\n formats: this.options.formats,\n batchConcurrency: this.options.scrapeConcurrency,\n proxy: this.options.proxy,\n userAgent: this.options.userAgent,\n verbose: this.options.verbose,\n showChrome: this.options.showChrome,\n pool: this.pool,\n // Content cleaning options\n removeAds: this.options.removeAds,\n removeBase64Images: this.options.removeBase64Images,\n });\n }\n}\n\n/**\n * Convenience function to crawl a website\n *\n * @param options - Crawl options\n * @returns Crawl result\n *\n * @example\n * const result = await crawl({\n * url: 'https://example.com',\n * depth: 2,\n * maxPages: 20,\n * scrape: true\n * });\n */\nexport async function crawl(options: CrawlOptions): Promise<CrawlResult> {\n const crawler = new Crawler(options);\n return crawler.crawl();\n}\n","import pLimit from \"p-limit\";\n\n/**\n * Simple rate limit function\n */\nexport async function rateLimit(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Rate limiter using p-limit to control concurrent requests\n */\nexport class RateLimiter {\n private limit: ReturnType<typeof pLimit>;\n\n constructor(requestsPerSecond: number) {\n // Convert requests per second to concurrency limit\n // For rate limiting, we use pLimit with a delay between requests\n this.limit = pLimit(1);\n this.requestsPerSecond = requestsPerSecond;\n }\n\n private requestsPerSecond: number;\n private lastRequestTime = 0;\n\n /**\n * Execute a function with rate limiting\n */\n async execute<T>(fn: () => Promise<T>): Promise<T> {\n return this.limit(async () => {\n await this.waitForNextSlot();\n return fn();\n });\n }\n\n /**\n * Wait for the next available time slot based on rate limit\n */\n private async waitForNextSlot(): Promise<void> {\n const now = Date.now();\n const timeSinceLastRequest = now - this.lastRequestTime;\n const minInterval = 1000 / this.requestsPerSecond;\n\n if (timeSinceLastRequest < minInterval) {\n const delay = minInterval - timeSinceLastRequest;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n\n this.lastRequestTime = Date.now();\n }\n\n /**\n * Execute multiple functions concurrently with rate limiting\n */\n async executeAll<T>(functions: Array<() => Promise<T>>): Promise<T[]> {\n return Promise.all(functions.map((fn) => this.execute(fn)));\n }\n}\n","import Hero from \"@ulixee/hero\";\nimport { createHeroConfig } from \"./hero-config\";\nimport type {\n BrowserInstance,\n QueueItem,\n PoolConfig,\n PoolStats,\n HealthStatus,\n IBrowserPool,\n} from \"./types\";\nimport type { ProxyConfig } from \"../types\";\nimport { createLogger } from \"../utils/logger\";\n\n/**\n * Default pool configuration\n */\nconst DEFAULT_POOL_CONFIG: PoolConfig = {\n size: 2,\n retireAfterPageCount: 100,\n retireAfterAgeMs: 30 * 60 * 1000, // 30 minutes\n recycleCheckInterval: 60 * 1000, // 1 minute\n healthCheckInterval: 5 * 60 * 1000, // 5 minutes\n maxConsecutiveFailures: 3,\n maxQueueSize: 100,\n queueTimeout: 60 * 1000, // 1 minute\n};\n\n/**\n * Generate unique ID\n */\nfunction generateId(): string {\n return `browser_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n}\n\n/**\n * Browser Pool\n *\n * Manages a pool of Hero browser instances with:\n * - Auto-recycling based on age/request count\n * - Request queuing when pool is full\n * - Health monitoring\n *\n * @example\n * const pool = new BrowserPool({ size: 5 });\n * await pool.initialize();\n *\n * // Use withBrowser for automatic acquire/release\n * await pool.withBrowser(async (hero) => {\n * await hero.goto('https://example.com');\n * const title = await hero.document.title;\n * return title;\n * });\n *\n * await pool.shutdown();\n */\nexport class BrowserPool implements IBrowserPool {\n private instances: BrowserInstance[] = [];\n private available: BrowserInstance[] = [];\n private inUse: Set<BrowserInstance> = new Set();\n private queue: QueueItem[] = [];\n private config: PoolConfig;\n private proxy?: ProxyConfig;\n private recycleTimer?: NodeJS.Timeout;\n private healthTimer?: NodeJS.Timeout;\n private totalRequests = 0;\n private totalRequestDuration = 0;\n private showChrome: boolean;\n private connectionToCore?: any;\n private userAgent?: string;\n private verbose: boolean;\n private logger = createLogger(\"pool\");\n\n constructor(\n config: Partial<PoolConfig> = {},\n proxy?: ProxyConfig,\n showChrome: boolean = false,\n connectionToCore?: any,\n userAgent?: string,\n verbose: boolean = false\n ) {\n this.config = { ...DEFAULT_POOL_CONFIG, ...config };\n this.proxy = proxy;\n this.showChrome = showChrome;\n this.connectionToCore = connectionToCore;\n this.userAgent = userAgent;\n this.verbose = verbose;\n }\n\n /**\n * Initialize the pool by pre-launching browsers\n */\n async initialize(): Promise<void> {\n if (this.verbose) {\n this.logger.info(`Initializing pool with ${this.config.size} browsers...`);\n }\n\n // Pre-launch browsers\n const launchPromises: Promise<BrowserInstance>[] = [];\n for (let i = 0; i < this.config.size; i++) {\n launchPromises.push(this.createInstance());\n }\n\n this.instances = await Promise.all(launchPromises);\n this.available = [...this.instances];\n\n // Start background tasks\n this.startRecycling();\n this.startHealthChecks();\n\n if (this.verbose) {\n this.logger.info(`Pool ready: ${this.instances.length} browsers available`);\n }\n }\n\n /**\n * Shutdown the pool and close all browsers\n */\n async shutdown(): Promise<void> {\n if (this.verbose) {\n const stats = this.getStats();\n this.logger.info(\n `Shutting down pool: ${stats.totalRequests} total requests processed, ` +\n `${Math.round(stats.avgRequestDuration)}ms avg duration`\n );\n }\n\n // Stop background tasks\n if (this.recycleTimer) clearInterval(this.recycleTimer);\n if (this.healthTimer) clearInterval(this.healthTimer);\n\n // Reject all queued requests\n for (const item of this.queue) {\n item.reject(new Error(\"Pool shutting down\"));\n }\n this.queue = [];\n\n // Close all browsers\n const closePromises = this.instances.map((instance) => instance.hero.close().catch(() => {}));\n await Promise.all(closePromises);\n\n // Disconnect the connection to core to release event listeners\n if (this.connectionToCore) {\n try {\n await this.connectionToCore.disconnect();\n } catch {\n // Ignore disconnect errors\n }\n this.connectionToCore = undefined;\n }\n\n // Clear instances\n this.instances = [];\n this.available = [];\n this.inUse.clear();\n }\n\n /**\n * Acquire a browser from the pool\n */\n async acquire(): Promise<Hero> {\n // Get available instance\n const instance = this.available.shift();\n if (!instance) {\n // No available instances, queue the request\n if (this.verbose) {\n this.logger.info(`No browsers available, queuing request (queue: ${this.queue.length + 1})`);\n }\n return this.queueRequest();\n }\n\n // Mark as busy\n instance.status = \"busy\";\n instance.lastUsed = Date.now();\n this.inUse.add(instance);\n\n if (this.verbose) {\n this.logger.info(\n `Acquired browser ${instance.id} (available: ${this.available.length}, busy: ${this.inUse.size})`\n );\n }\n\n return instance.hero;\n }\n\n /**\n * Release a browser back to the pool\n */\n release(hero: Hero): void {\n const instance = this.instances.find((i) => i.hero === hero);\n if (!instance) return;\n\n // Update stats\n instance.status = \"idle\";\n instance.requestCount++;\n this.inUse.delete(instance);\n\n if (this.verbose) {\n this.logger.info(\n `Released browser ${instance.id} (requests: ${instance.requestCount}, available: ${this.available.length + 1})`\n );\n }\n\n // Check if needs recycling\n if (this.shouldRecycle(instance)) {\n if (this.verbose) {\n this.logger.info(`Recycling browser ${instance.id} (age or request limit reached)`);\n }\n this.recycleInstance(instance).catch(() => {});\n } else {\n this.available.push(instance);\n this.processQueue();\n }\n }\n\n /**\n * Execute callback with auto-managed browser\n */\n async withBrowser<T>(callback: (hero: Hero) => Promise<T>): Promise<T> {\n const startTime = Date.now();\n const hero = await this.acquire();\n\n try {\n const result = await callback(hero);\n\n // Update request stats\n this.totalRequests++;\n this.totalRequestDuration += Date.now() - startTime;\n\n return result;\n } finally {\n this.release(hero);\n }\n }\n\n /**\n * Get pool statistics\n */\n getStats(): PoolStats {\n const recycling = this.instances.filter((i) => i.status === \"recycling\").length;\n const unhealthy = this.instances.filter((i) => i.status === \"unhealthy\").length;\n\n return {\n total: this.instances.length,\n available: this.available.length,\n busy: this.inUse.size,\n recycling,\n unhealthy,\n queueLength: this.queue.length,\n totalRequests: this.totalRequests,\n avgRequestDuration:\n this.totalRequests > 0 ? this.totalRequestDuration / this.totalRequests : 0,\n };\n }\n\n /**\n * Run health check\n */\n async healthCheck(): Promise<HealthStatus> {\n const issues: string[] = [];\n const stats = this.getStats();\n\n // Check for unhealthy instances\n if (stats.unhealthy > 0) {\n issues.push(`${stats.unhealthy} unhealthy instances`);\n }\n\n // Check queue size\n if (stats.queueLength > this.config.maxQueueSize * 0.8) {\n issues.push(`Queue near capacity: ${stats.queueLength}/${this.config.maxQueueSize}`);\n }\n\n // Check if pool is saturated\n if (stats.available === 0 && stats.queueLength > 0) {\n issues.push(\"Pool saturated - all browsers busy with pending requests\");\n }\n\n return {\n healthy: issues.length === 0,\n issues,\n stats,\n };\n }\n\n // =========================================================================\n // Private methods\n // =========================================================================\n\n /**\n * Create a new browser instance\n */\n private async createInstance(): Promise<BrowserInstance> {\n const heroConfig = createHeroConfig({\n proxy: this.proxy,\n showChrome: this.showChrome,\n connectionToCore: this.connectionToCore,\n userAgent: this.userAgent,\n });\n\n const hero = new Hero(heroConfig);\n\n return {\n hero,\n id: generateId(),\n createdAt: Date.now(),\n lastUsed: Date.now(),\n requestCount: 0,\n status: \"idle\",\n };\n }\n\n /**\n * Check if instance should be recycled\n */\n private shouldRecycle(instance: BrowserInstance): boolean {\n const age = Date.now() - instance.createdAt;\n return (\n instance.requestCount >= this.config.retireAfterPageCount ||\n age >= this.config.retireAfterAgeMs\n );\n }\n\n /**\n * Recycle an instance (close old, create new)\n */\n private async recycleInstance(instance: BrowserInstance): Promise<void> {\n instance.status = \"recycling\";\n\n try {\n // Close old instance\n await instance.hero.close().catch(() => {});\n\n // Create new instance\n const newInstance = await this.createInstance();\n\n // Replace in instances array\n const index = this.instances.indexOf(instance);\n if (index !== -1) {\n this.instances[index] = newInstance;\n }\n\n // Add to available pool\n this.available.push(newInstance);\n\n if (this.verbose) {\n this.logger.info(`Recycled browser: ${instance.id} → ${newInstance.id}`);\n }\n\n // Process queue\n this.processQueue();\n } catch (error) {\n // Failed to recycle, mark as unhealthy\n instance.status = \"unhealthy\";\n if (this.verbose) {\n this.logger.warn(`Failed to recycle browser ${instance.id}`);\n }\n }\n }\n\n /**\n * Queue a request when no browsers available\n */\n private queueRequest(): Promise<Hero> {\n return new Promise<Hero>((resolve, reject) => {\n // Check queue size\n if (this.queue.length >= this.config.maxQueueSize) {\n reject(new Error(\"Queue full\"));\n return;\n }\n\n // Add to queue\n const item: QueueItem = {\n resolve,\n reject,\n queuedAt: Date.now(),\n };\n this.queue.push(item);\n\n // Set timeout\n setTimeout(() => {\n const index = this.queue.indexOf(item);\n if (index !== -1) {\n this.queue.splice(index, 1);\n reject(new Error(\"Queue timeout\"));\n }\n }, this.config.queueTimeout);\n });\n }\n\n /**\n * Process queued requests\n */\n private processQueue(): void {\n while (this.queue.length > 0 && this.available.length > 0) {\n const item = this.queue.shift()!;\n\n // Check if still valid (not timed out)\n const age = Date.now() - item.queuedAt;\n if (age > this.config.queueTimeout) {\n item.reject(new Error(\"Queue timeout\"));\n continue;\n }\n\n // Acquire and resolve\n this.acquire().then(item.resolve).catch(item.reject);\n }\n }\n\n /**\n * Start background recycling task\n */\n private startRecycling(): void {\n this.recycleTimer = setInterval(() => {\n for (const instance of this.instances) {\n if (instance.status === \"idle\" && this.shouldRecycle(instance)) {\n this.recycleInstance(instance).catch(() => {});\n }\n }\n }, this.config.recycleCheckInterval);\n // Allow process to exit even if timer is still running\n this.recycleTimer.unref();\n }\n\n /**\n * Start background health checks\n */\n private startHealthChecks(): void {\n this.healthTimer = setInterval(async () => {\n const health = await this.healthCheck();\n if (!health.healthy && health.issues.length > 0) {\n console.warn(\"[BrowserPool] Health issues:\", health.issues);\n }\n }, this.config.healthCheckInterval);\n // Allow process to exit even if timer is still running\n this.healthTimer.unref();\n }\n}\n\n// Backward compatibility alias\nexport { BrowserPool as HeroBrowserPool };\n","import type { ProxyConfig } from \"../types\";\n\n/**\n * Create proxy URL from configuration\n *\n * Supports both datacenter and residential proxies.\n * For residential proxies (e.g., IPRoyal), generates a sticky session ID.\n *\n * @param config - Proxy configuration\n * @returns Formatted proxy URL\n *\n * @example\n * // Datacenter proxy\n * createProxyUrl({\n * type: 'datacenter',\n * username: 'user',\n * password: 'pass',\n * host: 'proxy.example.com',\n * port: 8080\n * })\n * // Returns: \"http://user:pass@proxy.example.com:8080\"\n *\n * @example\n * // Residential proxy with sticky session\n * createProxyUrl({\n * type: 'residential',\n * username: 'customer-abc',\n * password: 'secret',\n * host: 'geo.iproyal.com',\n * port: 12321,\n * country: 'us'\n * })\n * // Returns: \"http://customer-abc_session-hero_123_abc456_country-us:secret@geo.iproyal.com:12321\"\n */\nexport function createProxyUrl(config: ProxyConfig): string {\n // If full URL provided, use it directly\n if (config.url) {\n return config.url;\n }\n\n // Residential proxy with sticky session\n if (config.type === \"residential\") {\n // Generate unique session ID for sticky sessions\n const sessionId = `hero_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n\n // Format: customer-{username}_session-{sessionId}_country-{country}:{password}@{host}:{port}\n return `http://customer-${config.username}_session-${sessionId}_country-${\n config.country || \"us\"\n }:${config.password}@${config.host}:${config.port}`;\n }\n\n // Datacenter proxy (simple authentication)\n return `http://${config.username}:${config.password}@${config.host}:${config.port}`;\n}\n\n/**\n * Parse proxy URL into ProxyConfig\n *\n * @param url - Proxy URL string\n * @returns Parsed proxy configuration\n *\n * @example\n * parseProxyUrl(\"http://user:pass@proxy.example.com:8080\")\n * // Returns: { username: 'user', password: 'pass', host: 'proxy.example.com', port: 8080 }\n */\nexport function parseProxyUrl(url: string): ProxyConfig {\n try {\n const parsed = new URL(url);\n\n return {\n url,\n username: parsed.username,\n password: parsed.password,\n host: parsed.hostname,\n port: parsed.port ? parseInt(parsed.port, 10) : undefined,\n };\n } catch (error) {\n throw new Error(`Invalid proxy URL: ${url}`);\n }\n}\n","import type { ProxyConfig } from \"../types\";\nimport { createProxyUrl } from \"../proxy/config\";\n\n/**\n * Hero configuration options\n */\nexport interface HeroConfigOptions {\n /** Proxy configuration */\n proxy?: ProxyConfig;\n /** Show Chrome window (default: false) */\n showChrome?: boolean;\n /** Custom user agent */\n userAgent?: string;\n /** Connection to Core (for in-process Core) */\n connectionToCore?: any;\n}\n\n/**\n * Create Hero configuration with optimal anti-bot bypass settings\n *\n * Extracted from proven hero-test implementation.\n * Includes:\n * - TLS fingerprint emulation (disableMitm: false)\n * - DNS over TLS (mimics Chrome)\n * - WebRTC IP masking\n * - Proper locale and timezone\n *\n * @param options - Configuration options\n * @returns Hero configuration object\n */\nexport function createHeroConfig(options: HeroConfigOptions = {}): any {\n const config: any = {\n // Show or hide Chrome window\n showChrome: options.showChrome ?? false,\n\n // ============================================================================\n // CRITICAL: TLS fingerprint emulation\n // ============================================================================\n // Setting disableMitm to false enables TLS/TCP fingerprint emulation\n // This is ESSENTIAL for bypassing Cloudflare and other anti-bot systems\n disableMitm: false,\n\n // ============================================================================\n // Session management\n // ============================================================================\n // Use incognito for clean session state\n disableIncognito: false,\n\n // ============================================================================\n // Docker compatibility\n // ============================================================================\n // Required when running in containerized environments\n noChromeSandbox: true,\n\n // ============================================================================\n // DNS over TLS (mimics Chrome behavior)\n // ============================================================================\n // Using Cloudflare's DNS (1.1.1.1) over TLS makes the connection\n // look more like a real Chrome browser\n dnsOverTlsProvider: {\n host: \"1.1.1.1\",\n servername: \"cloudflare-dns.com\",\n },\n\n // ============================================================================\n // WebRTC IP leak prevention\n // ============================================================================\n // Masks the real IP address in WebRTC connections\n // Uses ipify.org to detect the public IP\n upstreamProxyIpMask: {\n ipLookupService: \"https://api.ipify.org?format=json\",\n },\n\n // ============================================================================\n // Locale and timezone\n // ============================================================================\n locale: \"en-US\",\n timezoneId: \"America/New_York\",\n\n // ============================================================================\n // Viewport (standard desktop size)\n // ============================================================================\n viewport: {\n width: 1920,\n height: 1080,\n },\n\n // ============================================================================\n // User agent (if provided)\n // ============================================================================\n ...(options.userAgent && { userAgent: options.userAgent }),\n\n // ============================================================================\n // Connection to Core (if provided)\n // ============================================================================\n ...(options.connectionToCore && { connectionToCore: options.connectionToCore }),\n };\n\n // ============================================================================\n // Proxy configuration\n // ============================================================================\n if (options.proxy) {\n config.upstreamProxyUrl = createProxyUrl(options.proxy);\n // Don't use system DNS when using proxy\n config.upstreamProxyUseSystemDns = false;\n }\n\n return config;\n}\n\n/**\n * Default Hero configuration (no proxy)\n */\nexport function getDefaultHeroConfig(): any {\n return createHeroConfig();\n}\n","/**\n * Daemon Server\n *\n * An HTTP server that wraps ReaderClient, allowing multiple CLI\n * commands to share a single browser pool for efficient scraping.\n *\n * @example\n * // Start daemon\n * const daemon = new DaemonServer({ port: 3847, poolSize: 5 });\n * await daemon.start();\n *\n * // Stop daemon\n * await daemon.stop();\n */\n\nimport http from \"http\";\nimport { ReaderClient, type ReaderClientOptions } from \"../client\";\nimport type { ScrapeOptions, ScrapeResult } from \"../types\";\nimport type { CrawlOptions, CrawlResult } from \"../crawl-types\";\nimport { createLogger } from \"../utils/logger\";\n\nconst logger = createLogger(\"daemon\");\n\nexport const DEFAULT_DAEMON_PORT = 3847;\nconst PID_FILE_NAME = \".reader-daemon.pid\";\n\n/**\n * Daemon server configuration\n */\nexport interface DaemonServerOptions {\n /** Port to listen on (default: 3847) */\n port?: number;\n /** Browser pool size (default: 5) */\n poolSize?: number;\n /** Enable verbose logging (default: false) */\n verbose?: boolean;\n /** Show Chrome browser windows (default: false) */\n showChrome?: boolean;\n}\n\n/**\n * Request body types\n */\ninterface ScrapeRequest {\n action: \"scrape\";\n options: Omit<ScrapeOptions, \"connectionToCore\">;\n}\n\ninterface CrawlRequest {\n action: \"crawl\";\n options: Omit<CrawlOptions, \"connectionToCore\">;\n}\n\ninterface StatusRequest {\n action: \"status\";\n}\n\ninterface ShutdownRequest {\n action: \"shutdown\";\n}\n\ntype DaemonRequest = ScrapeRequest | CrawlRequest | StatusRequest | ShutdownRequest;\n\n/**\n * Response types\n */\ninterface SuccessResponse<T> {\n success: true;\n data: T;\n}\n\ninterface ErrorResponse {\n success: false;\n error: string;\n}\n\ntype DaemonResponse<T> = SuccessResponse<T> | ErrorResponse;\n\n/**\n * Status response data\n */\nexport interface DaemonStatus {\n running: true;\n port: number;\n poolSize: number;\n uptime: number;\n pid: number;\n}\n\n/**\n * Daemon Server\n */\nexport class DaemonServer {\n private server: http.Server | null = null;\n private client: ReaderClient | null = null;\n private options: Required<DaemonServerOptions>;\n private startTime: number = 0;\n\n constructor(options: DaemonServerOptions = {}) {\n this.options = {\n port: options.port ?? DEFAULT_DAEMON_PORT,\n poolSize: options.poolSize ?? 5,\n verbose: options.verbose ?? false,\n showChrome: options.showChrome ?? false,\n };\n }\n\n /**\n * Start the daemon server\n */\n async start(): Promise<void> {\n if (this.server) {\n throw new Error(\"Daemon is already running\");\n }\n\n // Initialize ReaderClient\n const clientOptions: ReaderClientOptions = {\n verbose: this.options.verbose,\n showChrome: this.options.showChrome,\n browserPool: {\n size: this.options.poolSize,\n },\n };\n\n this.client = new ReaderClient(clientOptions);\n await this.client.start();\n\n // Create HTTP server\n this.server = http.createServer(this.handleRequest.bind(this));\n\n // Start listening\n await new Promise<void>((resolve, reject) => {\n this.server!.listen(this.options.port, () => {\n this.startTime = Date.now();\n if (this.options.verbose) {\n logger.info(`Daemon started on port ${this.options.port} with pool size ${this.options.poolSize}`);\n }\n resolve();\n });\n\n this.server!.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"EADDRINUSE\") {\n reject(new Error(`Port ${this.options.port} is already in use. Is another daemon running?`));\n } else {\n reject(error);\n }\n });\n });\n\n // Write PID file\n await this.writePidFile();\n }\n\n /**\n * Stop the daemon server\n */\n async stop(): Promise<void> {\n if (this.server) {\n await new Promise<void>((resolve) => {\n this.server!.close(() => resolve());\n });\n this.server = null;\n }\n\n if (this.client) {\n await this.client.close();\n this.client = null;\n }\n\n // Remove PID file\n await this.removePidFile();\n\n if (this.options.verbose) {\n logger.info(\"Daemon stopped\");\n }\n }\n\n /**\n * Get the port the daemon is running on\n */\n getPort(): number {\n return this.options.port;\n }\n\n /**\n * Handle incoming HTTP requests\n */\n private async handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n // Only accept POST requests to /\n if (req.method !== \"POST\" || req.url !== \"/\") {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ success: false, error: \"Not found\" }));\n return;\n }\n\n // Parse request body\n let body = \"\";\n for await (const chunk of req) {\n body += chunk;\n }\n\n let request: DaemonRequest;\n try {\n request = JSON.parse(body);\n } catch {\n this.sendResponse(res, 400, { success: false, error: \"Invalid JSON\" });\n return;\n }\n\n // Handle request\n try {\n switch (request.action) {\n case \"scrape\":\n await this.handleScrape(res, request.options);\n break;\n case \"crawl\":\n await this.handleCrawl(res, request.options);\n break;\n case \"status\":\n this.handleStatus(res);\n break;\n case \"shutdown\":\n await this.handleShutdown(res);\n break;\n default:\n this.sendResponse(res, 400, { success: false, error: \"Unknown action\" });\n }\n } catch (error: any) {\n this.sendResponse(res, 500, { success: false, error: error.message });\n }\n }\n\n /**\n * Handle scrape request\n */\n private async handleScrape(\n res: http.ServerResponse,\n options: Omit<ScrapeOptions, \"connectionToCore\">\n ): Promise<void> {\n if (!this.client) {\n this.sendResponse(res, 500, { success: false, error: \"Client not initialized\" });\n return;\n }\n\n const result = await this.client.scrape(options);\n this.sendResponse<ScrapeResult>(res, 200, { success: true, data: result });\n }\n\n /**\n * Handle crawl request\n */\n private async handleCrawl(\n res: http.ServerResponse,\n options: Omit<CrawlOptions, \"connectionToCore\">\n ): Promise<void> {\n if (!this.client) {\n this.sendResponse(res, 500, { success: false, error: \"Client not initialized\" });\n return;\n }\n\n const result = await this.client.crawl(options);\n this.sendResponse<CrawlResult>(res, 200, { success: true, data: result });\n }\n\n /**\n * Handle status request\n */\n private handleStatus(res: http.ServerResponse): void {\n const status: DaemonStatus = {\n running: true,\n port: this.options.port,\n poolSize: this.options.poolSize,\n uptime: Date.now() - this.startTime,\n pid: process.pid,\n };\n this.sendResponse<DaemonStatus>(res, 200, { success: true, data: status });\n }\n\n /**\n * Handle shutdown request\n */\n private async handleShutdown(res: http.ServerResponse): Promise<void> {\n this.sendResponse(res, 200, { success: true, data: { message: \"Shutting down\" } });\n\n // Delay shutdown to allow response to be sent\n setTimeout(() => {\n this.stop().then(() => process.exit(0));\n }, 100);\n }\n\n /**\n * Send JSON response\n */\n private sendResponse<T>(res: http.ServerResponse, statusCode: number, data: DaemonResponse<T>): void {\n res.writeHead(statusCode, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(data));\n }\n\n /**\n * Write PID file\n */\n private async writePidFile(): Promise<void> {\n const fs = await import(\"fs/promises\");\n const path = await import(\"path\");\n const os = await import(\"os\");\n\n const pidFile = path.join(os.tmpdir(), PID_FILE_NAME);\n const data = JSON.stringify({\n pid: process.pid,\n port: this.options.port,\n startedAt: new Date().toISOString(),\n });\n\n await fs.writeFile(pidFile, data);\n }\n\n /**\n * Remove PID file\n */\n private async removePidFile(): Promise<void> {\n const fs = await import(\"fs/promises\");\n const path = await import(\"path\");\n const os = await import(\"os\");\n\n const pidFile = path.join(os.tmpdir(), PID_FILE_NAME);\n try {\n await fs.unlink(pidFile);\n } catch {\n // Ignore errors\n }\n }\n}\n\n/**\n * Get path to PID file\n */\nexport async function getPidFilePath(): Promise<string> {\n const path = await import(\"path\");\n const os = await import(\"os\");\n return path.join(os.tmpdir(), PID_FILE_NAME);\n}\n\n/**\n * Check if daemon is running by reading PID file\n */\nexport async function getDaemonInfo(): Promise<{ pid: number; port: number; startedAt: string } | null> {\n const fs = await import(\"fs/promises\");\n const pidFile = await getPidFilePath();\n\n try {\n const data = await fs.readFile(pidFile, \"utf-8\");\n const info = JSON.parse(data);\n\n // Check if process is still running\n try {\n process.kill(info.pid, 0); // Signal 0 tests if process exists\n return info;\n } catch {\n // Process not running, clean up stale PID file\n await fs.unlink(pidFile).catch(() => {});\n return null;\n }\n } catch {\n return null;\n }\n}\n","/**\n * Daemon Client\n *\n * A client that connects to the daemon server via HTTP.\n * Used by CLI commands when a daemon is running.\n *\n * @example\n * const client = new DaemonClient({ port: 3847 });\n *\n * const result = await client.scrape({\n * urls: ['https://example.com'],\n * formats: ['markdown'],\n * });\n */\n\nimport http from \"http\";\nimport type { ScrapeOptions, ScrapeResult } from \"../types\";\nimport type { CrawlOptions, CrawlResult } from \"../crawl-types\";\nimport type { DaemonStatus } from \"./server\";\nimport { DEFAULT_DAEMON_PORT } from \"./server\";\n\n/**\n * Daemon client configuration\n */\nexport interface DaemonClientOptions {\n /** Port the daemon is running on (default: 3847) */\n port?: number;\n /** Request timeout in milliseconds (default: 600000 = 10 minutes) */\n timeoutMs?: number;\n}\n\n/**\n * Daemon Client\n */\nexport class DaemonClient {\n private options: Required<DaemonClientOptions>;\n\n constructor(options: DaemonClientOptions = {}) {\n this.options = {\n port: options.port ?? DEFAULT_DAEMON_PORT,\n timeoutMs: options.timeoutMs ?? 600000, // 10 minutes default\n };\n }\n\n /**\n * Scrape URLs via daemon\n */\n async scrape(options: Omit<ScrapeOptions, \"connectionToCore\">): Promise<ScrapeResult> {\n return this.request<ScrapeResult>({\n action: \"scrape\",\n options,\n });\n }\n\n /**\n * Crawl URL via daemon\n */\n async crawl(options: Omit<CrawlOptions, \"connectionToCore\">): Promise<CrawlResult> {\n return this.request<CrawlResult>({\n action: \"crawl\",\n options,\n });\n }\n\n /**\n * Get daemon status\n */\n async status(): Promise<DaemonStatus> {\n return this.request<DaemonStatus>({\n action: \"status\",\n });\n }\n\n /**\n * Request daemon shutdown\n */\n async shutdown(): Promise<void> {\n await this.request<{ message: string }>({\n action: \"shutdown\",\n });\n }\n\n /**\n * Check if daemon is reachable\n */\n async isRunning(): Promise<boolean> {\n try {\n await this.status();\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Make HTTP request to daemon\n */\n private request<T>(body: object): Promise<T> {\n return new Promise((resolve, reject) => {\n const data = JSON.stringify(body);\n\n const req = http.request(\n {\n hostname: \"127.0.0.1\",\n port: this.options.port,\n path: \"/\",\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(data),\n },\n timeout: this.options.timeoutMs,\n },\n (res) => {\n let responseBody = \"\";\n\n res.on(\"data\", (chunk) => {\n responseBody += chunk;\n });\n\n res.on(\"end\", () => {\n try {\n const response = JSON.parse(responseBody);\n\n if (response.success) {\n resolve(response.data);\n } else {\n reject(new Error(response.error || \"Unknown daemon error\"));\n }\n } catch (error) {\n reject(new Error(`Failed to parse daemon response: ${responseBody}`));\n }\n });\n }\n );\n\n req.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"ECONNREFUSED\") {\n reject(new Error(`Cannot connect to daemon on port ${this.options.port}. Is it running?`));\n } else {\n reject(error);\n }\n });\n\n req.on(\"timeout\", () => {\n req.destroy();\n reject(new Error(`Request to daemon timed out after ${this.options.timeoutMs}ms`));\n });\n\n req.write(data);\n req.end();\n });\n }\n}\n\n/**\n * Check if daemon is running on the specified port\n */\nexport async function isDaemonRunning(port: number = DEFAULT_DAEMON_PORT): Promise<boolean> {\n const client = new DaemonClient({ port, timeoutMs: 5000 });\n return client.isRunning();\n}\n"],"mappings":"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","names":["extractDomainFromUrl","extractDomainFromUrl","parseHTML","parseHTML","parseHTML","URL","parseHTML","shouldCrawlUrl","normalizeUrl","result","parseHTML","pLimit","parseHTML","logger","logger","http","http"]}