@arabold/docs-mcp-server 1.17.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +267 -209
- package/db/migrations/002-normalize-library-table.sql +50 -0
- package/db/migrations/003-normalize-vector-table.sql +33 -0
- package/db/migrations/004-complete-normalization.sql +67 -0
- package/db/migrations/005-add-status-tracking.sql +42 -0
- package/db/migrations/006-add-scraper-options.sql +16 -0
- package/dist/EmbeddingFactory-CElwVk3X.js.map +1 -1
- package/dist/assets/main.css +1 -1
- package/dist/assets/main.js +5670 -5138
- package/dist/assets/main.js.map +1 -1
- package/dist/index.js +5651 -4115
- package/dist/index.js.map +1 -1
- package/package.json +35 -35
- package/public/assets/main.css +1 -1
- package/public/assets/main.js +5670 -5138
- package/public/assets/main.js.map +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/utils/logger.ts","../src/pipeline/types.ts","../src/tools/CancelJobTool.ts","../src/tools/ClearCompletedJobsTool.ts","../src/tools/errors.ts","../src/utils/mimeTypeUtils.ts","../src/scraper/middleware/HtmlCheerioParserMiddleware.ts","../src/utils/dom.ts","../src/scraper/middleware/HtmlLinkExtractorMiddleware.ts","../src/scraper/middleware/HtmlMetadataExtractorMiddleware.ts","../src/utils/config.ts","../src/scraper/types.ts","../src/scraper/middleware/HtmlPlaywrightMiddleware.ts","../src/scraper/middleware/HtmlSanitizerMiddleware.ts","../src/scraper/middleware/HtmlToMarkdownMiddleware.ts","../src/scraper/middleware/MarkdownLinkExtractorMiddleware.ts","../src/scraper/middleware/MarkdownMetadataExtractorMiddleware.ts","../src/scraper/utils/buffer.ts","../src/scraper/pipelines/BasePipeline.ts","../src/scraper/pipelines/HtmlPipeline.ts","../src/scraper/pipelines/MarkdownPipeline.ts","../src/utils/errors.ts","../src/tools/FetchUrlTool.ts","../src/tools/FindVersionTool.ts","../src/tools/GetJobInfoTool.ts","../src/tools/ListJobsTool.ts","../src/tools/ListLibrariesTool.ts","../src/tools/RemoveTool.ts","../src/tools/ScrapeTool.ts","../src/tools/SearchTool.ts","../src/mcp/utils.ts","../src/mcp/mcpServer.ts","../src/mcp/startHttpServer.ts","../src/mcp/startStdioServer.ts","../src/pipeline/errors.ts","../src/scraper/fetcher/FingerprintGenerator.ts","../src/scraper/fetcher/HttpFetcher.ts","../src/scraper/fetcher/FileFetcher.ts","../src/mcp/tools.ts","../src/mcp/index.ts","../src/utils/url.ts","../src/scraper/utils/patternMatcher.ts","../src/scraper/utils/scope.ts","../src/scraper/strategies/BaseScraperStrategy.ts","../src/scraper/strategies/WebScraperStrategy.ts","../src/scraper/strategies/GitHubScraperStrategy.ts","../src/scraper/strategies/LocalFileStrategy.ts","../src/scraper/strategies/NpmScraperStrategy.ts","../src/scraper/strategies/PyPiScraperStrategy.ts","../src/scraper/ScraperRegistry.ts","../src/scraper/ScraperService.ts","../src/pipeline/PipelineWorker.ts","../src/pipeline/PipelineManager.ts","../src/utils/string.ts","../src/splitter/errors.ts","../src/splitter/splitters/CodeContentSplitter.ts","../src/splitter/splitters/TableContentSplitter.ts","../src/splitter/splitters/TextContentSplitter.ts","../src/splitter/SemanticMarkdownSplitter.ts","../src/splitter/GreedySplitter.ts","../src/utils/paths.ts","../src/store/DocumentRetrieverService.ts","../src/store/errors.ts","../src/store/applyMigrations.ts","../src/store/types.ts","../src/store/DocumentStore.ts","../src/store/DocumentManagementService.ts","../src/web/components/Layout.tsx","../src/web/routes/index.tsx","../src/web/routes/jobs/cancel.tsx","../src/web/routes/jobs/clear-completed.tsx","../src/web/components/VersionBadge.tsx","../src/web/components/LoadingSpinner.tsx","../src/web/components/JobItem.tsx","../src/web/components/JobList.tsx","../src/web/routes/jobs/list.tsx","../src/web/components/Alert.tsx","../src/web/components/Tooltip.tsx","../src/web/components/ScrapeFormContent.tsx","../src/web/components/ScrapeForm.tsx","../src/web/routes/jobs/new.tsx","../src/web/components/VersionDetailsRow.tsx","../src/web/components/LibraryDetailCard.tsx","../src/web/components/LibrarySearchCard.tsx","../src/web/components/SearchResultItem.tsx","../src/web/components/SearchResultList.tsx","../src/web/components/SearchResultSkeletonItem.tsx","../src/web/routes/libraries/detail.tsx","../src/web/components/LibraryItem.tsx","../src/web/components/LibraryList.tsx","../src/web/routes/libraries/list.tsx","../src/web/web.ts","../src/index.ts"],"sourcesContent":["/**\n * Defines the available log levels.\n */\nexport enum LogLevel {\n ERROR = 0,\n WARN = 1,\n INFO = 2,\n DEBUG = 3,\n}\n\nlet currentLogLevel: LogLevel = LogLevel.INFO; // Default level\n\n/**\n * Sets the current logging level for the application.\n * @param level - The desired log level.\n */\nexport function setLogLevel(level: LogLevel): void {\n currentLogLevel = level;\n}\n\n/**\n * Provides logging functionalities with level control.\n */\nexport const logger = {\n /**\n * Logs a debug message if the current log level is DEBUG or higher.\n * @param message - The message to log.\n */\n debug: (message: string) => {\n if (currentLogLevel >= LogLevel.DEBUG && !process.env.VITEST_WORKER_ID) {\n console.debug(message);\n }\n },\n /**\n * Logs an info message if the current log level is INFO or higher.\n * @param message - The message to log.\n */\n info: (message: string) => {\n if (currentLogLevel >= LogLevel.INFO && !process.env.VITEST_WORKER_ID) {\n console.log(message); // Using console.log for INFO\n }\n },\n /**\n * Logs a warning message if the current log level is WARN or higher.\n * @param message - The message to log.\n */\n warn: (message: string) => {\n if (currentLogLevel >= LogLevel.WARN && !process.env.VITEST_WORKER_ID) {\n console.warn(message);\n }\n },\n /**\n * Logs an error message if the current log level is ERROR or higher (always logs).\n * @param message - The message to log.\n */\n error: (message: string) => {\n if (currentLogLevel >= LogLevel.ERROR && !process.env.VITEST_WORKER_ID) {\n console.error(message);\n }\n },\n};\n","import type { ScraperOptions, ScraperProgress } from \"../scraper/types\";\nimport type { Document } from \"../types\"; // Use local Document type\n\n/**\n * Represents the possible states of a pipeline job.\n */\nexport enum PipelineJobStatus {\n QUEUED = \"queued\",\n RUNNING = \"running\",\n COMPLETED = \"completed\",\n FAILED = \"failed\",\n CANCELLING = \"cancelling\",\n CANCELLED = \"cancelled\",\n}\n\n/**\n * Represents a single document processing job within the pipeline.\n */\nexport interface PipelineJob {\n /** Unique identifier for the job. */\n id: string;\n /** The library name associated with the job. */\n library: string;\n /** The library version associated with the job. */\n version: string;\n /** Options provided for the scraper. */\n options: ScraperOptions;\n /** Current status of the job. */\n status: PipelineJobStatus;\n /** Detailed progress information. */\n progress: ScraperProgress | null;\n /** Error object if the job failed. */\n error: Error | null;\n /** Timestamp when the job was created. */\n createdAt: Date;\n /** Timestamp when the job started running. */\n startedAt: Date | null;\n /** Timestamp when the job finished (completed, failed, or cancelled). */\n finishedAt: Date | null;\n /** AbortController to signal cancellation. */\n abortController: AbortController;\n /** Promise that resolves/rejects when the job finishes. */\n completionPromise: Promise<void>;\n /** Resolver function for the completion promise. */\n resolveCompletion: () => void;\n /** Rejector function for the completion promise. */\n rejectCompletion: (reason?: unknown) => void;\n}\n\n/**\n * Defines the structure for callback functions used with the PipelineManager.\n * Allows external components to hook into job lifecycle events.\n */\nexport interface PipelineManagerCallbacks {\n /** Callback triggered when a job's status changes. */\n onJobStatusChange?: (job: PipelineJob) => Promise<void>;\n /** Callback triggered when a job makes progress. */\n onJobProgress?: (job: PipelineJob, progress: ScraperProgress) => Promise<void>;\n /** Callback triggered when a job encounters an error during processing (e.g., storing a doc). */\n onJobError?: (job: PipelineJob, error: Error, document?: Document) => Promise<void>;\n}\n","import type { PipelineManager } from \"../pipeline/PipelineManager\";\nimport { PipelineJobStatus } from \"../pipeline/types\";\nimport { logger } from \"../utils/logger\";\n\n/**\n * Input parameters for the CancelJobTool.\n */\nexport interface CancelJobInput {\n /** The ID of the job to cancel. */\n jobId: string;\n}\n\n/**\n * Output result for the CancelJobTool.\n */\nexport interface CancelJobResult {\n /** A message indicating the outcome of the cancellation attempt. */\n message: string;\n /** Indicates if the cancellation request was successfully initiated or if the job was already finished/cancelled. */\n success: boolean;\n}\n\n/**\n * Tool for attempting to cancel a pipeline job.\n */\nexport class CancelJobTool {\n private manager: PipelineManager;\n\n /**\n * Creates an instance of CancelJobTool.\n * @param manager The PipelineManager instance.\n */\n constructor(manager: PipelineManager) {\n this.manager = manager;\n }\n\n /**\n * Executes the tool to attempt cancellation of a specific job.\n * @param input - The input parameters, containing the jobId.\n * @returns A promise that resolves with the outcome message.\n */\n async execute(input: CancelJobInput): Promise<CancelJobResult> {\n try {\n // Retrieve the job first to check its status before attempting cancellation\n const job = await this.manager.getJob(input.jobId);\n\n if (!job) {\n logger.warn(`❓ [CancelJobTool] Job not found: ${input.jobId}`);\n return {\n message: `Job with ID ${input.jobId} not found.`,\n success: false,\n };\n }\n\n // Check if the job is already in a final state\n if (\n job.status === PipelineJobStatus.COMPLETED || // Use enum member\n job.status === PipelineJobStatus.FAILED || // Use enum member\n job.status === PipelineJobStatus.CANCELLED // Use enum member\n ) {\n logger.debug(`Job ${input.jobId} is already in a final state: ${job.status}.`);\n return {\n message: `Job ${input.jobId} is already ${job.status}. No action taken.`,\n success: true, // Considered success as no cancellation needed\n };\n }\n\n // Attempt cancellation\n await this.manager.cancelJob(input.jobId);\n\n // Re-fetch the job to confirm status change (or check status directly if cancelJob returned it)\n // PipelineManager.cancelJob doesn't return status, so re-fetch is needed for confirmation.\n const updatedJob = await this.manager.getJob(input.jobId);\n const finalStatus = updatedJob?.status ?? \"UNKNOWN (job disappeared?)\";\n\n logger.debug(\n `Cancellation requested for job ${input.jobId}. Current status: ${finalStatus}`,\n );\n return {\n message: `Cancellation requested for job ${input.jobId}. Current status: ${finalStatus}.`,\n success: true,\n };\n } catch (error) {\n logger.error(`❌ Error cancelling job ${input.jobId}: ${error}`);\n return {\n message: `Failed to cancel job ${input.jobId}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n success: false,\n };\n }\n }\n}\n","import type { PipelineManager } from \"../pipeline/PipelineManager\";\nimport { logger } from \"../utils/logger\";\n\n/**\n * Input parameters for the ClearCompletedJobsTool.\n */\n// biome-ignore lint/suspicious/noEmptyInterface: No input parameters needed for this tool\nexport interface ClearCompletedJobsInput {\n // No input parameters needed for this tool\n}\n\n/**\n * Output result for the ClearCompletedJobsTool.\n */\nexport interface ClearCompletedJobsResult {\n /** A message indicating the outcome of the clear operation. */\n message: string;\n /** Indicates if the clear operation was successful. */\n success: boolean;\n /** The number of jobs that were cleared. */\n clearedCount: number;\n}\n\n/**\n * Tool for clearing all completed, cancelled, and failed jobs from the pipeline.\n * This helps keep the job queue clean by removing jobs that are no longer active.\n */\nexport class ClearCompletedJobsTool {\n private manager: PipelineManager;\n\n /**\n * Creates an instance of ClearCompletedJobsTool.\n * @param manager The PipelineManager instance.\n */\n constructor(manager: PipelineManager) {\n this.manager = manager;\n }\n\n /**\n * Executes the tool to clear all completed jobs from the pipeline.\n * @param input - The input parameters (currently unused).\n * @returns A promise that resolves with the outcome of the clear operation.\n */\n async execute(input: ClearCompletedJobsInput): Promise<ClearCompletedJobsResult> {\n try {\n const clearedCount = await this.manager.clearCompletedJobs();\n\n const message =\n clearedCount > 0\n ? `Successfully cleared ${clearedCount} completed job${clearedCount === 1 ? \"\" : \"s\"} from the queue.`\n : \"No completed jobs to clear.\";\n\n logger.debug(`[ClearCompletedJobsTool] ${message}`);\n\n return {\n message,\n success: true,\n clearedCount,\n };\n } catch (error) {\n const errorMessage = `Failed to clear completed jobs: ${\n error instanceof Error ? error.message : String(error)\n }`;\n\n logger.error(`❌ [ClearCompletedJobsTool] ${errorMessage}`);\n\n return {\n message: errorMessage,\n success: false,\n clearedCount: 0,\n };\n }\n }\n}\n","import semver from \"semver\";\nimport type { LibraryVersionDetails } from \"../store/types\"; // Import LibraryVersionDetails\n\nclass ToolError extends Error {\n constructor(\n message: string,\n public readonly toolName: string,\n ) {\n super(message);\n this.name = this.constructor.name;\n }\n}\n\nclass VersionNotFoundError extends ToolError {\n constructor(\n public readonly library: string,\n public readonly requestedVersion: string,\n public readonly availableVersions: LibraryVersionDetails[], // Use LibraryVersionDetails\n ) {\n super(\n `Version ${requestedVersion} not found for ${library}. Available versions: ${availableVersions.map((v) => v.version).join(\", \")}`,\n \"SearchTool\",\n );\n }\n\n getLatestVersion() {\n return this.availableVersions.sort((a, b) => semver.compare(b.version, a.version))[0];\n }\n}\n\n/**\n * Error thrown when a requested library cannot be found in the store.\n * Includes suggestions for similar library names if available.\n */\nclass LibraryNotFoundError extends ToolError {\n constructor(\n public readonly requestedLibrary: string,\n public readonly suggestions: string[] = [],\n ) {\n let message = `Library '${requestedLibrary}' not found.`;\n if (suggestions.length > 0) {\n message += ` Did you mean one of these: ${suggestions.join(\", \")}?`;\n }\n // Assuming this error might originate from various tools, but SearchTool is a primary candidate.\n // We might need to adjust the toolName if it's thrown elsewhere.\n super(message, \"SearchTool\");\n }\n}\n\nexport { LibraryNotFoundError, ToolError, VersionNotFoundError };\n","/**\n * Represents a parsed Content-Type header.\n */\nexport interface ParsedContentType {\n mimeType: string;\n charset?: string;\n}\n\n/**\n * Utility functions for handling MIME types and charsets.\n */\n// biome-ignore lint/complexity/noStaticOnlyClass: helpers are static\nexport class MimeTypeUtils {\n /**\n * Parses a Content-Type header string into its MIME type and charset.\n * @param contentTypeHeader The Content-Type header string (e.g., \"text/html; charset=utf-8\").\n * @returns A ParsedContentType object, or a default if parsing fails.\n */\n public static parseContentType(contentTypeHeader?: string | null): ParsedContentType {\n if (!contentTypeHeader) {\n return { mimeType: \"application/octet-stream\" };\n }\n const parts = contentTypeHeader.split(\";\").map((part) => part.trim());\n const mimeType = parts[0].toLowerCase();\n let charset: string | undefined;\n\n for (let i = 1; i < parts.length; i++) {\n const param = parts[i];\n if (param.toLowerCase().startsWith(\"charset=\")) {\n charset = param.substring(\"charset=\".length).toLowerCase();\n break;\n }\n }\n return { mimeType, charset };\n }\n\n /**\n * Checks if a MIME type represents HTML content.\n */\n public static isHtml(mimeType: string): boolean {\n return mimeType === \"text/html\" || mimeType === \"application/xhtml+xml\";\n }\n\n /**\n * Checks if a MIME type represents Markdown content.\n */\n public static isMarkdown(mimeType: string): boolean {\n return mimeType === \"text/markdown\" || mimeType === \"text/x-markdown\";\n }\n\n /**\n * Checks if a MIME type represents plain text content.\n */\n public static isText(mimeType: string): boolean {\n return mimeType.startsWith(\"text/\");\n }\n\n // Extend with more helpers as needed (isJson, isXml, isPdf, etc.)\n}\n","import * as cheerio from \"cheerio\";\nimport { logger } from \"../../utils/logger\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Middleware to parse HTML string/buffer content into a Cheerio object.\n * It populates the `context.dom` property.\n * Assumes the input HTML in `context.content` is the final version to be parsed\n * (e.g., after potential rendering by Playwright or modification by JS execution).\n */\nexport class HtmlCheerioParserMiddleware implements ContentProcessorMiddleware {\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n try {\n logger.debug(`Parsing HTML content with Cheerio from ${context.source}`);\n // Load the HTML string using Cheerio\n const $ = cheerio.load(context.content);\n\n // Add the Cheerio API object to the context\n context.dom = $;\n\n // Proceed to the next middleware\n await next();\n } catch (error) {\n logger.error(\n `❌ Failed to parse HTML with Cheerio for ${context.source}: ${error}`,\n );\n context.errors.push(\n error instanceof Error\n ? error\n : new Error(`Cheerio HTML parsing failed: ${String(error)}`),\n );\n // Do not proceed further down the pipeline if parsing fails\n return;\n }\n }\n}\n","import { JSDOM, VirtualConsole } from \"jsdom\";\nimport type { ConstructorOptions } from \"jsdom\";\n\n/**\n * Creates a JSDOM instance with a pre-configured virtual console to suppress console noise.\n * This utility simplifies the setup of JSDOM by providing a standard configuration.\n *\n * @param html - The HTML content to parse.\n * @param options - Optional JSDOM configuration options. These will be merged with the default virtual console setup.\n * @returns A JSDOM instance.\n */\nexport function createJSDOM(html: string, options?: ConstructorOptions): JSDOM {\n const virtualConsole = new VirtualConsole();\n // Suppress console output from JSDOM by default\n virtualConsole.on(\"error\", () => {});\n virtualConsole.on(\"warn\", () => {});\n virtualConsole.on(\"info\", () => {});\n virtualConsole.on(\"debug\", () => {});\n virtualConsole.on(\"log\", () => {}); // Also suppress regular logs\n\n const defaultOptions: ConstructorOptions = {\n virtualConsole,\n };\n\n // Merge provided options with defaults, letting provided options override\n const finalOptions: ConstructorOptions = { ...defaultOptions, ...options };\n\n return new JSDOM(html, finalOptions);\n}\n","import { logger } from \"../../utils/logger\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Middleware to extract links (href attributes from <a> tags) from HTML content using Cheerio.\n * It expects the Cheerio API object to be available in `context.dom`.\n * This should run *after* parsing but *before* conversion to Markdown.\n */\nexport class HtmlLinkExtractorMiddleware implements ContentProcessorMiddleware {\n /**\n * Processes the context to extract links from the sanitized HTML body.\n * @param context The current middleware context.\n * @param next Function to call the next middleware.\n */\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n // Check if we have a Cheerio object from a previous step\n const $ = context.dom;\n if (!$) {\n logger.warn(\n `⏭️ Skipping ${this.constructor.name}: context.dom is missing. Ensure HtmlCheerioParserMiddleware runs before this.`,\n );\n await next();\n return;\n }\n\n try {\n const linkElements = $(\"a[href]\");\n logger.debug(`Found ${linkElements.length} potential links in ${context.source}`);\n\n const extractedLinks: string[] = [];\n linkElements.each((index, element) => {\n const href = $(element).attr(\"href\");\n if (href && href.trim() !== \"\") {\n try {\n const urlObj = new URL(href, context.source);\n if (![\"http:\", \"https:\", \"file:\"].includes(urlObj.protocol)) {\n logger.debug(`Ignoring link with invalid protocol: ${href}`);\n return;\n }\n extractedLinks.push(urlObj.href);\n } catch (e) {\n logger.debug(`Ignoring invalid URL syntax: ${href}`);\n }\n }\n });\n\n context.links = [...new Set(extractedLinks)];\n logger.debug(\n `Extracted ${context.links.length} unique, valid links from ${context.source}`,\n );\n } catch (error) {\n logger.error(`❌ Error extracting links from ${context.source}: ${error}`);\n context.errors.push(\n new Error(\n `Failed to extract links from HTML: ${error instanceof Error ? error.message : String(error)}`,\n ),\n );\n }\n\n await next();\n }\n}\n","import { logger } from \"../../utils/logger\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Middleware to extract the title from HTML content using Cheerio.\n * Assumes context.dom (Cheerio API object) is populated by a preceding middleware\n * (e.g., HtmlCheerioParserMiddleware).\n */\nexport class HtmlMetadataExtractorMiddleware implements ContentProcessorMiddleware {\n /**\n * Processes the context to extract the HTML title.\n * @param context The current processing context.\n * @param next Function to call the next middleware.\n */\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n // Check if Cheerio DOM exists from previous middleware\n const $ = context.dom;\n if (!$) {\n logger.warn(\n `⏭️ Skipping ${this.constructor.name}: context.dom is missing. Ensure HtmlCheerioParserMiddleware runs before this.`,\n );\n await next();\n return;\n }\n\n // Only process if we have a Cheerio object (implicitly means it's HTML)\n try {\n // Extract title (using title tag, fallback to h1 if title is empty/missing)\n let title = $(\"title\").first().text().trim();\n\n if (!title) {\n // Fallback to the first H1 if title is empty\n title = $(\"h1\").first().text().trim();\n }\n\n // Default to \"Untitled\" if both are empty\n title = title || \"Untitled\";\n\n // Basic cleanup (replace multiple spaces with single space)\n title = title.replace(/\\s+/g, \" \").trim();\n\n context.metadata.title = title;\n logger.debug(`Extracted title: \"${title}\" from ${context.source}`);\n } catch (error) {\n logger.error(`❌ Error extracting metadata from ${context.source}: ${error}`);\n context.errors.push(\n new Error(\n `Failed to extract metadata from HTML: ${error instanceof Error ? error.message : String(error)}`,\n ),\n );\n // Optionally decide whether to stop the pipeline here\n }\n\n // Call the next middleware in the chain\n await next();\n\n // No cleanup needed for Cheerio\n }\n}\n","/**\n * Default configuration values for the scraping pipeline and server\n */\n\n/** Maximum number of pages to scrape in a single job */\nexport const DEFAULT_MAX_PAGES = 1000;\n\n/** Maximum navigation depth when crawling links */\nexport const DEFAULT_MAX_DEPTH = 3;\n\n/** Maximum number of concurrent page requests */\nexport const DEFAULT_MAX_CONCURRENCY = 3;\n\n/** Default protocol for the MCP server */\nexport const DEFAULT_PROTOCOL = \"stdio\";\n\n/** Default port for the HTTP protocol */\nexport const DEFAULT_HTTP_PORT = 6280;\n\n/** Default port for the Web UI */\nexport const DEFAULT_WEB_PORT = 6281;\n\n/**\n * Default timeout in milliseconds for page operations (e.g., Playwright waitForSelector).\n */\nexport const DEFAULT_PAGE_TIMEOUT = 5000;\n\n/**\n * Maximum number of retries for HTTP fetcher requests.\n */\nexport const FETCHER_MAX_RETRIES = 6;\n\n/**\n * Base delay in milliseconds for HTTP fetcher retry backoff.\n */\nexport const FETCHER_BASE_DELAY = 1000;\n\n/**\n * Default chunk size settings for splitters\n */\nexport const SPLITTER_MIN_CHUNK_SIZE = 500;\nexport const SPLITTER_PREFERRED_CHUNK_SIZE = 1500;\nexport const SPLITTER_MAX_CHUNK_SIZE = 5000;\n\n/**\n * Maximum number of documents to process in a single batch for embeddings.\n */\nexport const EMBEDDING_BATCH_SIZE = 100;\n\n/**\n * Maximum number of retries for database migrations if busy.\n */\nexport const MIGRATION_MAX_RETRIES = 5;\n\n/**\n * Delay in milliseconds between migration retry attempts.\n */\nexport const MIGRATION_RETRY_DELAY_MS = 300;\n","import type { Document, ProgressCallback } from \"../types\";\n\n/**\n * Enum defining the available HTML processing strategies.\n */\nexport enum ScrapeMode {\n Fetch = \"fetch\",\n Playwright = \"playwright\",\n Auto = \"auto\",\n}\n\n/**\n * Strategy interface for implementing different scraping behaviors\n */\nexport interface ScraperStrategy {\n canHandle(url: string): boolean;\n scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal, // Add optional signal\n ): Promise<void>;\n}\n\n/**\n * Options for configuring the scraping process\n */\nexport interface ScraperOptions {\n url: string;\n library: string;\n version: string;\n maxPages?: number;\n maxDepth?: number;\n /**\n * Defines the allowed crawling boundary relative to the starting URL\n * - 'subpages': Only crawl URLs on the same hostname and within the same starting path (default)\n * - 'hostname': Crawl any URL on the same exact hostname, regardless of path\n * - 'domain': Crawl any URL on the same top-level domain, including subdomains\n */\n scope?: \"subpages\" | \"hostname\" | \"domain\";\n /**\n * Controls whether HTTP redirects (3xx responses) should be followed\n * - When true: Redirects are followed automatically (default)\n * - When false: A RedirectError is thrown when a 3xx response is received\n */\n followRedirects?: boolean;\n maxConcurrency?: number;\n ignoreErrors?: boolean;\n /** CSS selectors for elements to exclude during HTML processing */\n excludeSelectors?: string[];\n /**\n * Determines the HTML processing strategy.\n * - 'fetch': Use a simple DOM parser (faster, less JS support).\n * - 'playwright': Use a headless browser (slower, full JS support).\n * - 'auto': Automatically select the best strategy (currently defaults to 'playwright').\n * @default ScrapeMode.Auto\n */\n scrapeMode?: ScrapeMode;\n /** Optional AbortSignal for cancellation */\n signal?: AbortSignal;\n /**\n * Patterns for including URLs during scraping. If not set, all are included by default.\n */\n includePatterns?: string[];\n /**\n * Patterns for excluding URLs during scraping. Exclude takes precedence over include.\n */\n excludePatterns?: string[];\n /**\n * Custom HTTP headers to send with each HTTP request (e.g., for authentication).\n * Keys are header names, values are header values.\n */\n headers?: Record<string, string>;\n}\n\n/**\n * Result of scraping a single page. Used internally by HtmlScraper.\n */\nexport interface ScrapedPage {\n content: string;\n title: string;\n url: string;\n /** URLs extracted from page links, used for recursive scraping */\n links: string[];\n}\n\n/**\n * Progress information during scraping\n */\nexport interface ScraperProgress {\n pagesScraped: number;\n maxPages: number;\n currentUrl: string;\n depth: number;\n maxDepth: number;\n document?: Document;\n}\n","import { type Browser, type BrowserContext, type Page, chromium } from \"playwright\";\nimport { DEFAULT_PAGE_TIMEOUT } from \"../../utils/config\";\nimport { logger } from \"../../utils/logger\";\nimport { ScrapeMode } from \"../types\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Middleware to process HTML content using Playwright for rendering dynamic content,\n * *if* the scrapeMode option requires it ('playwright' or 'auto').\n * It updates `context.content` with the rendered HTML if Playwright runs.\n * Subsequent middleware (e.g., HtmlCheerioParserMiddleware) should handle parsing this content.\n *\n * This middleware also supports URLs with embedded credentials (user:password@host) and ensures\n * credentials are used for all same-origin resource requests (not just the main page) via HTTP Basic Auth.\n *\n * Additionally, all custom headers from context.options?.headers are forwarded to Playwright requests.\n */\nexport class HtmlPlaywrightMiddleware implements ContentProcessorMiddleware {\n private browser: Browser | null = null;\n\n /**\n * Initializes the Playwright browser instance.\n * Consider making this more robust (e.g., lazy initialization, singleton).\n */\n private async ensureBrowser(): Promise<Browser> {\n if (!this.browser || !this.browser.isConnected()) {\n const launchArgs = process.env.PLAYWRIGHT_LAUNCH_ARGS?.split(\" \") ?? [];\n const executablePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || undefined;\n logger.debug(\n `Launching new Playwright browser instance (Chromium) with args: ${launchArgs.join(\" \") || \"none\"}...`,\n );\n this.browser = await chromium.launch({\n channel: \"chromium\",\n args: launchArgs,\n executablePath: executablePath,\n });\n this.browser.on(\"disconnected\", () => {\n logger.debug(\"Playwright browser instance disconnected.\");\n this.browser = null;\n });\n }\n return this.browser;\n }\n\n /**\n * Closes the Playwright browser instance if it exists.\n * Should be called during application shutdown.\n */\n async closeBrowser(): Promise<void> {\n if (this.browser?.isConnected()) {\n logger.debug(\"Closing Playwright browser instance...\");\n await this.browser.close();\n this.browser = null;\n }\n }\n\n /**\n * Waits for common loading indicators (spinners, loaders) that are currently visible to disappear from the page.\n * Only waits for selectors that are present and visible at the time of check.\n *\n * @param page The Playwright page instance to operate on.\n */\n private async waitForLoadingToComplete(page: Page): Promise<void> {\n const commonLoadingSelectors = [\n '[class*=\"loading\"]',\n '[class*=\"spinner\"]',\n '[class*=\"loader\"]',\n '[id*=\"loading\"]',\n '[class*=\"preload\"]',\n \"#loading\",\n '[aria-label*=\"loading\" i]',\n '[aria-label*=\"spinner\" i]',\n ];\n\n // Wait for all visible loading indicators in parallel\n const waitPromises: Promise<unknown>[] = [];\n for (const selector of commonLoadingSelectors) {\n try {\n // Use page.isVisible to check if any matching element is visible (legacy API, but works for any visible match)\n const isVisible = await page.isVisible(selector).catch(() => false);\n if (isVisible) {\n waitPromises.push(\n page\n .waitForSelector(selector, {\n state: \"hidden\",\n timeout: DEFAULT_PAGE_TIMEOUT,\n })\n .catch(() => {}),\n );\n }\n } catch {\n // Ignore errors (e.g., selector not found or timeout)\n }\n }\n if (waitPromises.length > 0) {\n await Promise.all(waitPromises);\n }\n }\n\n /**\n * Processes the context using Playwright, rendering dynamic content and propagating credentials for all same-origin requests.\n *\n * - Parses credentials from the URL (if present).\n * - Uses browser.newContext({ httpCredentials }) for HTTP Basic Auth on the main page and subresources.\n * - Injects Authorization header for all same-origin requests if credentials are present and not already set.\n * - Forwards all custom headers from context.options?.headers to Playwright requests.\n * - Waits for common loading indicators to disappear before extracting HTML.\n *\n * @param context The middleware context containing the HTML and source URL.\n * @param next The next middleware function in the pipeline.\n */\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n // Always process, content type is handled by pipeline selection\n\n // Determine if Playwright should run based on scrapeMode\n const scrapeMode = context.options?.scrapeMode ?? ScrapeMode.Auto;\n const shouldRunPlaywright =\n scrapeMode === ScrapeMode.Playwright || scrapeMode === ScrapeMode.Auto;\n\n if (!shouldRunPlaywright) {\n logger.debug(\n `Skipping Playwright rendering for ${context.source} as scrapeMode is '${scrapeMode}'.`,\n );\n await next();\n return;\n }\n\n logger.debug(\n `Running Playwright rendering for ${context.source} (scrapeMode: '${scrapeMode}')`,\n );\n\n let page: Page | null = null;\n let browserContext: BrowserContext | null = null;\n let renderedHtml: string | null = null;\n\n // Extract credentials and origin using helper\n const { credentials, origin } = extractCredentialsAndOrigin(context.source);\n\n // Extract custom headers (Record<string, string>)\n const customHeaders: Record<string, string> = context.options?.headers ?? {};\n\n try {\n const browser = await this.ensureBrowser();\n if (credentials) {\n browserContext = await browser.newContext({ httpCredentials: credentials });\n page = await browserContext.newPage();\n } else {\n page = await browser.newPage();\n }\n logger.debug(`Playwright: Processing ${context.source}`);\n\n // Block unnecessary resources and inject credentials and custom headers for same-origin requests\n await page.route(\"**/*\", async (route) => {\n const reqUrl = route.request().url();\n const reqOrigin = (() => {\n try {\n return new URL(reqUrl).origin;\n } catch {\n return null;\n }\n })();\n // Serve the initial HTML for the main page\n if (reqUrl === context.source) {\n return route.fulfill({\n status: 200,\n contentType: \"text/html\",\n body: context.content,\n });\n }\n // Abort non-essential resources\n const resourceType = route.request().resourceType();\n if ([\"image\", \"stylesheet\", \"font\", \"media\"].includes(resourceType)) {\n return route.abort();\n }\n // Use helper to merge headers\n const headers = mergePlaywrightHeaders(\n route.request().headers(),\n customHeaders,\n credentials ?? undefined,\n origin ?? undefined,\n reqOrigin ?? undefined,\n );\n return route.continue({ headers });\n });\n\n // Load initial HTML content\n await page.goto(context.source, { waitUntil: \"load\" });\n await page.waitForSelector(\"body\");\n await this.waitForLoadingToComplete(page);\n // await page.waitForLoadState(\"networkidle\");\n\n renderedHtml = await page.content();\n logger.debug(`Playwright: Successfully rendered content for ${context.source}`);\n } catch (error) {\n logger.error(`❌ Playwright failed to render ${context.source}: ${error}`);\n context.errors.push(\n error instanceof Error\n ? error\n : new Error(`Playwright rendering failed: ${String(error)}`),\n );\n } finally {\n // Ensure page/context are closed even if subsequent steps fail\n if (page) {\n await page.unroute(\"**/*\");\n await page.close();\n }\n if (browserContext) {\n await browserContext.close();\n }\n }\n\n if (renderedHtml !== null) {\n context.content = renderedHtml;\n logger.debug(\n `Playwright middleware updated content for ${context.source}. Proceeding.`,\n );\n } else {\n logger.warn(\n `⚠️ Playwright rendering resulted in null content for ${context.source}. Proceeding without content update.`,\n );\n }\n\n await next();\n }\n}\n\n/**\n * Extracts credentials and origin from a URL string.\n * Returns { credentials, origin } where credentials is null if not present.\n */\nexport function extractCredentialsAndOrigin(urlString: string): {\n credentials: { username: string; password: string } | null;\n origin: string | null;\n} {\n try {\n const url = new URL(urlString);\n const origin = url.origin;\n if (url.username && url.password) {\n return {\n credentials: { username: url.username, password: url.password },\n origin,\n };\n }\n return { credentials: null, origin };\n } catch {\n return { credentials: null, origin: null };\n }\n}\n\n/**\n * Merges Playwright request headers, custom headers, and credentials.\n * - Custom headers are merged in unless already present (except Authorization, see below).\n * - If credentials are present and the request is same-origin, injects Authorization if not already set.\n */\nexport function mergePlaywrightHeaders(\n requestHeaders: Record<string, string>,\n customHeaders: Record<string, string>,\n credentials?: { username: string; password: string },\n origin?: string,\n reqOrigin?: string,\n): Record<string, string> {\n let headers = { ...requestHeaders };\n for (const [key, value] of Object.entries(customHeaders)) {\n if (key.toLowerCase() === \"authorization\" && headers.authorization) continue;\n headers[key] = value;\n }\n if (credentials && origin && reqOrigin === origin && !headers.authorization) {\n const basic = Buffer.from(`${credentials.username}:${credentials.password}`).toString(\n \"base64\",\n );\n headers = {\n ...headers,\n Authorization: `Basic ${basic}`,\n };\n }\n return headers;\n}\n","import { logger } from \"../../utils/logger\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Options for HtmlSanitizerMiddleware.\n */\nexport interface HtmlSanitizerOptions {\n /** CSS selectors for elements to remove *in addition* to the defaults. */\n excludeSelectors?: string[];\n}\n\n/**\n * Middleware to remove unwanted elements from parsed HTML content using Cheerio.\n * It expects the Cheerio API object (`context.dom`) to be populated by a preceding middleware\n * (e.g., HtmlCheerioParserMiddleware).\n * It modifies the `context.dom` object in place.\n */\nexport class HtmlSanitizerMiddleware implements ContentProcessorMiddleware {\n // Default selectors to remove\n private readonly defaultSelectorsToRemove = [\n \"nav\",\n \"footer\",\n \"script\",\n \"style\",\n \"noscript\",\n \"svg\",\n \"link\",\n \"meta\",\n \"iframe\",\n \"header\",\n \"button\",\n \"input\",\n \"textarea\",\n \"select\",\n // \"form\", // Keep commented\n \".ads\",\n \".advertisement\",\n \".banner\",\n \".cookie-banner\",\n \".cookie-consent\",\n \".hidden\",\n \".hide\",\n \".modal\",\n \".nav-bar\",\n \".overlay\",\n \".popup\",\n \".promo\",\n \".mw-editsection\",\n \".side-bar\",\n \".social-share\",\n \".sticky\",\n \"#ads\",\n \"#banner\",\n \"#cookieBanner\",\n \"#modal\",\n \"#nav\",\n \"#overlay\",\n \"#popup\",\n \"#sidebar\",\n \"#socialMediaBox\",\n \"#stickyHeader\",\n \"#ad-container\",\n \".ad-container\",\n \".login-form\",\n \".signup-form\",\n \".tooltip\",\n \".dropdown-menu\",\n // \".alert\", // Keep commented\n \".breadcrumb\",\n \".pagination\",\n // '[role=\"alert\"]', // Keep commented\n '[role=\"banner\"]',\n '[role=\"dialog\"]',\n '[role=\"alertdialog\"]',\n '[role=\"region\"][aria-label*=\"skip\" i]',\n '[aria-modal=\"true\"]',\n \".noprint\",\n ];\n\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n // Check if Cheerio DOM exists\n const $ = context.dom;\n if (!$) {\n logger.warn(\n `⏭️ Skipping ${this.constructor.name}: context.dom is missing. Ensure HtmlCheerioParserMiddleware runs before this.`,\n );\n await next();\n return;\n }\n\n try {\n // Remove unwanted elements using Cheerio\n const selectorsToRemove = [\n ...(context.options.excludeSelectors || []), // Use options from the context\n ...this.defaultSelectorsToRemove,\n ];\n logger.debug(\n `Removing elements matching ${selectorsToRemove.length} selectors for ${context.source}`,\n );\n let removedCount = 0;\n for (const selector of selectorsToRemove) {\n try {\n const elements = $(selector); // Use Cheerio selector\n const count = elements.length;\n if (count > 0) {\n elements.remove(); // Use Cheerio remove\n removedCount += count;\n }\n } catch (selectorError) {\n // Log invalid selectors but continue with others\n // Cheerio is generally more tolerant of invalid selectors than querySelectorAll\n logger.warn(\n `⚠️ Potentially invalid selector \"${selector}\" during element removal: ${selectorError}`,\n );\n context.errors.push(\n new Error(`Invalid selector \"${selector}\": ${selectorError}`),\n );\n }\n }\n logger.debug(`Removed ${removedCount} elements for ${context.source}`);\n\n // The context.dom object ($) has been modified in place.\n } catch (error) {\n logger.error(\n `❌ Error during HTML element removal for ${context.source}: ${error}`,\n );\n context.errors.push(\n error instanceof Error\n ? error\n : new Error(`HTML element removal failed: ${String(error)}`),\n );\n // Decide if pipeline should stop? For now, continue.\n }\n\n // Proceed to the next middleware\n await next();\n }\n}\n","// @ts-ignore\nimport { gfm } from \"@joplin/turndown-plugin-gfm\";\nimport TurndownService from \"turndown\";\nimport { logger } from \"../../utils/logger\"; // Added logger\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Middleware to convert the final processed HTML content (from Cheerio object in context.dom)\n * into Markdown using Turndown, applying custom rules.\n */\nexport class HtmlToMarkdownMiddleware implements ContentProcessorMiddleware {\n private turndownService: TurndownService;\n\n constructor() {\n this.turndownService = new TurndownService({\n headingStyle: \"atx\",\n hr: \"---\",\n bulletListMarker: \"-\",\n codeBlockStyle: \"fenced\",\n emDelimiter: \"_\",\n strongDelimiter: \"**\",\n linkStyle: \"inlined\",\n });\n\n this.turndownService.use(gfm);\n\n this.addCustomRules();\n }\n\n private addCustomRules(): void {\n // Preserve code blocks and syntax (replicated from HtmlProcessor)\n this.turndownService.addRule(\"pre\", {\n filter: [\"pre\"],\n replacement: (content, node) => {\n const element = node as unknown as HTMLElement;\n let language = element.getAttribute(\"data-language\") || \"\";\n if (!language) {\n // Try to infer the language from the class name\n // This is a common pattern in syntax highlighters\n const highlightElement =\n element.closest(\n '[class*=\"highlight-source-\"], [class*=\"highlight-\"], [class*=\"language-\"]',\n ) ||\n element.querySelector(\n '[class*=\"highlight-source-\"], [class*=\"highlight-\"], [class*=\"language-\"]',\n );\n if (highlightElement) {\n const className = highlightElement.className;\n const match = className.match(\n /(?:highlight-source-|highlight-|language-)(\\w+)/,\n );\n if (match) language = match[1];\n }\n }\n\n const brElements = Array.from(element.querySelectorAll(\"br\"));\n for (const br of brElements) {\n br.replaceWith(\"\\n\");\n }\n const text = element.textContent || \"\";\n\n return `\\n\\`\\`\\`${language}\\n${text.replace(/^\\n+|\\n+$/g, \"\")}\\n\\`\\`\\`\\n`;\n },\n });\n this.turndownService.addRule(\"anchor\", {\n filter: [\"a\"],\n replacement: (content, node) => {\n const href = (node as HTMLElement).getAttribute(\"href\");\n if (!content || content === \"#\") {\n return \"\"; // Remove if content is # or empty\n }\n if (!href) {\n return content; // Preserve content if href is missing or empty\n }\n return `[${content}](${href})`; // Standard link conversion\n },\n });\n }\n\n /**\n * Processes the context to convert the sanitized HTML body node to Markdown.\n * @param context The current processing context.\n * @param next Function to call the next middleware.\n */\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n // Check if we have a Cheerio object from a previous step\n const $ = context.dom;\n if (!$) {\n logger.warn(\n `⏭️ Skipping ${this.constructor.name}: context.dom is missing. Ensure HtmlCheerioParserMiddleware ran correctly.`,\n );\n await next();\n return;\n }\n\n // Only process if we have a Cheerio object (implicitly means it's HTML)\n try {\n logger.debug(`Converting HTML content to Markdown for ${context.source}`);\n // Provide Turndown with the HTML string content from the Cheerio object's body,\n // or the whole document if body is empty/unavailable.\n const htmlToConvert = $(\"body\").html() || $.html();\n const markdown = this.turndownService.turndown(htmlToConvert).trim();\n\n if (!markdown) {\n // If conversion results in empty markdown, log a warning but treat as valid empty markdown\n const warnMsg = `HTML to Markdown conversion resulted in empty content for ${context.source}.`;\n logger.warn(`⚠️ ${warnMsg}`);\n context.content = \"\";\n } else {\n // Conversion successful and produced non-empty markdown\n context.content = markdown;\n logger.debug(`Successfully converted HTML to Markdown for ${context.source}`);\n }\n } catch (error) {\n logger.error(\n `❌ Error converting HTML to Markdown for ${context.source}: ${error}`,\n );\n context.errors.push(\n new Error(\n `Failed to convert HTML to Markdown: ${error instanceof Error ? error.message : String(error)}`,\n ),\n );\n // Decide if pipeline should stop? For now, continue.\n }\n\n // Call the next middleware in the chain regardless of whether conversion happened\n await next();\n\n // No need to close/free Cheerio object explicitly\n // context.dom = undefined; // Optionally clear the dom property if no longer needed downstream\n }\n}\n","import type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Placeholder middleware for extracting links from Markdown content.\n * Currently, it does not implement link extraction, matching the\n * original MarkdownProcessor's TODO status.\n */\nexport class MarkdownLinkExtractorMiddleware implements ContentProcessorMiddleware {\n /**\n * Processes the context. Currently a no-op regarding link extraction.\n * @param context The current processing context.\n * @param next Function to call the next middleware.\n */\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n // TODO: Implement Markdown link extraction (e.g., using regex or a Markdown parser)\n // For now, ensure context.links exists, defaulting to empty array if not set.\n if (!Array.isArray(context.links)) {\n context.links = [];\n }\n // No links are added here yet.\n\n await next();\n }\n}\n","import type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Middleware to extract the title (first H1 heading) from Markdown content.\n */\nexport class MarkdownMetadataExtractorMiddleware implements ContentProcessorMiddleware {\n /**\n * Processes the context to extract the title from Markdown.\n * @param context The current processing context.\n * @param next Function to call the next middleware.\n */\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n try {\n let title = \"Untitled\";\n const match = context.content.match(/^#\\s+(.*)$/m);\n if (match?.[1]) {\n title = match[1].trim();\n }\n context.metadata.title = title;\n } catch (error) {\n context.errors.push(\n new Error(\n `Failed to extract metadata from Markdown: ${error instanceof Error ? error.message : String(error)}`,\n ),\n );\n }\n\n await next();\n }\n}\n","import iconv from \"iconv-lite\";\n\n/**\n * Decodes a Buffer or string to a JavaScript string using the specified charset.\n * The charset should be the encoding as reported by the source (e.g., HTTP header).\n * The result is always a valid JS string (Unicode/UTF-16).\n *\n * If the charset is missing or unsupported, falls back to UTF-8.\n *\n * @param content The content to decode (Buffer or string)\n * @param charset The source encoding (e.g., 'utf-8', 'iso-8859-1', 'utf-16le', etc.)\n * @returns The decoded string\n */\nexport function convertToString(content: string | Buffer, charset?: string): string {\n if (typeof content === \"string\") return content;\n try {\n return iconv.decode(content, charset || \"utf-8\");\n } catch {\n // Fallback to utf-8 if decoding fails\n return iconv.decode(content, \"utf-8\");\n }\n}\n","import type { ContentFetcher, RawContent } from \"../fetcher/types\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"../middleware/types\";\nimport type { ScraperOptions } from \"../types\";\nimport type { ContentPipeline, ProcessedContent } from \"./types\";\n\n/**\n * Base class for content processing pipelines.\n * Provides common functionality for executing middleware stacks.\n */\nexport class BasePipeline implements ContentPipeline {\n /**\n * Determines if this pipeline can process the given content.\n * Must be implemented by derived classes.\n */\n public canProcess(_rawContent: RawContent): boolean {\n throw new Error(\"Method not implemented.\");\n }\n\n /**\n * Processes the raw content through the pipeline.\n * Must be implemented by derived classes.\n */\n public async process(\n _rawContent: RawContent,\n _options: ScraperOptions,\n _fetcher?: ContentFetcher,\n ): Promise<ProcessedContent> {\n throw new Error(\"Method not implemented.\");\n }\n\n /**\n * Executes a middleware stack on the given context.\n * This is a utility method used by derived pipeline classes.\n *\n * @param middleware - The middleware stack to execute\n * @param context - The context to process\n */\n protected async executeMiddlewareStack(\n middleware: ContentProcessorMiddleware[],\n context: MiddlewareContext,\n ): Promise<void> {\n let index = -1;\n const dispatch = async (i: number): Promise<void> => {\n if (i <= index) throw new Error(\"next() called multiple times\");\n index = i;\n const mw = middleware[i];\n if (!mw) return;\n await mw.process(context, dispatch.bind(null, i + 1));\n };\n\n try {\n await dispatch(0);\n } catch (error) {\n context.errors.push(error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n /**\n * Cleans up resources when the pipeline is no longer needed.\n * Default implementation does nothing.\n */\n public async close(): Promise<void> {\n // Default implementation does nothing\n }\n}\n","import { MimeTypeUtils } from \"../../utils/mimeTypeUtils\";\nimport type { RawContent } from \"../fetcher/types\";\nimport type { ContentFetcher } from \"../fetcher/types\";\nimport { HtmlSanitizerMiddleware } from \"../middleware\";\nimport { HtmlCheerioParserMiddleware } from \"../middleware/HtmlCheerioParserMiddleware\";\nimport { HtmlLinkExtractorMiddleware } from \"../middleware/HtmlLinkExtractorMiddleware\";\nimport { HtmlMetadataExtractorMiddleware } from \"../middleware/HtmlMetadataExtractorMiddleware\";\nimport { HtmlPlaywrightMiddleware } from \"../middleware/HtmlPlaywrightMiddleware\";\nimport { HtmlToMarkdownMiddleware } from \"../middleware/HtmlToMarkdownMiddleware\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"../middleware/types\";\nimport type { ScraperOptions } from \"../types\";\nimport { convertToString } from \"../utils/buffer\";\nimport { BasePipeline } from \"./BasePipeline\";\nimport type { ProcessedContent } from \"./types\";\n\n/**\n * Pipeline for processing HTML content using middleware.\n */\nexport class HtmlPipeline extends BasePipeline {\n private readonly playwrightMiddleware: HtmlPlaywrightMiddleware;\n private readonly standardMiddleware: ContentProcessorMiddleware[];\n\n constructor() {\n super();\n this.playwrightMiddleware = new HtmlPlaywrightMiddleware();\n this.standardMiddleware = [\n new HtmlCheerioParserMiddleware(),\n new HtmlMetadataExtractorMiddleware(),\n new HtmlLinkExtractorMiddleware(),\n new HtmlSanitizerMiddleware(),\n new HtmlToMarkdownMiddleware(),\n ];\n }\n\n canProcess(rawContent: RawContent): boolean {\n return MimeTypeUtils.isHtml(rawContent.mimeType);\n }\n\n async process(\n rawContent: RawContent,\n options: ScraperOptions,\n fetcher?: ContentFetcher,\n ): Promise<ProcessedContent> {\n const contentString = convertToString(rawContent.content, rawContent.charset);\n\n const context: MiddlewareContext = {\n content: contentString,\n source: rawContent.source,\n metadata: {},\n links: [],\n errors: [],\n options,\n fetcher,\n };\n\n // Build middleware stack dynamically based on scrapeMode\n let middleware: ContentProcessorMiddleware[] = [...this.standardMiddleware];\n if (options.scrapeMode === \"playwright\" || options.scrapeMode === \"auto\") {\n middleware = [this.playwrightMiddleware, ...middleware];\n }\n\n // Execute the middleware stack using the base class method\n await this.executeMiddlewareStack(middleware, context);\n\n return {\n textContent: typeof context.content === \"string\" ? context.content : \"\",\n metadata: context.metadata,\n links: context.links,\n errors: context.errors,\n };\n }\n\n async close(): Promise<void> {\n await this.playwrightMiddleware.closeBrowser();\n }\n}\n","import { MimeTypeUtils } from \"../../utils/mimeTypeUtils\";\nimport type { RawContent } from \"../fetcher/types\";\nimport type { ContentFetcher } from \"../fetcher/types\";\nimport { MarkdownLinkExtractorMiddleware } from \"../middleware/MarkdownLinkExtractorMiddleware\";\nimport { MarkdownMetadataExtractorMiddleware } from \"../middleware/MarkdownMetadataExtractorMiddleware\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"../middleware/types\";\nimport type { ScraperOptions } from \"../types\";\nimport { convertToString } from \"../utils/buffer\";\nimport { BasePipeline } from \"./BasePipeline\";\nimport type { ProcessedContent } from \"./types\";\n\n/**\n * Pipeline for processing Markdown content using middleware.\n */\nexport class MarkdownPipeline extends BasePipeline {\n private readonly middleware: ContentProcessorMiddleware[];\n\n constructor() {\n super();\n this.middleware = [\n new MarkdownMetadataExtractorMiddleware(),\n new MarkdownLinkExtractorMiddleware(),\n ];\n }\n\n canProcess(rawContent: RawContent): boolean {\n if (!rawContent.mimeType) return false;\n return (\n MimeTypeUtils.isMarkdown(rawContent.mimeType) ||\n MimeTypeUtils.isText(rawContent.mimeType)\n );\n }\n\n async process(\n rawContent: RawContent,\n options: ScraperOptions,\n fetcher?: ContentFetcher,\n ): Promise<ProcessedContent> {\n const contentString = convertToString(rawContent.content, rawContent.charset);\n\n const context: MiddlewareContext = {\n content: contentString,\n source: rawContent.source,\n metadata: {},\n links: [],\n errors: [],\n options,\n fetcher,\n };\n\n // Execute the middleware stack using the base class method\n await this.executeMiddlewareStack(this.middleware, context);\n\n return {\n textContent: typeof context.content === \"string\" ? context.content : \"\",\n metadata: context.metadata,\n links: context.links,\n errors: context.errors,\n };\n }\n\n async close(): Promise<void> {}\n}\n","class ScraperError extends Error {\n constructor(\n message: string,\n public readonly isRetryable: boolean = false,\n public readonly cause?: Error,\n ) {\n super(message);\n this.name = this.constructor.name;\n if (cause?.stack) {\n this.stack = `${this.stack}\\nCaused by: ${cause.stack}`;\n }\n }\n}\n\nclass NetworkError extends ScraperError {\n constructor(\n message: string,\n public readonly statusCode?: number,\n cause?: Error,\n ) {\n super(message, true, cause);\n }\n}\n\nclass RateLimitError extends ScraperError {\n constructor(\n message: string,\n public readonly retryAfter?: number,\n ) {\n super(message, true);\n }\n}\n\nclass InvalidUrlError extends ScraperError {\n constructor(url: string, cause?: Error) {\n super(`Invalid URL: ${url}`, false, cause);\n }\n}\n\nclass ParsingError extends ScraperError {\n constructor(message: string, cause?: Error) {\n super(`Failed to parse content: ${message}`, false, cause);\n }\n}\n\nclass RedirectError extends ScraperError {\n constructor(\n public readonly originalUrl: string,\n public readonly redirectUrl: string,\n public readonly statusCode: number,\n ) {\n super(\n `Redirect detected from ${originalUrl} to ${redirectUrl} (status: ${statusCode})`,\n false,\n );\n }\n}\n\nexport {\n ScraperError,\n NetworkError,\n RateLimitError,\n InvalidUrlError,\n ParsingError,\n RedirectError,\n};\n","import type {\n ContentFetcher,\n FileFetcher,\n HttpFetcher,\n RawContent,\n} from \"../scraper/fetcher\";\nimport { HtmlPipeline } from \"../scraper/pipelines/HtmlPipeline\";\nimport { MarkdownPipeline } from \"../scraper/pipelines/MarkdownPipeline\";\nimport { ScrapeMode } from \"../scraper/types\";\nimport { ScraperError } from \"../utils/errors\";\nimport { logger } from \"../utils/logger\";\nimport { ToolError } from \"./errors\";\n\nexport interface FetchUrlToolOptions {\n /**\n * The URL to fetch and convert to markdown.\n * Must be a valid HTTP/HTTPS URL or file:// URL.\n */\n url: string;\n\n /**\n * Whether to follow HTTP redirects.\n * @default true\n */\n followRedirects?: boolean;\n\n /**\n * Determines the HTML processing strategy.\n * - 'fetch': Use a simple DOM parser (faster, less JS support).\n * - 'playwright': Use a headless browser (slower, full JS support).\n * - 'auto': Automatically select the best strategy (currently defaults to 'playwright').\n * @default ScrapeMode.Auto\n */\n scrapeMode?: ScrapeMode;\n\n /**\n * Custom HTTP headers to send with the request (e.g., for authentication).\n * Keys are header names, values are header values.\n */\n headers?: Record<string, string>;\n}\n\n/**\n * Tool for fetching a single URL and converting its content to Markdown.\n * Unlike scrape_docs, this tool only processes one page without crawling\n * or storing the content.\n *\n * Supports both HTTP/HTTPS URLs and local file URLs (file://).\n */\nexport class FetchUrlTool {\n /**\n * Collection of fetchers that will be tried in order for a given URL.\n */\n private readonly fetchers: ContentFetcher[];\n\n constructor(httpFetcher: HttpFetcher, fileFetcher: FileFetcher) {\n this.fetchers = [httpFetcher, fileFetcher];\n }\n\n /**\n * Fetches content from a URL and converts it to Markdown.\n * Supports both HTTP/HTTPS URLs and local file URLs (file://).\n * @returns The processed Markdown content\n * @throws {ToolError} If fetching or processing fails\n */\n async execute(options: FetchUrlToolOptions): Promise<string> {\n const { url, scrapeMode = ScrapeMode.Auto, headers } = options;\n\n const canFetchResults = this.fetchers.map((f) => f.canFetch(url));\n const fetcherIndex = canFetchResults.findIndex((result) => result === true);\n if (fetcherIndex === -1) {\n throw new ToolError(\n `Invalid URL: ${url}. Must be an HTTP/HTTPS URL or a file:// URL.`,\n this.constructor.name,\n );\n }\n\n const fetcher = this.fetchers[fetcherIndex];\n const htmlPipeline = new HtmlPipeline();\n const markdownPipeline = new MarkdownPipeline();\n const pipelines = [htmlPipeline, markdownPipeline];\n\n try {\n logger.info(`📡 Fetching ${url}...`);\n const rawContent: RawContent = await fetcher.fetch(url, {\n followRedirects: options.followRedirects ?? true,\n maxRetries: 3,\n headers, // propagate custom headers\n });\n\n logger.info(\"🔄 Processing content...\");\n\n let processed: Awaited<ReturnType<(typeof htmlPipeline)[\"process\"]>> | undefined;\n for (const pipeline of pipelines) {\n if (pipeline.canProcess(rawContent)) {\n processed = await pipeline.process(\n rawContent,\n {\n url,\n library: \"\",\n version: \"\",\n maxDepth: 0,\n maxPages: 1,\n maxConcurrency: 1,\n scope: \"subpages\",\n followRedirects: options.followRedirects ?? true,\n excludeSelectors: undefined,\n ignoreErrors: false,\n scrapeMode,\n headers, // propagate custom headers\n },\n fetcher,\n );\n break;\n }\n }\n\n if (!processed) {\n logger.warn(\n `⚠️ Unsupported content type \"${rawContent.mimeType}\" for ${url}. Returning raw content.`,\n );\n const contentString =\n typeof rawContent.content === \"string\"\n ? rawContent.content\n : Buffer.from(rawContent.content).toString(\"utf-8\");\n return contentString;\n }\n\n for (const err of processed.errors) {\n logger.warn(`⚠️ Processing error for ${url}: ${err.message}`);\n }\n\n if (typeof processed.textContent !== \"string\" || !processed.textContent.trim()) {\n throw new ToolError(\n `Processing resulted in empty content for ${url}`,\n this.constructor.name,\n );\n }\n\n logger.info(`✅ Successfully processed ${url}`);\n return processed.textContent;\n } catch (error) {\n if (error instanceof ScraperError || error instanceof ToolError) {\n throw new ToolError(\n `Failed to fetch or process URL: ${error.message}`,\n this.constructor.name,\n );\n }\n throw new ToolError(\n `Failed to fetch or process URL: ${error instanceof Error ? error.message : String(error)}`,\n this.constructor.name,\n );\n } finally {\n await htmlPipeline.close();\n await markdownPipeline.close();\n }\n }\n}\n","import type { DocumentManagementService } from \"../store\";\nimport { logger } from \"../utils/logger\";\nimport { VersionNotFoundError } from \"./errors\";\n\nexport interface FindVersionToolOptions {\n library: string;\n targetVersion?: string;\n}\n\n/**\n * Tool for finding the best matching version of a library in the store.\n * Supports exact version matches and X-Range patterns (e.g., '5.x', '5.2.x').\n */\nexport class FindVersionTool {\n private docService: DocumentManagementService;\n\n constructor(docService: DocumentManagementService) {\n this.docService = docService;\n }\n\n /**\n * Executes the tool to find the best matching version and checks for unversioned docs.\n * @returns A descriptive string indicating the best match and unversioned status, or an error message.\n */\n async execute(options: FindVersionToolOptions): Promise<string> {\n const { library, targetVersion } = options;\n const targetVersionString = targetVersion ? `@${targetVersion}` : \"\";\n\n try {\n const { bestMatch, hasUnversioned } = await this.docService.findBestVersion(\n library,\n targetVersion,\n );\n\n let message = \"\";\n if (bestMatch) {\n message = `Best match: ${bestMatch}.`;\n if (hasUnversioned) {\n message += \" Unversioned docs also available.\";\n }\n } else if (hasUnversioned) {\n message = `No matching version found for ${library}${targetVersionString}, but unversioned docs exist.`;\n } else {\n // This case should ideally be caught by VersionNotFoundError below,\n // but added for completeness.\n message = `No matching version or unversioned documents found for ${library}${targetVersionString}.`;\n }\n return message;\n } catch (error) {\n if (error instanceof VersionNotFoundError) {\n // This error is thrown when no semver versions AND no unversioned docs exist.\n logger.info(`ℹ️ Version not found: ${error.message}`);\n return `No matching version or unversioned documents found for ${library}${targetVersionString}. Available: ${\n error.availableVersions.length > 0\n ? error.availableVersions.map((v) => v.version).join(\", \")\n : \"None\"\n }.`;\n }\n // Re-throw unexpected errors\n logger.error(\n `❌ Error finding version for ${library}${targetVersionString}: ${error instanceof Error ? error.message : error}`,\n );\n throw error;\n }\n }\n}\n","import type { PipelineManager } from \"../pipeline/PipelineManager\";\nimport type { PipelineJob, PipelineJobStatus } from \"../pipeline/types\";\n\n/**\n * Input parameters for the GetJobInfoTool.\n */\nexport interface GetJobInfoInput {\n /** The ID of the job to retrieve info for. */\n jobId: string;\n}\n\n/**\n * Simplified information about a pipeline job for external use.\n */\nexport interface JobInfo {\n id: string;\n library: string;\n version: string;\n status: PipelineJobStatus;\n createdAt: string;\n startedAt: string | null;\n finishedAt: string | null;\n error: string | null;\n}\n\n/**\n * Response structure for the GetJobInfoTool.\n */\nexport interface GetJobInfoToolResponse {\n job: JobInfo | null;\n}\n\n/**\n * Tool for retrieving simplified information about a specific pipeline job.\n */\nexport class GetJobInfoTool {\n private manager: PipelineManager;\n\n /**\n * Creates an instance of GetJobInfoTool.\n * @param manager The PipelineManager instance.\n */\n constructor(manager: PipelineManager) {\n this.manager = manager;\n }\n\n /**\n * Executes the tool to retrieve simplified info for a specific job.\n * @param input - The input parameters, containing the jobId.\n * @returns A promise that resolves with the simplified job info or null if not found.\n */\n async execute(input: GetJobInfoInput): Promise<GetJobInfoToolResponse> {\n const job = await this.manager.getJob(input.jobId);\n\n if (!job) {\n // Return null in the result if job not found\n return { job: null };\n }\n\n // Transform the job into a simplified object\n const jobInfo: JobInfo = {\n id: job.id,\n library: job.library,\n version: job.version,\n status: job.status,\n createdAt: job.createdAt.toISOString(),\n startedAt: job.startedAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n error: job.error?.message ?? null,\n };\n\n return { job: jobInfo };\n }\n}\n","import type { PipelineManager } from \"../pipeline/PipelineManager\";\nimport type { PipelineJob, PipelineJobStatus } from \"../pipeline/types\";\nimport type { JobInfo } from \"./GetJobInfoTool\"; // Import JobInfo\n\n/**\n * Input parameters for the ListJobsTool.\n */\nexport interface ListJobsInput {\n /** Optional status to filter jobs by. */\n status?: PipelineJobStatus;\n}\n\n/**\n * Response structure for the ListJobsTool.\n */\nexport interface ListJobsToolResponse {\n jobs: JobInfo[];\n}\n\n/**\n * Tool for listing pipeline jobs managed by the PipelineManager.\n * Allows filtering jobs by their status.\n */\nexport class ListJobsTool {\n private manager: PipelineManager; // Change property name and type\n\n /**\n * Creates an instance of ListJobsTool.\n * @param manager The PipelineManager instance.\n */\n constructor(manager: PipelineManager) {\n // Change constructor parameter\n this.manager = manager;\n }\n\n /**\n * Executes the tool to retrieve a list of pipeline jobs.\n * @param input - The input parameters, optionally including a status filter.\n * @returns A promise that resolves with the list of simplified job objects.\n * @throws {PipelineStateError} If the pipeline manager is somehow unavailable.\n */\n async execute(input: ListJobsInput): Promise<ListJobsToolResponse> {\n const jobs = await this.manager.getJobs(input.status);\n\n // Transform jobs into simplified objects\n const simplifiedJobs: JobInfo[] = jobs.map(\n (job: PipelineJob): JobInfo => ({\n id: job.id,\n library: job.library,\n version: job.version,\n status: job.status,\n createdAt: job.createdAt.toISOString(),\n startedAt: job.startedAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n error: job.error?.message ?? null,\n }),\n );\n\n return { jobs: simplifiedJobs };\n }\n}\n","import type { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport type { LibraryVersionDetails } from \"../store/types\";\n\n// Define the structure for the tool's output, using the detailed version info\nexport interface LibraryInfo {\n name: string;\n versions: LibraryVersionDetails[]; // Use the detailed interface\n}\n\nexport interface ListLibrariesResult {\n libraries: LibraryInfo[];\n}\n\n/**\n * Tool for listing all available libraries and their indexed versions in the store.\n */\nexport class ListLibrariesTool {\n private docService: DocumentManagementService;\n\n constructor(docService: DocumentManagementService) {\n this.docService = docService;\n }\n\n async execute(options?: Record<string, never>): Promise<ListLibrariesResult> {\n // docService.listLibraries() now returns the detailed structure directly\n const rawLibraries = await this.docService.listLibraries();\n\n // The structure returned by listLibraries already matches LibraryInfo[]\n // No complex mapping is needed here anymore, just ensure the names match\n const libraries: LibraryInfo[] = rawLibraries.map(({ library, versions }) => ({\n name: library,\n versions: versions, // Directly assign the detailed versions array\n }));\n\n return { libraries };\n }\n}\n","import type { PipelineManager } from \"../pipeline/PipelineManager\";\nimport { PipelineJobStatus } from \"../pipeline/types\";\nimport type { DocumentManagementService } from \"../store\";\nimport { logger } from \"../utils/logger\";\nimport { ToolError } from \"./errors\";\n\n/**\n * Represents the arguments for the remove_docs tool.\n * The MCP server should validate the input against RemoveToolInputSchema before calling execute.\n */\nexport interface RemoveToolArgs {\n library: string;\n version?: string;\n}\n\n/**\n * Tool to remove indexed documentation for a specific library version.\n * This class provides the core logic, intended to be called by the McpServer.\n */\nexport class RemoveTool {\n constructor(\n private readonly documentManagementService: DocumentManagementService,\n private readonly pipelineManager?: PipelineManager, // Optional for backward compatibility\n ) {}\n\n /**\n * Executes the tool to remove the specified library version documents.\n * Aborts any QUEUED/RUNNING job for the same library+version before deleting.\n */\n async execute(args: RemoveToolArgs): Promise<{ message: string }> {\n const { library, version } = args;\n\n logger.info(\n `🗑️ Removing library: ${library}${version ? `, version: ${version}` : \" (unversioned)\"}`,\n );\n\n try {\n // Abort any QUEUED or RUNNING job for this library+version\n if (this.pipelineManager) {\n const jobs = this.pipelineManager.findJobsByLibraryVersion(\n library,\n (version ?? \"\").toLowerCase(),\n [PipelineJobStatus.QUEUED, PipelineJobStatus.RUNNING],\n );\n for (const job of jobs) {\n logger.info(\n `🚫 Aborting job for ${library}@${version ?? \"\"} before deletion: ${job.id}`,\n );\n await this.pipelineManager.cancelJob(job.id);\n // Wait for job to finish cancelling if running\n await this.pipelineManager.waitForJobCompletion(job.id);\n }\n }\n // Core logic: Call the document management service\n await this.documentManagementService.removeAllDocuments(library, version);\n\n const message = `Successfully removed documents for ${library}${version ? `@${version}` : \" (unversioned)\"}.`;\n logger.info(`✅ ${message}`);\n // Return a simple success object, the McpServer will format the final response\n return { message };\n } catch (error) {\n const errorMessage = `Failed to remove documents for ${library}${version ? `@${version}` : \" (unversioned)\"}: ${error instanceof Error ? error.message : String(error)}`;\n logger.error(`❌ Error removing library: ${errorMessage}`);\n // Re-throw the error for the McpServer to handle and format\n throw new ToolError(errorMessage, this.constructor.name);\n }\n }\n}\n","import * as semver from \"semver\";\nimport type { PipelineManager } from \"../pipeline/PipelineManager\";\nimport { ScrapeMode } from \"../scraper/types\";\nimport type { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport type { ProgressResponse } from \"../types\";\nimport {\n DEFAULT_MAX_CONCURRENCY,\n DEFAULT_MAX_DEPTH,\n DEFAULT_MAX_PAGES,\n} from \"../utils/config\";\nimport { logger } from \"../utils/logger\";\n\nexport interface ScrapeToolOptions {\n library: string;\n version?: string | null; // Make version optional\n url: string;\n options?: {\n maxPages?: number;\n maxDepth?: number;\n /**\n * Defines the allowed crawling boundary relative to the starting URL\n * - 'subpages': Only crawl URLs on the same hostname and within the same starting path (default)\n * - 'hostname': Crawl any URL on the same hostname, regardless of path\n * - 'domain': Crawl any URL on the same top-level domain, including subdomains\n */\n scope?: \"subpages\" | \"hostname\" | \"domain\";\n /**\n * Controls whether HTTP redirects (3xx responses) should be followed\n * - When true: Redirects are followed automatically (default)\n * - When false: A RedirectError is thrown when a 3xx response is received\n */\n followRedirects?: boolean;\n maxConcurrency?: number; // Note: Concurrency is now set when PipelineManager is created\n ignoreErrors?: boolean;\n /**\n * Determines the HTML processing strategy.\n * - 'fetch': Use a simple DOM parser (faster, less JS support).\n * - 'playwright': Use a headless browser (slower, full JS support).\n * - 'auto': Automatically select the best strategy (currently defaults to 'playwright').\n * @default ScrapeMode.Auto\n */\n scrapeMode?: ScrapeMode;\n /**\n * Patterns for including URLs during scraping. If not set, all are included by default.\n * Regex patterns must be wrapped in slashes, e.g. /pattern/.\n */\n includePatterns?: string[];\n /**\n * Patterns for excluding URLs during scraping. Exclude takes precedence over include.\n * Regex patterns must be wrapped in slashes, e.g. /pattern/.\n */\n excludePatterns?: string[];\n /**\n * Custom HTTP headers to send with each request (e.g., for authentication).\n * Keys are header names, values are header values.\n */\n headers?: Record<string, string>;\n };\n /** If false, returns jobId immediately without waiting. Defaults to true. */\n waitForCompletion?: boolean;\n}\n\nexport interface ScrapeResult {\n /** Indicates the number of pages scraped if waitForCompletion was true and the job succeeded. May be 0 or inaccurate if job failed or waitForCompletion was false. */\n pagesScraped: number;\n}\n\n/** Return type for ScrapeTool.execute */\nexport type ScrapeExecuteResult = ScrapeResult | { jobId: string };\n\n/**\n * Tool for enqueuing documentation scraping jobs via the PipelineManager.\n */\nexport class ScrapeTool {\n private docService: DocumentManagementService;\n private manager: PipelineManager; // Add manager property\n\n constructor(docService: DocumentManagementService, manager: PipelineManager) {\n // Add manager to constructor\n this.docService = docService;\n this.manager = manager; // Store manager instance\n }\n\n async execute(options: ScrapeToolOptions): Promise<ScrapeExecuteResult> {\n const {\n library,\n version,\n url,\n options: scraperOptions,\n waitForCompletion = true,\n } = options;\n\n // Store initialization and manager start should happen externally\n\n let internalVersion: string;\n const partialVersionRegex = /^\\d+(\\.\\d+)?$/; // Matches '1' or '1.2'\n\n if (version === null || version === undefined) {\n internalVersion = \"\";\n } else {\n const validFullVersion = semver.valid(version);\n if (validFullVersion) {\n internalVersion = validFullVersion;\n } else if (partialVersionRegex.test(version)) {\n const coercedVersion = semver.coerce(version);\n if (coercedVersion) {\n internalVersion = coercedVersion.version;\n } else {\n throw new Error(\n `Invalid version format for scraping: '${version}'. Use 'X.Y.Z', 'X.Y.Z-prerelease', 'X.Y', 'X', or omit.`,\n );\n }\n } else {\n throw new Error(\n `Invalid version format for scraping: '${version}'. Use 'X.Y.Z', 'X.Y.Z-prerelease', 'X.Y', 'X', or omit.`,\n );\n }\n }\n\n internalVersion = internalVersion.toLowerCase();\n\n // Remove any existing documents for this library/version\n await this.docService.removeAllDocuments(library, internalVersion);\n logger.info(\n `💾 Cleared store for ${library}@${internalVersion || \"[no version]\"} before scraping.`,\n );\n\n // Use the injected manager instance\n const manager = this.manager;\n\n // Remove internal progress tracking and callbacks\n // let pagesScraped = 0;\n // let lastReportedPages = 0;\n // const reportProgress = ...\n // manager.setCallbacks(...)\n\n // Enqueue the job using the injected manager\n const jobId = await manager.enqueueJob(library, internalVersion, {\n url: url,\n library: library,\n version: internalVersion,\n scope: scraperOptions?.scope ?? \"subpages\",\n followRedirects: scraperOptions?.followRedirects ?? true,\n maxPages: scraperOptions?.maxPages ?? DEFAULT_MAX_PAGES,\n maxDepth: scraperOptions?.maxDepth ?? DEFAULT_MAX_DEPTH,\n maxConcurrency: scraperOptions?.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY,\n ignoreErrors: scraperOptions?.ignoreErrors ?? true,\n scrapeMode: scraperOptions?.scrapeMode ?? ScrapeMode.Auto, // Pass scrapeMode enum\n includePatterns: scraperOptions?.includePatterns,\n excludePatterns: scraperOptions?.excludePatterns,\n headers: scraperOptions?.headers, // <-- propagate headers\n });\n\n // Conditionally wait for completion\n if (waitForCompletion) {\n try {\n await manager.waitForJobCompletion(jobId);\n // Fetch final job state to get status and potentially final page count\n const finalJob = await manager.getJob(jobId);\n const finalPagesScraped = finalJob?.progress?.pagesScraped ?? 0; // Get count from final job state\n logger.debug(\n `Job ${jobId} finished with status ${finalJob?.status}. Pages scraped: ${finalPagesScraped}`,\n );\n return {\n pagesScraped: finalPagesScraped,\n };\n } catch (error) {\n logger.error(`❌ Job ${jobId} failed or was cancelled: ${error}`);\n throw error; // Re-throw so the caller knows it failed\n }\n // No finally block needed to stop manager, as it's managed externally\n }\n\n // If not waiting, return the job ID immediately\n return { jobId };\n }\n}\n","import type { DocumentManagementService } from \"../store\";\nimport type { LibraryVersionDetails, StoreSearchResult } from \"../store/types\"; // Import LibraryVersionDetails\nimport { logger } from \"../utils/logger\";\nimport { VersionNotFoundError } from \"./errors\";\n\nexport interface SearchToolOptions {\n library: string;\n version?: string;\n query: string;\n limit?: number;\n exactMatch?: boolean;\n}\n\nexport interface SearchToolResultError {\n message: string;\n availableVersions?: LibraryVersionDetails[]; // Use LibraryVersionDetails\n suggestions?: string[]; // Specific to LibraryNotFoundError\n}\n\nexport interface SearchToolResult {\n results: StoreSearchResult[];\n}\n\n/**\n * Tool for searching indexed documentation.\n * Supports exact version matches and version range patterns.\n * Returns available versions when requested version is not found.\n */\nexport class SearchTool {\n private docService: DocumentManagementService;\n\n constructor(docService: DocumentManagementService) {\n this.docService = docService;\n }\n\n async execute(options: SearchToolOptions): Promise<SearchToolResult> {\n const { library, version, query, limit = 5, exactMatch = false } = options;\n\n // When exactMatch is true, version must be specified and not 'latest'\n if (exactMatch && (!version || version === \"latest\")) {\n // Get available *detailed* versions for error message\n await this.docService.validateLibraryExists(library);\n // Fetch detailed versions using listLibraries and find the specific library\n const allLibraries = await this.docService.listLibraries();\n const libraryInfo = allLibraries.find((lib) => lib.library === library);\n const detailedVersions = libraryInfo ? libraryInfo.versions : [];\n throw new VersionNotFoundError(\n library,\n \"latest\", // Or perhaps the original 'version' if it wasn't 'latest'? Check logic.\n detailedVersions,\n );\n }\n\n // Default to 'latest' only when exactMatch is false\n const resolvedVersion = version || \"latest\";\n\n logger.info(\n `🔍 Searching ${library}@${resolvedVersion} for: ${query}${exactMatch ? \" (exact match)\" : \"\"}`,\n );\n\n try {\n // 1. Validate library exists first\n await this.docService.validateLibraryExists(library);\n\n // 2. Proceed with version finding and searching\n let versionToSearch: string | null | undefined = resolvedVersion;\n\n if (!exactMatch) {\n // If not exact match, find the best version (which might be null)\n const versionResult = await this.docService.findBestVersion(library, version);\n // Use the bestMatch from the result, which could be null\n versionToSearch = versionResult.bestMatch;\n\n // If findBestVersion returned null (no matching semver) AND unversioned docs exist,\n // should we search unversioned? The current logic passes null to searchStore,\n // which gets normalized to \"\" (unversioned). This seems reasonable.\n // If findBestVersion threw VersionNotFoundError, it's caught below.\n }\n // If exactMatch is true, versionToSearch remains the originally provided version.\n\n // Note: versionToSearch can be string | null | undefined here.\n // searchStore handles null/undefined by normalizing to \"\".\n const results = await this.docService.searchStore(\n library,\n versionToSearch,\n query,\n limit,\n );\n logger.info(`✅ Found ${results.length} matching results`);\n\n return { results };\n } catch (error) {\n logger.error(\n `❌ Search failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n throw error;\n }\n }\n}\n","import type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\n\n/**\n * Creates a success response object in the format expected by the MCP server.\n * @param text The text content of the response.\n * @returns The response object.\n */\nexport function createResponse(text: string): CallToolResult {\n return {\n content: [\n {\n type: \"text\",\n text,\n },\n ],\n isError: false,\n };\n}\n\n/**\n * Creates an error response object in the format expected by the MCP server.\n * @param text The error message.\n * @returns The response object.\n */\nexport function createError(text: string): CallToolResult {\n return {\n content: [\n {\n type: \"text\",\n text,\n },\n ],\n isError: true,\n };\n}\n","import { McpServer, ResourceTemplate } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { PipelineJobStatus } from \"../pipeline/types\";\nimport { type JobInfo, LibraryNotFoundError, VersionNotFoundError } from \"../tools\";\nimport { DEFAULT_MAX_DEPTH, DEFAULT_MAX_PAGES } from \"../utils/config\";\nimport { logger } from \"../utils/logger\";\nimport type { McpServerTools } from \"./tools\";\nimport { createError, createResponse } from \"./utils\";\n\n/**\n * Creates and configures an instance of the MCP server with registered tools, prompts, and resources.\n * @param tools The shared tool instances to use for server operations.\n * @returns A configured McpServer instance.\n */\nexport function createMcpServerInstance(tools: McpServerTools): McpServer {\n const server = new McpServer(\n {\n name: \"docs-mcp-server\",\n version: \"0.1.0\",\n },\n {\n capabilities: {\n tools: {},\n prompts: {},\n resources: {},\n },\n },\n );\n\n // --- Tool Definitions ---\n\n // Scrape docs tool\n server.tool(\n \"scrape_docs\",\n \"Scrape and index documentation from a URL for a library. Use this tool to index a new library or a new version.\",\n {\n url: z.string().url().describe(\"Documentation root URL to scrape.\"),\n library: z.string().describe(\"Library name.\"),\n version: z.string().optional().describe(\"Library version (optional).\"),\n maxPages: z\n .number()\n .optional()\n .default(DEFAULT_MAX_PAGES)\n .describe(`Maximum number of pages to scrape (default: ${DEFAULT_MAX_PAGES}).`),\n maxDepth: z\n .number()\n .optional()\n .default(DEFAULT_MAX_DEPTH)\n .describe(`Maximum navigation depth (default: ${DEFAULT_MAX_DEPTH}).`),\n scope: z\n .enum([\"subpages\", \"hostname\", \"domain\"])\n .optional()\n .default(\"subpages\")\n .describe(\"Crawling boundary: 'subpages', 'hostname', or 'domain'.\"),\n followRedirects: z\n .boolean()\n .optional()\n .default(true)\n .describe(\"Follow HTTP redirects (3xx responses).\"),\n },\n {\n title: \"Scrape New Library Documentation\",\n destructiveHint: true, // replaces existing docs\n openWorldHint: true, // requires internet access\n },\n async ({ url, library, version, maxPages, maxDepth, scope, followRedirects }) => {\n try {\n // Execute scrape tool without waiting and without progress callback\n const result = await tools.scrape.execute({\n url,\n library,\n version,\n waitForCompletion: false, // Don't wait for completion\n // onProgress: undefined, // Explicitly undefined or omitted\n options: {\n maxPages,\n maxDepth,\n scope,\n followRedirects,\n },\n });\n\n // Check the type of result\n if (\"jobId\" in result) {\n // If we got a jobId back, report that\n return createResponse(`🚀 Scraping job started with ID: ${result.jobId}.`);\n }\n // This case shouldn't happen if waitForCompletion is false, but handle defensively\n return createResponse(\n `Scraping finished immediately (unexpectedly) with ${result.pagesScraped} pages.`,\n );\n } catch (error) {\n // Handle errors during job *enqueueing* or initial setup\n return createError(\n `Failed to scrape documentation: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // Search docs tool\n server.tool(\n \"search_docs\",\n \"Search up-to-date documentation for a library or package. Examples:\\n\\n\" +\n '- {library: \"react\", query: \"hooks lifecycle\"} -> matches latest version of React\\n' +\n '- {library: \"react\", version: \"18.0.0\", query: \"hooks lifecycle\"} -> matches React 18.0.0 or earlier\\n' +\n '- {library: \"typescript\", version: \"5.x\", query: \"ReturnType example\"} -> any TypeScript 5.x.x version\\n' +\n '- {library: \"typescript\", version: \"5.2.x\", query: \"ReturnType example\"} -> any TypeScript 5.2.x version',\n {\n library: z.string().describe(\"Library name.\"),\n version: z\n .string()\n .optional()\n .describe(\"Library version (exact or X-Range, optional).\"),\n query: z.string().describe(\"Documentation search query.\"),\n limit: z.number().optional().default(5).describe(\"Maximum number of results.\"),\n },\n {\n title: \"Search Library Documentation\",\n readOnlyHint: true,\n destructiveHint: false,\n },\n async ({ library, version, query, limit }) => {\n try {\n const result = await tools.search.execute({\n library,\n version,\n query,\n limit,\n exactMatch: false, // Always false for MCP interface\n });\n\n const formattedResults = result.results.map(\n (r: { url: string; content: string }, i: number) => `\n------------------------------------------------------------\nResult ${i + 1}: ${r.url}\n\n${r.content}\\n`,\n );\n\n if (formattedResults.length === 0) {\n return createResponse(\n `No results found for '${query}' in ${library}. Try to use a different or more general query.`,\n );\n }\n return createResponse(formattedResults.join(\"\"));\n } catch (error) {\n if (error instanceof LibraryNotFoundError) {\n return createResponse(\n [\n `Library \"${library}\" not found.`,\n error.suggestions?.length\n ? `Did you mean: ${error.suggestions?.join(\", \")}?`\n : undefined,\n ].join(\" \"),\n );\n }\n if (error instanceof VersionNotFoundError) {\n const indexedVersions = error.availableVersions.map((v) => v.version);\n return createResponse(\n [\n `Version \"${version}\" not found.`,\n indexedVersions.length > 0\n ? `Available indexed versions for ${library}: ${indexedVersions.join(\", \")}`\n : undefined,\n ].join(\" \"),\n );\n }\n return createError(\n `Failed to search documentation: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // List libraries tool\n server.tool(\n \"list_libraries\",\n \"List all indexed libraries.\",\n {\n // no params\n },\n {\n title: \"List Libraries\",\n readOnlyHint: true,\n destructiveHint: false,\n },\n async () => {\n try {\n const result = await tools.listLibraries.execute();\n if (result.libraries.length === 0) {\n return createResponse(\"No libraries indexed yet.\");\n }\n\n return createResponse(\n `Indexed libraries:\\n\\n${result.libraries.map((lib: { name: string }) => `- ${lib.name}`).join(\"\\n\")}`,\n );\n } catch (error) {\n return createError(\n `Failed to list libraries: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // Find version tool\n server.tool(\n \"find_version\",\n \"Find the best matching version for a library. Use to identify available or closest versions.\",\n {\n library: z.string().describe(\"Library name.\"),\n targetVersion: z\n .string()\n .optional()\n .describe(\"Version pattern to match (exact or X-Range, optional).\"),\n },\n {\n title: \"Find Library Version\",\n readOnlyHint: true,\n destructiveHint: false,\n },\n async ({ library, targetVersion }) => {\n try {\n const message = await tools.findVersion.execute({\n library,\n targetVersion,\n });\n\n if (!message) {\n return createError(\"No matching version found\");\n }\n\n return createResponse(message);\n } catch (error) {\n return createError(\n `Failed to find version: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // List jobs tool\n server.tool(\n \"list_jobs\",\n \"List all indexing jobs. Optionally filter by status.\",\n {\n status: z\n .nativeEnum(PipelineJobStatus)\n .optional()\n .describe(\"Filter jobs by status (optional).\"),\n },\n {\n title: \"List Indexing Jobs\",\n readOnlyHint: true,\n destructiveHint: false,\n },\n async ({ status }) => {\n try {\n const result = await tools.listJobs.execute({ status });\n // Format the simplified job list for display\n const formattedJobs = result.jobs\n .map(\n (job: JobInfo) =>\n `- ID: ${job.id}\\n Status: ${job.status}\\n Library: ${job.library}\\n Version: ${job.version}\\n Created: ${job.createdAt}${job.startedAt ? `\\n Started: ${job.startedAt}` : \"\"}${job.finishedAt ? `\\n Finished: ${job.finishedAt}` : \"\"}${job.error ? `\\n Error: ${job.error}` : \"\"}`,\n )\n .join(\"\\n\\n\");\n return createResponse(\n result.jobs.length > 0 ? `Current Jobs:\\n\\n${formattedJobs}` : \"No jobs found.\",\n );\n } catch (error) {\n return createError(\n `Failed to list jobs: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // Get job info tool\n server.tool(\n \"get_job_info\",\n \"Get details for a specific indexing job. Use the 'list_jobs' tool to find the job ID.\",\n {\n jobId: z.string().uuid().describe(\"Job ID to query.\"),\n },\n {\n title: \"Get Indexing Job Info\",\n readOnlyHint: true,\n destructiveHint: false,\n },\n async ({ jobId }) => {\n try {\n const result = await tools.getJobInfo.execute({ jobId });\n if (!result.job) {\n return createError(`Job with ID ${jobId} not found.`);\n }\n const job = result.job;\n const formattedJob = `- ID: ${job.id}\\n Status: ${job.status}\\n Library: ${job.library}@${job.version}\\n Created: ${job.createdAt}${job.startedAt ? `\\n Started: ${job.startedAt}` : \"\"}${job.finishedAt ? `\\n Finished: ${job.finishedAt}` : \"\"}${job.error ? `\\n Error: ${job.error}` : \"\"}`;\n return createResponse(`Job Info:\\n\\n${formattedJob}`);\n } catch (error) {\n return createError(\n `Failed to get job info for ${jobId}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // Cancel job tool\n server.tool(\n \"cancel_job\",\n \"Cancel a queued or running indexing job. Use the 'list_jobs' tool to find the job ID.\",\n {\n jobId: z.string().uuid().describe(\"Job ID to cancel.\"),\n },\n {\n title: \"Cancel Indexing Job\",\n destructiveHint: true,\n },\n async ({ jobId }) => {\n try {\n const result = await tools.cancelJob.execute({ jobId });\n // Use the message and success status from the tool's result\n if (result.success) {\n return createResponse(result.message);\n }\n // If not successful according to the tool, treat it as an error in MCP\n return createError(result.message);\n } catch (error) {\n // Catch any unexpected errors during the tool execution itself\n return createError(\n `Failed to cancel job ${jobId}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // Remove docs tool\n server.tool(\n \"remove_docs\",\n \"Remove indexed documentation for a library version. Use only if explicitly instructed.\",\n {\n library: z.string().describe(\"Library name.\"),\n version: z\n .string()\n .optional()\n .describe(\"Library version (optional, removes unversioned if omitted).\"),\n },\n {\n title: \"Remove Library Documentation\",\n destructiveHint: true,\n },\n async ({ library, version }) => {\n try {\n // Execute the remove tool logic\n const result = await tools.remove.execute({ library, version });\n // Use the message from the tool's successful execution\n return createResponse(result.message);\n } catch (error) {\n // Catch errors thrown by the RemoveTool's execute method\n return createError(\n `Failed to remove documents: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // Fetch URL tool\n server.tool(\n \"fetch_url\",\n \"Fetch a single URL and convert its content to Markdown. Use this tool to read the content of any web page.\",\n {\n url: z.string().url().describe(\"URL to fetch and convert to Markdown.\"),\n followRedirects: z\n .boolean()\n .optional()\n .default(true)\n .describe(\"Follow HTTP redirects (3xx responses).\"),\n },\n {\n title: \"Fetch URL\",\n readOnlyHint: true,\n destructiveHint: false,\n openWorldHint: true, // requires internet access\n },\n async ({ url, followRedirects }) => {\n try {\n const result = await tools.fetchUrl.execute({ url, followRedirects });\n return createResponse(result);\n } catch (error) {\n return createError(\n `Failed to fetch URL: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n },\n );\n\n server.resource(\n \"libraries\",\n \"docs://libraries\",\n {\n description: \"List all indexed libraries\",\n },\n async (uri: URL) => {\n const result = await tools.listLibraries.execute();\n\n return {\n contents: result.libraries.map((lib: { name: string }) => ({\n uri: new URL(lib.name, uri).href,\n text: lib.name,\n })),\n };\n },\n );\n\n server.resource(\n \"versions\",\n new ResourceTemplate(\"docs://libraries/{library}/versions\", {\n list: undefined,\n }),\n {\n description: \"List all indexed versions for a library\",\n },\n async (uri: URL, { library }) => {\n const result = await tools.listLibraries.execute();\n\n const lib = result.libraries.find((l: { name: string }) => l.name === library);\n if (!lib) {\n return { contents: [] };\n }\n\n return {\n contents: lib.versions.map((v: { version: string }) => ({\n uri: new URL(v.version, uri).href,\n text: v.version,\n })),\n };\n },\n );\n\n /**\n * Resource handler for listing pipeline jobs.\n * Supports filtering by status via a query parameter (e.g., ?status=running).\n * URI: docs://jobs[?status=<status>]\n */\n server.resource(\n \"jobs\",\n \"docs://jobs\",\n {\n description: \"List indexing jobs, optionally filtering by status.\",\n mimeType: \"application/json\",\n },\n async (uri: URL) => {\n const statusParam = uri.searchParams.get(\"status\");\n let statusFilter: PipelineJobStatus | undefined;\n\n // Validate status parameter if provided\n if (statusParam) {\n const validation = z.nativeEnum(PipelineJobStatus).safeParse(statusParam);\n if (validation.success) {\n statusFilter = validation.data;\n } else {\n // Handle invalid status - perhaps return an error or ignore?\n // For simplicity, let's ignore invalid status for now and return all jobs.\n // Alternatively, could throw an McpError or return specific error content.\n logger.warn(`⚠️ Invalid status parameter received: ${statusParam}`);\n }\n }\n\n // Fetch simplified jobs using the ListJobsTool\n const result = await tools.listJobs.execute({ status: statusFilter });\n\n return {\n contents: result.jobs.map((job) => ({\n uri: new URL(job.id, uri).href,\n mimeType: \"application/json\",\n text: JSON.stringify({\n id: job.id,\n library: job.library,\n version: job.version,\n status: job.status,\n error: job.error || undefined,\n }),\n })),\n };\n },\n );\n\n /**\n * Resource handler for retrieving a specific pipeline job by its ID.\n * URI Template: docs://jobs/{jobId}\n */\n server.resource(\n \"job\", // A distinct name for this specific resource type\n new ResourceTemplate(\"docs://jobs/{jobId}\", { list: undefined }),\n {\n description: \"Get details for a specific indexing job by ID.\",\n mimeType: \"application/json\",\n },\n async (uri: URL, { jobId }) => {\n // Validate jobId format if necessary (basic check)\n if (typeof jobId !== \"string\" || jobId.length === 0) {\n // Handle invalid jobId format - return empty or error\n logger.warn(`⚠️ Invalid jobId received in URI: ${jobId}`);\n return { contents: [] }; // Return empty content for invalid ID format\n }\n\n // Fetch the simplified job info using GetJobInfoTool\n const result = await tools.getJobInfo.execute({ jobId });\n\n // result.job is either the simplified job object or null\n if (!result.job) {\n // Job not found, return empty content\n return { contents: [] };\n }\n\n // Job found, return its simplified details as JSON\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({\n id: result.job.id,\n library: result.job.library,\n version: result.job.version,\n status: result.job.status,\n error: result.job.error || undefined,\n }),\n },\n ],\n };\n },\n );\n\n return server;\n}\n","import * as http from \"node:http\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { SSEServerTransport } from \"@modelcontextprotocol/sdk/server/sse.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { LogLevel, logger, setLogLevel } from \"../utils/logger\";\nimport { createMcpServerInstance } from \"./mcpServer\";\nimport type { McpServerTools } from \"./tools\";\n\n/**\n * Starts the MCP server using the Streamable HTTP and SSE transports.\n * @param tools The shared tool instances.\n * @param port The port to listen on.\n * @returns The created McpServer instance.\n */\nexport async function startHttpServer(\n tools: McpServerTools,\n port: number,\n): Promise<McpServer> {\n setLogLevel(LogLevel.INFO);\n\n const server = createMcpServerInstance(tools);\n const sseTransports: Record<string, SSEServerTransport> = {};\n\n const httpServer = http.createServer(async (req, res) => {\n try {\n const url = new URL(req.url || \"/\", `http://${req.headers.host}`);\n\n if (req.method === \"GET\" && url.pathname === \"/sse\") {\n // Handle SSE connection\n const transport = new SSEServerTransport(\"/messages\", res);\n sseTransports[transport.sessionId] = transport;\n\n res.on(\"close\", () => {\n delete sseTransports[transport.sessionId];\n transport.close();\n });\n\n await server.connect(transport);\n } else if (req.method === \"POST\" && url.pathname === \"/messages\") {\n // Handle SSE messages\n const sessionId = url.searchParams.get(\"sessionId\");\n const transport = sessionId ? sseTransports[sessionId] : undefined;\n\n if (transport) {\n let body = \"\";\n for await (const chunk of req) {\n body += chunk;\n }\n const parsedBody = JSON.parse(body);\n await transport.handlePostMessage(req, res, parsedBody);\n } else {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"No transport found for sessionId\" }));\n }\n } else if (url.pathname === \"/mcp\") {\n // Handle Streamable HTTP (stateless)\n let body = \"\";\n for await (const chunk of req) {\n body += chunk;\n }\n const parsedBody = JSON.parse(body);\n\n // In stateless mode, create a new instance of server and transport for each request\n const requestServer = createMcpServerInstance(tools);\n const requestTransport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n });\n\n res.on(\"close\", () => {\n logger.debug(\"Streamable HTTP request closed\");\n requestTransport.close();\n requestServer.close(); // Close the per-request server instance\n });\n\n await requestServer.connect(requestTransport);\n await requestTransport.handleRequest(req, res, parsedBody);\n } else {\n // Handle 404 Not Found\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: `Endpoint ${url.pathname} not found.`,\n }),\n );\n }\n } catch (error) {\n logger.error(`❌ Error handling HTTP request: ${error}`);\n // Send an error response\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n });\n\n httpServer.listen(port, () => {\n logger.info(`🤖 MCP server listening at http://127.0.0.1:${port}`);\n });\n\n const originalClose = httpServer.close.bind(httpServer);\n server.close = async () => {\n logger.debug(\"Closing MCP server instance...\");\n httpServer.close();\n for (const transport of Object.values(sseTransports)) {\n await transport.close();\n }\n logger.debug(\"MCP server instance closed.\");\n await originalClose();\n };\n\n // Return the server instance\n return server;\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { LogLevel, logger, setLogLevel } from \"../utils/logger\";\nimport { createMcpServerInstance } from \"./mcpServer\";\nimport type { McpServerTools } from \"./tools\";\n\n/**\n * Starts the MCP server using the Stdio transport.\n * @param tools The shared tool instances.\n * @returns The created McpServer instance.\n */\nexport async function startStdioServer(tools: McpServerTools): Promise<McpServer> {\n setLogLevel(LogLevel.ERROR);\n\n // Create a server instance using the factory and shared tools\n const server = createMcpServerInstance(tools);\n\n // Start server with Stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n logger.info(\"🤖 MCP server listening on stdio\");\n\n // Return the server instance\n return server;\n}\n","export class PipelineError extends Error {\n constructor(\n message: string,\n public readonly cause?: Error,\n ) {\n super(message);\n this.name = this.constructor.name;\n if (cause?.stack) {\n this.stack = `${this.stack}\\nCaused by: ${cause.stack}`;\n }\n }\n}\n\nexport class DocumentProcessingError extends PipelineError {\n constructor(\n message: string,\n public readonly documentId: string,\n cause?: Error,\n ) {\n super(`Failed to process document ${documentId}: ${message}`, cause);\n }\n}\n\nexport class PipelineStateError extends PipelineError {}\n\n/**\n * Error indicating that an operation was cancelled.\n */\nexport class CancellationError extends PipelineError {\n constructor(message = \"Operation cancelled\") {\n super(message);\n }\n}\n","import { HeaderGenerator, type HeaderGeneratorOptions } from \"header-generator\";\n\n/**\n * Generates realistic browser-like HTTP headers to help avoid bot detection.\n * Uses the `header-generator` library for header generation.\n */\nexport class FingerprintGenerator {\n private headerGenerator: HeaderGenerator;\n\n /**\n * Creates an instance of FingerprintGenerator.\n * @param options Optional configuration for the header generator.\n */\n constructor(options?: Partial<HeaderGeneratorOptions>) {\n // Default options for a broad range of realistic headers\n const defaultOptions: Partial<HeaderGeneratorOptions> = {\n browsers: [{ name: \"chrome\", minVersion: 100 }, \"firefox\", \"safari\"],\n devices: [\"desktop\", \"mobile\"],\n operatingSystems: [\"windows\", \"linux\", \"macos\", \"android\", \"ios\"],\n locales: [\"en-US\", \"en\"],\n httpVersion: \"2\",\n };\n\n this.headerGenerator = new HeaderGenerator({\n ...defaultOptions,\n ...options,\n });\n }\n\n /**\n * Generates a set of realistic HTTP headers.\n * @returns A set of realistic HTTP headers.\n */\n generateHeaders(): Record<string, string> {\n return this.headerGenerator.getHeaders();\n }\n}\n","import axios, { type AxiosError, type AxiosRequestConfig } from \"axios\";\nimport { CancellationError } from \"../../pipeline/errors\";\nimport { FETCHER_BASE_DELAY, FETCHER_MAX_RETRIES } from \"../../utils/config\";\nimport { RedirectError, ScraperError } from \"../../utils/errors\";\nimport { logger } from \"../../utils/logger\";\nimport { MimeTypeUtils } from \"../../utils/mimeTypeUtils\";\nimport { FingerprintGenerator } from \"./FingerprintGenerator\";\nimport type { ContentFetcher, FetchOptions, RawContent } from \"./types\";\n\n/**\n * Fetches content from remote sources using HTTP/HTTPS.\n */\nexport class HttpFetcher implements ContentFetcher {\n private readonly retryableStatusCodes = [\n 408, // Request Timeout\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n 525, // SSL Handshake Failed (Cloudflare specific)\n ];\n\n private fingerprintGenerator: FingerprintGenerator;\n\n constructor() {\n this.fingerprintGenerator = new FingerprintGenerator();\n }\n\n canFetch(source: string): boolean {\n return source.startsWith(\"http://\") || source.startsWith(\"https://\");\n }\n\n private async delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n async fetch(source: string, options?: FetchOptions): Promise<RawContent> {\n const maxRetries = options?.maxRetries ?? FETCHER_MAX_RETRIES;\n const baseDelay = options?.retryDelay ?? FETCHER_BASE_DELAY;\n // Default to following redirects if not specified\n const followRedirects = options?.followRedirects ?? true;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const fingerprint = this.fingerprintGenerator.generateHeaders();\n const headers = {\n ...fingerprint,\n ...options?.headers, // User-provided headers override generated ones\n };\n\n const config: AxiosRequestConfig = {\n responseType: \"arraybuffer\", // For handling both text and binary\n headers,\n timeout: options?.timeout,\n signal: options?.signal, // Pass signal to axios\n // Axios follows redirects by default, we need to explicitly disable it if needed\n maxRedirects: followRedirects ? 5 : 0,\n };\n\n const response = await axios.get(source, config);\n\n const contentTypeHeader = response.headers[\"content-type\"];\n const { mimeType, charset } = MimeTypeUtils.parseContentType(contentTypeHeader);\n const contentEncoding = response.headers[\"content-encoding\"];\n\n return {\n content: response.data,\n mimeType,\n charset,\n encoding: contentEncoding,\n source: source,\n } satisfies RawContent;\n } catch (error: unknown) {\n const axiosError = error as AxiosError;\n const status = axiosError.response?.status;\n const code = axiosError.code;\n\n // Handle abort/cancel: do not retry, throw CancellationError\n if (options?.signal?.aborted || code === \"ERR_CANCELED\") {\n // Throw with isError = false to indicate cancellation is not an error\n throw new CancellationError(\"HTTP fetch cancelled\");\n }\n\n // Handle redirect errors (status codes 301, 302, 303, 307, 308)\n if (!followRedirects && status && status >= 300 && status < 400) {\n const location = axiosError.response?.headers?.location;\n if (location) {\n throw new RedirectError(source, location, status);\n }\n }\n\n if (\n attempt < maxRetries &&\n (status === undefined || this.retryableStatusCodes.includes(status))\n ) {\n const delay = baseDelay * 2 ** attempt;\n logger.warn(\n `⚠️ Attempt ${attempt + 1}/${\n maxRetries + 1\n } failed for ${source} (Status: ${status}, Code: ${code}). Retrying in ${delay}ms...`,\n );\n await this.delay(delay);\n continue;\n }\n\n // Not a 5xx error or max retries reached\n throw new ScraperError(\n `Failed to fetch ${source} after ${\n attempt + 1\n } attempts: ${axiosError.message ?? \"Unknown error\"}`,\n true,\n error instanceof Error ? error : undefined,\n );\n }\n }\n throw new ScraperError(\n `Failed to fetch ${source} after ${maxRetries + 1} attempts`,\n true,\n );\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport * as mime from \"mime-types\";\nimport { ScraperError } from \"../../utils/errors\";\nimport { logger } from \"../../utils/logger\";\nimport type { ContentFetcher, FetchOptions, RawContent } from \"./types\";\n\n/**\n * Fetches content from local file system.\n */\nexport class FileFetcher implements ContentFetcher {\n canFetch(source: string): boolean {\n return source.startsWith(\"file://\");\n }\n\n /**\n * Fetches the content of a file given a file:// URL, decoding percent-encoded paths as needed.\n * Only HTML and Markdown files are processed.\n */\n async fetch(source: string, options?: FetchOptions): Promise<RawContent> {\n // Always decode the file path from file:// URL\n const rawPath = source.replace(\"file://\", \"\");\n const filePath = decodeURIComponent(rawPath);\n\n try {\n const content = await fs.readFile(filePath);\n const ext = path.extname(filePath).toLowerCase();\n const mimeType = mime.lookup(ext) || \"application/octet-stream\";\n return {\n content,\n mimeType,\n source,\n encoding: \"utf-8\", // Assume UTF-8 for text files\n };\n } catch (error: unknown) {\n throw new ScraperError(\n `Failed to read file ${filePath}: ${\n (error as { message?: string }).message ?? \"Unknown error\"\n }`,\n false,\n error instanceof Error ? error : undefined,\n );\n }\n }\n}\n","import type { PipelineManager } from \"../pipeline/PipelineManager\";\nimport { FileFetcher, HttpFetcher } from \"../scraper/fetcher\";\nimport type { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport {\n CancelJobTool,\n FetchUrlTool,\n FindVersionTool,\n GetJobInfoTool,\n ListJobsTool,\n ListLibrariesTool,\n RemoveTool,\n ScrapeTool,\n SearchTool,\n} from \"../tools\";\n\n/**\n * Interface for the shared tool instances.\n */\nexport interface McpServerTools {\n listLibraries: ListLibrariesTool;\n findVersion: FindVersionTool;\n scrape: ScrapeTool;\n search: SearchTool;\n listJobs: ListJobsTool;\n getJobInfo: GetJobInfoTool;\n cancelJob: CancelJobTool;\n remove: RemoveTool;\n fetchUrl: FetchUrlTool;\n}\n\n/**\n * Initializes and returns the shared tool instances.\n * This should be called after initializeServices has completed.\n * @param docService The initialized DocumentManagementService instance.\n * @param pipelineManager The initialized PipelineManager instance.\n * @returns An object containing all instantiated tool instances.\n */\nexport async function initializeTools(\n docService: DocumentManagementService,\n pipelineManager: PipelineManager,\n): Promise<McpServerTools> {\n const tools: McpServerTools = {\n listLibraries: new ListLibrariesTool(docService),\n findVersion: new FindVersionTool(docService),\n scrape: new ScrapeTool(docService, pipelineManager),\n search: new SearchTool(docService),\n listJobs: new ListJobsTool(pipelineManager),\n getJobInfo: new GetJobInfoTool(pipelineManager),\n cancelJob: new CancelJobTool(pipelineManager),\n // clearCompletedJobs: new ClearCompletedJobsTool(pipelineManager),\n remove: new RemoveTool(docService, pipelineManager),\n fetchUrl: new FetchUrlTool(new HttpFetcher(), new FileFetcher()),\n };\n\n return tools;\n}\n","import \"dotenv/config\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { PipelineManager } from \"../pipeline/PipelineManager\";\nimport type { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport { logger } from \"../utils/logger\";\nimport { startHttpServer } from \"./startHttpServer\";\nimport { startStdioServer } from \"./startStdioServer\";\nimport { type McpServerTools, initializeTools } from \"./tools\";\n\n// Variables to hold server instances for cleanup\nlet runningServer: McpServer | null = null;\n\nexport async function startServer(\n protocol: \"stdio\" | \"http\",\n docService: DocumentManagementService, // NEW PARAM\n pipelineManager: PipelineManager, // NEW PARAM\n port?: number, // Existing optional param\n) {\n try {\n // Initialize and get shared tools\n const tools: McpServerTools = await initializeTools(docService, pipelineManager); // Pass instances\n\n let serverInstance: McpServer;\n if (protocol === \"stdio\") {\n serverInstance = await startStdioServer(tools); // startStdioServer needs to return McpServer\n } else if (protocol === \"http\") {\n if (port === undefined) {\n logger.error(\"❌ HTTP protocol requires a port.\");\n process.exit(1);\n }\n serverInstance = await startHttpServer(tools, port); // startHttpServer needs to return McpServer\n } else {\n // This case should be caught by src/server.ts, but handle defensively\n logger.error(`❌ Unknown protocol: ${protocol}`);\n process.exit(1);\n }\n\n // Capture the running server instance\n runningServer = serverInstance;\n } catch (error) {\n logger.error(`❌ Fatal Error during server startup: ${error}`);\n // Attempt cleanup even if startup failed partially\n await stopServer();\n process.exit(1);\n }\n}\n\n/**\n * Stops the MCP server instance gracefully.\n * Shared services (PipelineManager, DocumentManagementService) are shut down\n * separately by the caller (e.g., src/index.ts).\n */\nexport async function stopServer() {\n logger.debug(\"Attempting to close MCP Server instance...\");\n let hadError = false;\n try {\n if (runningServer) {\n logger.debug(\"Closing MCP Server instance (McpServer/McpHttpServer)...\");\n await runningServer.close();\n logger.debug(\"MCP Server instance closed.\");\n } else {\n logger.debug(\"MCP Server instance was not running or already null.\");\n }\n } catch (e) {\n logger.error(`❌ Error closing MCP Server instance: ${e}`);\n hadError = true;\n }\n\n runningServer = null;\n // DocumentManagementService and PipelineManager instances are managed and shut down by src/index.ts.\n\n if (hadError) {\n logger.warn(\"⚠️ MCP Server instance stopped with errors.\");\n } else {\n logger.info(\"✅ MCP Server instance stopped.\");\n }\n}\n","import psl from \"psl\";\nimport { InvalidUrlError } from \"./errors\";\n\ninterface UrlNormalizerOptions {\n ignoreCase?: boolean;\n removeHash?: boolean;\n removeTrailingSlash?: boolean;\n removeQuery?: boolean;\n removeIndex?: boolean;\n}\n\nconst defaultNormalizerOptions: UrlNormalizerOptions = {\n ignoreCase: true,\n removeHash: true,\n removeTrailingSlash: true,\n removeQuery: false,\n removeIndex: true,\n};\n\nexport function normalizeUrl(\n url: string,\n options: UrlNormalizerOptions = defaultNormalizerOptions,\n): string {\n try {\n const parsedUrl = new URL(url);\n const finalOptions = { ...defaultNormalizerOptions, ...options };\n\n // Create a new URL to ensure proper structure\n const normalized = new URL(parsedUrl.origin + parsedUrl.pathname);\n\n // Remove index files first, before handling trailing slashes\n if (finalOptions.removeIndex) {\n normalized.pathname = normalized.pathname.replace(\n /\\/index\\.(html|htm|asp|php|jsp)$/i,\n \"/\",\n );\n }\n\n // Handle trailing slash\n if (finalOptions.removeTrailingSlash && normalized.pathname.length > 1) {\n normalized.pathname = normalized.pathname.replace(/\\/+$/, \"\");\n }\n\n // Keep original parts we want to preserve\n const preservedHash = !finalOptions.removeHash ? parsedUrl.hash : \"\";\n const preservedSearch = !finalOptions.removeQuery ? parsedUrl.search : \"\";\n\n // Construct final URL string in correct order (query before hash)\n let result = normalized.origin + normalized.pathname;\n if (preservedSearch) {\n result += preservedSearch;\n }\n if (preservedHash) {\n result += preservedHash;\n }\n\n // Apply case normalization if configured\n if (finalOptions.ignoreCase) {\n result = result.toLowerCase();\n }\n\n return result;\n } catch {\n return url; // Return original URL if parsing fails\n }\n}\n\n/**\n * Validates if a string is a valid URL\n * @throws {InvalidUrlError} If the URL is invalid\n */\nexport function validateUrl(url: string): void {\n try {\n new URL(url);\n } catch (error) {\n throw new InvalidUrlError(url, error instanceof Error ? error : undefined);\n }\n}\n\n/**\n * Checks if two URLs have the exact same hostname\n */\nexport function hasSameHostname(urlA: URL, urlB: URL): boolean {\n return urlA.hostname.toLowerCase() === urlB.hostname.toLowerCase();\n}\n\n/**\n * Checks if two URLs are on the same domain (including subdomains)\n * Using the public suffix list to properly handle domains like .co.uk\n */\nexport function hasSameDomain(urlA: URL, urlB: URL): boolean {\n const domainA = psl.get(urlA.hostname.toLowerCase());\n const domainB = psl.get(urlB.hostname.toLowerCase());\n return domainA !== null && domainA === domainB;\n}\n\n/**\n * Checks if a target URL is under the same path as the base URL\n * Example: base = https://example.com/docs/\n * target = https://example.com/docs/getting-started\n * result = true\n */\nexport function isSubpath(baseUrl: URL, targetUrl: URL): boolean {\n // Normalize paths to ensure consistent comparison\n const basePath = baseUrl.pathname.endsWith(\"/\")\n ? baseUrl.pathname\n : `${baseUrl.pathname}/`;\n\n return targetUrl.pathname.startsWith(basePath);\n}\n\nexport type { UrlNormalizerOptions };\n","import { minimatch } from \"minimatch\";\n\n/**\n * Utility functions for pattern matching (glob and regex) for URL filtering.\n * Supports auto-detection and conversion of glob patterns to RegExp.\n *\n * Patterns starting and ending with '/' are treated as regex, otherwise as glob (minimatch syntax).\n * Glob wildcards supported: '*' (any chars except '/'), '**' (any chars, including '/').\n *\n * @module patternMatcher\n */\n\n/**\n * Detects if a pattern is a regex (starts and ends with '/')\n */\nexport function isRegexPattern(pattern: string): boolean {\n return pattern.length > 2 && pattern.startsWith(\"/\") && pattern.endsWith(\"/\");\n}\n\n/**\n * Converts a pattern string to a RegExp instance (auto-detects glob/regex).\n * For globs, uses minimatch's internal conversion.\n */\nexport function patternToRegExp(pattern: string): RegExp {\n if (isRegexPattern(pattern)) {\n return new RegExp(pattern.slice(1, -1));\n }\n // For globs, minimatch.makeRe returns a RegExp\n const re = minimatch.makeRe(pattern, { dot: true });\n if (!re) throw new Error(`Invalid glob pattern: ${pattern}`);\n return re;\n}\n\n/**\n * Checks if a given path matches any pattern in the list.\n * For globs, uses minimatch. For regex, uses RegExp.\n */\nexport function matchesAnyPattern(path: string, patterns?: string[]): boolean {\n if (!patterns || patterns.length === 0) return false;\n // Always match from a leading slash for path-based globs\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return patterns.some((pattern) => {\n if (isRegexPattern(pattern)) {\n return patternToRegExp(pattern).test(normalizedPath);\n }\n // minimatch expects no leading slash for relative globs, but we keep it for consistency\n // so we strip the leading slash for minimatch\n return minimatch(normalizedPath.replace(/^\\//, \"\"), pattern, { dot: true });\n });\n}\n\n/**\n * Extracts the path and query from a URL string (no domain).\n */\nexport function extractPathAndQuery(url: string): string {\n try {\n const u = new URL(url);\n return u.pathname + (u.search || \"\");\n } catch {\n return url; // fallback: return as-is\n }\n}\n\n/**\n * Determines if a URL should be included based on include/exclude patterns.\n * Exclude patterns take precedence. If no include patterns, all are included by default.\n */\nexport function shouldIncludeUrl(\n url: string,\n includePatterns?: string[],\n excludePatterns?: string[],\n): boolean {\n // Always match from a leading slash for path-based globs\n const path = extractPathAndQuery(url);\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n // For file:// URLs, also match against the basename (strip leading slash from pattern for basename matching)\n let basename: string | undefined;\n if (url.startsWith(\"file://\")) {\n try {\n const u = new URL(url);\n basename = u.pathname ? u.pathname.split(\"/\").pop() : undefined;\n } catch {}\n }\n // Helper to strip leading slash from patterns for basename matching\n const stripSlash = (patterns?: string[]) =>\n patterns?.map((p) => (p.startsWith(\"/\") ? p.slice(1) : p));\n // Exclude patterns take precedence\n if (\n matchesAnyPattern(normalizedPath, excludePatterns) ||\n (basename && matchesAnyPattern(basename, stripSlash(excludePatterns)))\n )\n return false;\n if (!includePatterns || includePatterns.length === 0) return true;\n return (\n matchesAnyPattern(normalizedPath, includePatterns) ||\n (basename ? matchesAnyPattern(basename, stripSlash(includePatterns)) : false)\n );\n}\n","// Utility for scope filtering, extracted from WebScraperStrategy\nimport type { URL } from \"node:url\";\n\n/**\n * Returns true if the targetUrl is in scope of the baseUrl for the given scope.\n * - \"subpages\": same hostname, and target path starts with the parent directory of the base path\n * - \"hostname\": same hostname\n * - \"domain\": same top-level domain (e.g. example.com)\n */\nexport function isInScope(\n baseUrl: URL,\n targetUrl: URL,\n scope: \"subpages\" | \"hostname\" | \"domain\",\n): boolean {\n if (baseUrl.protocol !== targetUrl.protocol) return false;\n switch (scope) {\n case \"subpages\": {\n if (baseUrl.hostname !== targetUrl.hostname) return false;\n // Use the parent directory of the base path\n const baseDir = baseUrl.pathname.endsWith(\"/\")\n ? baseUrl.pathname\n : baseUrl.pathname.replace(/\\/[^/]*$/, \"/\");\n return targetUrl.pathname.startsWith(baseDir);\n }\n case \"hostname\":\n return baseUrl.hostname === targetUrl.hostname;\n case \"domain\": {\n // Compare the last two segments of the hostname (e.g. example.com)\n const getDomain = (host: string) => host.split(\".\").slice(-2).join(\".\");\n return getDomain(baseUrl.hostname) === getDomain(targetUrl.hostname);\n }\n default:\n return false;\n }\n}\n","import { URL } from \"node:url\";\nimport { CancellationError } from \"../../pipeline/errors\";\nimport type { Document, ProgressCallback } from \"../../types\";\nimport { logger } from \"../../utils/logger\";\nimport { type UrlNormalizerOptions, normalizeUrl } from \"../../utils/url\";\nimport type { ScraperOptions, ScraperProgress, ScraperStrategy } from \"../types\";\nimport { shouldIncludeUrl } from \"../utils/patternMatcher\";\nimport { isInScope } from \"../utils/scope\";\n\n// Define defaults for optional options\nconst DEFAULT_MAX_PAGES = 100;\nconst DEFAULT_MAX_DEPTH = 3;\nconst DEFAULT_CONCURRENCY = 3;\n\nexport type QueueItem = {\n url: string;\n depth: number;\n};\n\nexport interface BaseScraperStrategyOptions {\n urlNormalizerOptions?: UrlNormalizerOptions;\n}\n\nexport abstract class BaseScraperStrategy implements ScraperStrategy {\n protected visited = new Set<string>();\n protected pageCount = 0;\n\n abstract canHandle(url: string): boolean;\n\n protected options: BaseScraperStrategyOptions;\n\n constructor(options: BaseScraperStrategyOptions = {}) {\n this.options = options;\n }\n\n /**\n * Determines if a URL should be processed based on scope and include/exclude patterns in ScraperOptions.\n * Scope is checked first, then patterns.\n */\n protected shouldProcessUrl(url: string, options: ScraperOptions): boolean {\n if (options.scope) {\n try {\n const base = new URL(options.url);\n const target = new URL(url);\n if (!isInScope(base, target, options.scope)) return false;\n } catch {\n return false;\n }\n }\n return shouldIncludeUrl(url, options.includePatterns, options.excludePatterns);\n }\n\n /**\n * Process a single item from the queue.\n *\n * @returns A list of URLs to add to the queue\n */\n protected abstract processItem(\n item: QueueItem,\n options: ScraperOptions,\n progressCallback?: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal, // Add signal\n ): Promise<{\n document?: Document;\n links?: string[];\n }>;\n\n // Removed getProcessor method as processing is now handled by strategies using middleware pipelines\n\n protected async processBatch(\n batch: QueueItem[],\n baseUrl: URL,\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal, // Add signal\n ): Promise<QueueItem[]> {\n const results = await Promise.all(\n batch.map(async (item) => {\n // Check signal before processing each item in the batch\n if (signal?.aborted) {\n throw new CancellationError(\"Scraping cancelled during batch processing\");\n }\n // Resolve default for maxDepth check\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n if (item.depth > maxDepth) {\n return [];\n }\n\n try {\n // Pass signal to processItem\n const result = await this.processItem(item, options, undefined, signal);\n\n if (result.document) {\n this.pageCount++;\n // Resolve defaults for logging and progress callback\n const maxPages = options.maxPages ?? DEFAULT_MAX_PAGES;\n // maxDepth already resolved above\n logger.info(\n `🌐 Scraping page ${this.pageCount}/${maxPages} (depth ${item.depth}/${maxDepth}): ${item.url}`,\n );\n await progressCallback({\n pagesScraped: this.pageCount,\n maxPages: maxPages,\n currentUrl: item.url,\n depth: item.depth,\n maxDepth: maxDepth,\n document: result.document,\n });\n }\n\n const nextItems = result.links || [];\n return nextItems\n .map((value) => {\n try {\n const targetUrl = new URL(value, baseUrl);\n // Filter using shouldProcessUrl\n if (!this.shouldProcessUrl(targetUrl.href, options)) {\n return null;\n }\n return {\n url: targetUrl.href,\n depth: item.depth + 1,\n } satisfies QueueItem;\n } catch (error) {\n // Invalid URL or path\n logger.warn(`❌ Invalid URL: ${value}`);\n }\n return null;\n })\n .filter((item) => item !== null);\n } catch (error) {\n if (options.ignoreErrors) {\n logger.error(`❌ Failed to process ${item.url}: ${error}`);\n return [];\n }\n throw error;\n }\n }),\n );\n\n // After all concurrent processing is done, deduplicate the results\n const allLinks = results.flat();\n const uniqueLinks: QueueItem[] = [];\n\n // Now perform deduplication once, after all parallel processing is complete\n for (const item of allLinks) {\n const normalizedUrl = normalizeUrl(item.url, this.options.urlNormalizerOptions);\n if (!this.visited.has(normalizedUrl)) {\n this.visited.add(normalizedUrl);\n uniqueLinks.push(item);\n }\n }\n\n return uniqueLinks;\n }\n\n async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal, // Add signal\n ): Promise<void> {\n this.visited.clear();\n this.pageCount = 0;\n\n const baseUrl = new URL(options.url);\n const queue = [{ url: options.url, depth: 0 } satisfies QueueItem];\n\n // Track values we've seen (either queued or visited)\n this.visited.add(normalizeUrl(options.url, this.options.urlNormalizerOptions));\n\n // Resolve optional values to defaults using temporary variables\n const maxPages = options.maxPages ?? DEFAULT_MAX_PAGES;\n const maxConcurrency = options.maxConcurrency ?? DEFAULT_CONCURRENCY;\n\n while (queue.length > 0 && this.pageCount < maxPages) {\n // Use variable\n // Check for cancellation at the start of each loop iteration\n if (signal?.aborted) {\n logger.debug(\"Scraping cancelled by signal.\");\n throw new CancellationError(\"Scraping cancelled by signal\");\n }\n\n const remainingPages = maxPages - this.pageCount; // Use variable\n if (remainingPages <= 0) {\n break;\n }\n\n const batchSize = Math.min(\n maxConcurrency, // Use variable\n remainingPages,\n queue.length,\n );\n\n const batch = queue.splice(0, batchSize);\n // Pass signal to processBatch\n const newUrls = await this.processBatch(\n batch,\n baseUrl,\n options,\n progressCallback,\n signal,\n );\n\n queue.push(...newUrls);\n }\n }\n}\n","import type { Document, ProgressCallback } from \"../../types\";\nimport { logger } from \"../../utils/logger\";\nimport type { UrlNormalizerOptions } from \"../../utils/url\";\nimport { hasSameDomain, hasSameHostname, isSubpath } from \"../../utils/url\";\nimport { HttpFetcher } from \"../fetcher\";\nimport type { RawContent } from \"../fetcher/types\";\nimport { HtmlPipeline } from \"../pipelines/HtmlPipeline\";\nimport { MarkdownPipeline } from \"../pipelines/MarkdownPipeline\";\nimport type { ContentPipeline, ProcessedContent } from \"../pipelines/types\";\nimport type { ScraperOptions, ScraperProgress } from \"../types\";\nimport { BaseScraperStrategy, type QueueItem } from \"./BaseScraperStrategy\";\n\nexport interface WebScraperStrategyOptions {\n urlNormalizerOptions?: UrlNormalizerOptions;\n shouldFollowLink?: (baseUrl: URL, targetUrl: URL) => boolean;\n}\n\nexport class WebScraperStrategy extends BaseScraperStrategy {\n private readonly httpFetcher = new HttpFetcher();\n private readonly shouldFollowLinkFn?: (baseUrl: URL, targetUrl: URL) => boolean;\n private readonly htmlPipeline: HtmlPipeline;\n private readonly markdownPipeline: MarkdownPipeline;\n private readonly pipelines: ContentPipeline[];\n\n constructor(options: WebScraperStrategyOptions = {}) {\n super({ urlNormalizerOptions: options.urlNormalizerOptions });\n this.shouldFollowLinkFn = options.shouldFollowLink;\n this.htmlPipeline = new HtmlPipeline();\n this.markdownPipeline = new MarkdownPipeline();\n this.pipelines = [this.htmlPipeline, this.markdownPipeline];\n }\n\n canHandle(url: string): boolean {\n try {\n const parsedUrl = new URL(url);\n return parsedUrl.protocol === \"http:\" || parsedUrl.protocol === \"https:\";\n } catch {\n return false;\n }\n }\n\n /**\n * Determines if a target URL should be followed based on the scope setting.\n */\n private isInScope(\n baseUrl: URL,\n targetUrl: URL,\n scope: \"subpages\" | \"hostname\" | \"domain\",\n ): boolean {\n try {\n // First check if the URLs are on the same domain or hostname\n if (scope === \"domain\") {\n return hasSameDomain(baseUrl, targetUrl);\n }\n if (scope === \"hostname\") {\n return hasSameHostname(baseUrl, targetUrl);\n }\n // 'subpages' (default)\n return hasSameHostname(baseUrl, targetUrl) && isSubpath(baseUrl, targetUrl);\n } catch {\n return false;\n }\n }\n\n /**\n * Processes a single queue item by fetching its content and processing it through pipelines.\n * @param item - The queue item to process.\n * @param options - Scraper options including headers for HTTP requests.\n * @param _progressCallback - Optional progress callback (not used here).\n * @param signal - Optional abort signal for request cancellation.\n * @returns An object containing the processed document and extracted links.\n */\n protected override async processItem(\n item: QueueItem,\n options: ScraperOptions,\n _progressCallback?: ProgressCallback<ScraperProgress>, // Base class passes it, but not used here\n signal?: AbortSignal, // Add signal\n ): Promise<{ document?: Document; links?: string[] }> {\n const { url } = item;\n\n try {\n // Define fetch options, passing signal, followRedirects, and headers\n const fetchOptions = {\n signal,\n followRedirects: options.followRedirects,\n headers: options.headers, // Forward custom headers\n };\n\n // Pass options to fetcher\n const rawContent: RawContent = await this.httpFetcher.fetch(url, fetchOptions);\n\n // --- Start Pipeline Processing ---\n let processed: ProcessedContent | undefined;\n for (const pipeline of this.pipelines) {\n if (pipeline.canProcess(rawContent)) {\n processed = await pipeline.process(rawContent, options, this.httpFetcher);\n break;\n }\n }\n\n if (!processed) {\n logger.warn(\n `⚠️ Unsupported content type \"${rawContent.mimeType}\" for URL ${url}. Skipping processing.`,\n );\n return { document: undefined, links: [] };\n }\n\n // Log errors from pipeline\n for (const err of processed.errors) {\n logger.warn(`⚠️ Processing error for ${url}: ${err.message}`);\n }\n\n // Check if content processing resulted in usable content\n if (!processed.textContent || !processed.textContent.trim()) {\n logger.warn(\n `⚠️ No processable content found for ${url} after pipeline execution.`,\n );\n return { document: undefined, links: processed.links };\n }\n\n // Filter extracted links based on scope and custom filter\n const baseUrl = new URL(options.url);\n const filteredLinks = processed.links.filter((link) => {\n try {\n const targetUrl = new URL(link);\n const scope = options.scope || \"subpages\";\n return (\n this.isInScope(baseUrl, targetUrl, scope) &&\n (!this.shouldFollowLinkFn || this.shouldFollowLinkFn(baseUrl, targetUrl))\n );\n } catch {\n return false;\n }\n });\n\n return {\n document: {\n content: processed.textContent,\n metadata: {\n url,\n title:\n typeof processed.metadata.title === \"string\"\n ? processed.metadata.title\n : \"Untitled\",\n library: options.library,\n version: options.version,\n ...processed.metadata,\n },\n } satisfies Document,\n links: filteredLinks,\n };\n } catch (error) {\n // Log fetch errors or pipeline execution errors (if run throws)\n logger.error(`❌ Failed processing page ${url}: ${error}`);\n throw error;\n }\n }\n\n /**\n * Overrides the base scrape method to ensure the Playwright browser is closed\n * after the scraping process completes or errors out.\n */\n override async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal,\n ): Promise<void> {\n try {\n // Call the base class scrape method\n await super.scrape(options, progressCallback, signal);\n } finally {\n // Ensure the browser instance is closed\n await this.htmlPipeline.close();\n await this.markdownPipeline.close();\n }\n }\n}\n","import type { ProgressCallback } from \"../../types\";\nimport type { ScraperOptions, ScraperProgress, ScraperStrategy } from \"../types\";\nimport { WebScraperStrategy } from \"./WebScraperStrategy\";\n\nexport class GitHubScraperStrategy implements ScraperStrategy {\n private defaultStrategy: WebScraperStrategy;\n\n canHandle(url: string): boolean {\n const { hostname } = new URL(url);\n return [\"github.com\", \"www.github.com\"].includes(hostname);\n }\n\n constructor() {\n const shouldFollowLink = (baseUrl: URL, targetUrl: URL) => {\n // Must be in same repository\n if (this.getRepoPath(baseUrl) !== this.getRepoPath(targetUrl)) {\n return false;\n }\n\n const path = targetUrl.pathname;\n\n // Root README (repository root)\n if (path === this.getRepoPath(targetUrl)) {\n return true;\n }\n\n // Wiki pages\n if (path.startsWith(`${this.getRepoPath(targetUrl)}/wiki`)) {\n return true;\n }\n\n // Markdown files under /blob/\n if (\n path.startsWith(`${this.getRepoPath(targetUrl)}/blob/`) &&\n path.endsWith(\".md\")\n ) {\n return true;\n }\n\n return false;\n };\n\n this.defaultStrategy = new WebScraperStrategy({\n urlNormalizerOptions: {\n ignoreCase: true,\n removeHash: true,\n removeTrailingSlash: true,\n removeQuery: true, // Remove query parameters like ?tab=readme-ov-file\n },\n shouldFollowLink,\n });\n }\n\n private getRepoPath(url: URL): string {\n // Extract /<org>/<repo> from github.com/<org>/<repo>/...\n const match = url.pathname.match(/^\\/[^/]+\\/[^/]+/);\n return match?.[0] || \"\";\n }\n\n async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal,\n ): Promise<void> {\n // Validate it's a GitHub URL\n const url = new URL(options.url);\n if (!url.hostname.includes(\"github.com\")) {\n throw new Error(\"URL must be a GitHub URL\");\n }\n\n // Pass signal down to the delegated strategy\n await this.defaultStrategy.scrape(options, progressCallback, signal);\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Document, ProgressCallback } from \"../../types\";\nimport { logger } from \"../../utils/logger\";\nimport { FileFetcher } from \"../fetcher\";\nimport type { RawContent } from \"../fetcher/types\";\nimport { HtmlPipeline } from \"../pipelines/HtmlPipeline\";\nimport { MarkdownPipeline } from \"../pipelines/MarkdownPipeline\";\nimport type { ScraperOptions, ScraperProgress } from \"../types\";\nimport { BaseScraperStrategy, type QueueItem } from \"./BaseScraperStrategy\";\n\n/**\n * LocalFileStrategy handles crawling and scraping of local files and folders using file:// URLs.\n *\n * All files with a MIME type of `text/*` are processed. This includes HTML, Markdown, plain text, and source code files such as `.js`, `.ts`, `.tsx`, `.css`, etc. Binary files, PDFs, images, and other non-text formats are ignored.\n *\n * Supports include/exclude filters and percent-encoded paths.\n */\nexport class LocalFileStrategy extends BaseScraperStrategy {\n private readonly fileFetcher = new FileFetcher();\n private readonly htmlPipeline: HtmlPipeline;\n private readonly markdownPipeline: MarkdownPipeline;\n private readonly pipelines: [HtmlPipeline, MarkdownPipeline];\n\n constructor() {\n super();\n this.htmlPipeline = new HtmlPipeline();\n this.markdownPipeline = new MarkdownPipeline();\n this.pipelines = [this.htmlPipeline, this.markdownPipeline];\n }\n\n canHandle(url: string): boolean {\n return url.startsWith(\"file://\");\n }\n\n protected async processItem(\n item: QueueItem,\n options: ScraperOptions,\n _progressCallback?: ProgressCallback<ScraperProgress>,\n _signal?: AbortSignal,\n ): Promise<{ document?: Document; links?: string[] }> {\n // Always decode the file path from file:// URL\n const filePath = decodeURIComponent(item.url.replace(/^file:\\/\\//, \"\"));\n const stats = await fs.stat(filePath);\n\n if (stats.isDirectory()) {\n const contents = await fs.readdir(filePath);\n // Only return links that pass shouldProcessUrl\n const links = contents\n .map((name) => `file://${path.join(filePath, name)}`)\n .filter((url) => this.shouldProcessUrl(url, options));\n return { links };\n }\n\n logger.info(`🗂️ Processing file ${this.pageCount}/${options.maxPages}: ${filePath}`);\n\n const rawContent: RawContent = await this.fileFetcher.fetch(item.url);\n\n let processed: Awaited<ReturnType<HtmlPipeline[\"process\"]>> | undefined;\n\n for (const pipeline of this.pipelines) {\n if (pipeline.canProcess(rawContent)) {\n processed = await pipeline.process(rawContent, options, this.fileFetcher);\n break;\n }\n }\n\n if (!processed) {\n logger.warn(\n `⚠️ Unsupported content type \"${rawContent.mimeType}\" for file ${filePath}. Skipping processing.`,\n );\n return { document: undefined, links: [] };\n }\n\n for (const err of processed.errors) {\n logger.warn(`⚠️ Processing error for ${filePath}: ${err.message}`);\n }\n\n return {\n document: {\n content: typeof processed.textContent === \"string\" ? processed.textContent : \"\",\n metadata: {\n url: rawContent.source,\n title:\n typeof processed.metadata.title === \"string\"\n ? processed.metadata.title\n : \"Untitled\",\n library: options.library,\n version: options.version,\n },\n } satisfies Document,\n };\n }\n\n async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal,\n ): Promise<void> {\n try {\n await super.scrape(options, progressCallback, signal);\n } finally {\n await this.htmlPipeline.close();\n await this.markdownPipeline.close();\n }\n }\n}\n","import type { ProgressCallback } from \"../../types\";\nimport type { ScraperOptions, ScraperProgress, ScraperStrategy } from \"../types\";\nimport { WebScraperStrategy } from \"./WebScraperStrategy\";\n\nexport class NpmScraperStrategy implements ScraperStrategy {\n private defaultStrategy: WebScraperStrategy;\n\n canHandle(url: string): boolean {\n const { hostname } = new URL(url);\n return [\"npmjs.org\", \"npmjs.com\", \"www.npmjs.com\"].includes(hostname);\n }\n\n constructor() {\n this.defaultStrategy = new WebScraperStrategy({\n urlNormalizerOptions: {\n ignoreCase: true,\n removeHash: true,\n removeTrailingSlash: true,\n removeQuery: true, // Enable removeQuery for NPM packages\n },\n });\n }\n\n async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal,\n ): Promise<void> {\n // Use default strategy with our configuration, passing the signal\n await this.defaultStrategy.scrape(options, progressCallback, signal);\n }\n}\n","import type { ProgressCallback } from \"../../types\";\nimport type { ScraperOptions, ScraperProgress, ScraperStrategy } from \"../types\";\nimport { WebScraperStrategy } from \"./WebScraperStrategy\";\n\nexport class PyPiScraperStrategy implements ScraperStrategy {\n private defaultStrategy: WebScraperStrategy;\n\n canHandle(url: string): boolean {\n const { hostname } = new URL(url);\n return [\"pypi.org\", \"www.pypi.org\"].includes(hostname);\n }\n\n constructor() {\n this.defaultStrategy = new WebScraperStrategy({\n urlNormalizerOptions: {\n ignoreCase: true,\n removeHash: true,\n removeTrailingSlash: true,\n removeQuery: true, // Enable removeQuery for PyPI packages\n },\n });\n }\n\n async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal,\n ): Promise<void> {\n // Use default strategy with our configuration, passing the signal\n await this.defaultStrategy.scrape(options, progressCallback, signal);\n }\n}\n","import { ScraperError } from \"../utils/errors\";\nimport { validateUrl } from \"../utils/url\";\nimport { GitHubScraperStrategy } from \"./strategies/GitHubScraperStrategy\";\nimport { LocalFileStrategy } from \"./strategies/LocalFileStrategy\";\nimport { NpmScraperStrategy } from \"./strategies/NpmScraperStrategy\";\nimport { PyPiScraperStrategy } from \"./strategies/PyPiScraperStrategy\";\nimport { WebScraperStrategy } from \"./strategies/WebScraperStrategy\";\nimport type { ScraperStrategy } from \"./types\";\n\nexport class ScraperRegistry {\n private strategies: ScraperStrategy[];\n\n constructor() {\n this.strategies = [\n new NpmScraperStrategy(),\n new PyPiScraperStrategy(),\n new GitHubScraperStrategy(),\n new WebScraperStrategy(),\n new LocalFileStrategy(),\n ];\n }\n\n getStrategy(url: string): ScraperStrategy {\n validateUrl(url);\n const strategy = this.strategies.find((s) => s.canHandle(url));\n if (!strategy) {\n throw new ScraperError(`No strategy found for URL: ${url}`);\n }\n return strategy;\n }\n}\n","import type { ProgressCallback } from \"../types\";\nimport { ScraperError } from \"../utils/errors\";\nimport type { ScraperRegistry } from \"./ScraperRegistry\";\nimport type { ScraperOptions, ScraperProgress } from \"./types\";\n\n/**\n * Orchestrates document scraping operations using registered scraping strategies.\n * Automatically selects appropriate strategy based on URL patterns.\n */\nexport class ScraperService {\n private registry: ScraperRegistry;\n\n constructor(registry: ScraperRegistry) {\n this.registry = registry;\n }\n\n /**\n * Scrapes content from the provided URL using the appropriate strategy.\n * Reports progress via callback and handles errors.\n */\n async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal, // Add optional signal parameter\n ): Promise<void> {\n // Find strategy for this URL\n const strategy = this.registry.getStrategy(options.url);\n if (!strategy) {\n throw new ScraperError(`No scraper strategy found for URL: ${options.url}`, false);\n }\n\n // Pass the signal down to the strategy\n await strategy.scrape(options, progressCallback, signal);\n }\n}\n","import type { ScraperService } from \"../scraper\";\nimport type { ScraperProgress } from \"../scraper/types\";\nimport type { DocumentManagementService } from \"../store\";\nimport { logger } from \"../utils/logger\";\nimport { CancellationError } from \"./errors\";\nimport type { PipelineJob, PipelineManagerCallbacks } from \"./types\";\n\n/**\n * Executes a single document processing job.\n * Handles scraping, storing documents, and reporting progress/errors via callbacks.\n */\nexport class PipelineWorker {\n // Dependencies are passed in, making the worker stateless regarding specific jobs\n private readonly store: DocumentManagementService;\n private readonly scraperService: ScraperService;\n\n // Constructor accepts dependencies needed for execution\n constructor(store: DocumentManagementService, scraperService: ScraperService) {\n this.store = store;\n this.scraperService = scraperService;\n }\n\n /**\n * Executes the given pipeline job.\n * @param job - The job to execute.\n * @param callbacks - Callbacks provided by the manager for reporting.\n */\n async executeJob(job: PipelineJob, callbacks: PipelineManagerCallbacks): Promise<void> {\n const { id: jobId, library, version, options, abortController } = job;\n const signal = abortController.signal;\n\n logger.debug(`[${jobId}] Worker starting job for ${library}@${version}`);\n\n try {\n // --- Core Job Logic ---\n await this.scraperService.scrape(\n options,\n async (progress: ScraperProgress) => {\n // Check for cancellation signal before processing each document\n if (signal.aborted) {\n throw new CancellationError(\"Job cancelled during scraping progress\");\n }\n\n // Update job object directly (manager holds the reference)\n job.progress = progress;\n // Report progress via manager's callback\n await callbacks.onJobProgress?.(job, progress);\n\n if (progress.document) {\n try {\n // TODO: Pass signal to store.addDocument if it supports it\n await this.store.addDocument(library, version, {\n pageContent: progress.document.content,\n metadata: progress.document.metadata,\n });\n logger.debug(\n `[${jobId}] Stored document: ${progress.document.metadata.url}`,\n );\n } catch (docError) {\n logger.error(\n `❌ [${jobId}] Failed to store document ${progress.document.metadata.url}: ${docError}`,\n );\n // Report document-specific errors via manager's callback\n await callbacks.onJobError?.(\n job,\n docError instanceof Error ? docError : new Error(String(docError)),\n progress.document,\n );\n // Decide if a single document error should fail the whole job\n // For now, we log and continue. To fail, re-throw here.\n }\n }\n },\n signal, // Pass signal to scraper service\n );\n // --- End Core Job Logic ---\n\n // Check signal one last time after scrape finishes\n if (signal.aborted) {\n throw new CancellationError(\"Job cancelled\");\n }\n\n // If successful and not cancelled, the manager will handle status update\n logger.debug(`[${jobId}] Worker finished job successfully.`);\n } catch (error) {\n // Re-throw error to be caught by the manager in _runJob\n logger.warn(`⚠️ [${jobId}] Worker encountered error: ${error}`);\n throw error;\n }\n // Note: The manager (_runJob) is responsible for updating final job status (COMPLETED/FAILED/CANCELLED)\n // and resolving/rejecting the completion promise based on the outcome here.\n }\n\n // --- Old methods removed ---\n // process()\n // stop()\n // setCallbacks()\n // handleScrapingProgress()\n}\n","import { v4 as uuidv4 } from \"uuid\";\nimport { ScraperRegistry, ScraperService } from \"../scraper\";\nimport type { ScraperOptions } from \"../scraper/types\";\nimport type { DocumentManagementService } from \"../store\";\nimport { logger } from \"../utils/logger\";\nimport { PipelineWorker } from \"./PipelineWorker\"; // Import the worker\nimport { CancellationError, PipelineStateError } from \"./errors\";\nimport type { PipelineJob, PipelineManagerCallbacks } from \"./types\";\nimport { PipelineJobStatus } from \"./types\";\n\nconst DEFAULT_CONCURRENCY = 3;\n\n/**\n * Manages a queue of document processing jobs, controlling concurrency and tracking progress.\n */\nexport class PipelineManager {\n private jobMap: Map<string, PipelineJob> = new Map();\n private jobQueue: string[] = [];\n private activeWorkers: Set<string> = new Set();\n private isRunning = false;\n private concurrency: number;\n private callbacks: PipelineManagerCallbacks = {};\n private store: DocumentManagementService;\n private scraperService: ScraperService;\n\n constructor(\n store: DocumentManagementService,\n concurrency: number = DEFAULT_CONCURRENCY,\n ) {\n this.store = store;\n this.concurrency = concurrency;\n // ScraperService needs a registry. We create one internally for the manager.\n const registry = new ScraperRegistry();\n this.scraperService = new ScraperService(registry);\n }\n\n /**\n * Registers callback handlers for pipeline manager events.\n */\n setCallbacks(callbacks: PipelineManagerCallbacks): void {\n this.callbacks = callbacks;\n }\n\n /**\n * Starts the pipeline manager's worker processing.\n */\n async start(): Promise<void> {\n if (this.isRunning) {\n logger.warn(\"⚠️ PipelineManager is already running.\");\n return;\n }\n this.isRunning = true;\n logger.debug(`PipelineManager started with concurrency ${this.concurrency}.`);\n this._processQueue(); // Start processing any existing jobs\n }\n\n /**\n * Stops the pipeline manager and attempts to gracefully shut down workers.\n * Currently, it just stops processing new jobs. Cancellation of active jobs\n * needs explicit `cancelJob` calls.\n */\n async stop(): Promise<void> {\n if (!this.isRunning) {\n logger.warn(\"⚠️ PipelineManager is not running.\");\n return;\n }\n this.isRunning = false;\n logger.debug(\"PipelineManager stopping. No new jobs will be started.\");\n // Note: Does not automatically cancel active jobs.\n }\n\n /**\n * Finds jobs by library, version, and optional status.\n */\n findJobsByLibraryVersion(\n library: string,\n version: string,\n statuses?: PipelineJobStatus[],\n ): PipelineJob[] {\n return Array.from(this.jobMap.values()).filter(\n (job) =>\n job.library === library &&\n job.version === version &&\n (!statuses || statuses.includes(job.status)),\n );\n }\n\n /**\n * Enqueues a new document processing job, aborting any existing QUEUED/RUNNING job for the same library+version (including unversioned).\n */\n async enqueueJob(\n library: string,\n version: string | undefined | null,\n options: ScraperOptions,\n ): Promise<string> {\n // Normalize version: treat undefined/null as \"\" (unversioned)\n const normalizedVersion = version ?? \"\";\n // Abort any existing QUEUED or RUNNING job for the same library+version\n const duplicateJobs = this.findJobsByLibraryVersion(library, normalizedVersion, [\n PipelineJobStatus.QUEUED,\n PipelineJobStatus.RUNNING,\n ]);\n for (const job of duplicateJobs) {\n logger.info(\n `🚫 Aborting duplicate job for ${library}@${normalizedVersion}: ${job.id}`,\n );\n await this.cancelJob(job.id);\n }\n\n const jobId = uuidv4();\n const abortController = new AbortController();\n let resolveCompletion!: () => void;\n let rejectCompletion!: (reason?: unknown) => void;\n\n const completionPromise = new Promise<void>((resolve, reject) => {\n resolveCompletion = resolve;\n rejectCompletion = reject;\n });\n\n const job: PipelineJob = {\n id: jobId,\n library,\n version: normalizedVersion,\n options,\n status: PipelineJobStatus.QUEUED,\n progress: null,\n error: null,\n createdAt: new Date(),\n startedAt: null,\n finishedAt: null,\n abortController,\n completionPromise,\n resolveCompletion,\n rejectCompletion,\n };\n\n this.jobMap.set(jobId, job);\n this.jobQueue.push(jobId);\n logger.info(\n `📝 Job enqueued: ${jobId} for ${library}${normalizedVersion ? `@${normalizedVersion}` : \" (unversioned)\"}`,\n );\n\n await this.callbacks.onJobStatusChange?.(job);\n\n // Trigger processing if manager is running\n if (this.isRunning) {\n this._processQueue();\n }\n\n return jobId;\n }\n\n /**\n * Retrieves the current state of a specific job.\n */\n async getJob(jobId: string): Promise<PipelineJob | undefined> {\n return this.jobMap.get(jobId);\n }\n\n /**\n * Retrieves the current state of all jobs (or a subset based on status).\n */\n async getJobs(status?: PipelineJobStatus): Promise<PipelineJob[]> {\n const allJobs = Array.from(this.jobMap.values());\n if (status) {\n return allJobs.filter((job) => job.status === status);\n }\n return allJobs;\n }\n\n /**\n * Returns a promise that resolves when the specified job completes, fails, or is cancelled.\n * For cancelled jobs, this resolves successfully rather than rejecting.\n */\n async waitForJobCompletion(jobId: string): Promise<void> {\n const job = this.jobMap.get(jobId);\n if (!job) {\n throw new PipelineStateError(`Job not found: ${jobId}`);\n }\n\n try {\n await job.completionPromise;\n } catch (error) {\n // If the job was cancelled, treat it as successful completion\n if (\n error instanceof CancellationError ||\n job.status === PipelineJobStatus.CANCELLED\n ) {\n return; // Resolve successfully for cancelled jobs\n }\n // Re-throw other errors (failed jobs)\n throw error;\n }\n }\n\n /**\n * Attempts to cancel a queued or running job.\n */\n async cancelJob(jobId: string): Promise<void> {\n const job = this.jobMap.get(jobId);\n if (!job) {\n logger.warn(`❓ Attempted to cancel non-existent job: ${jobId}`);\n return;\n }\n\n switch (job.status) {\n case PipelineJobStatus.QUEUED:\n // Remove from queue and mark as cancelled\n this.jobQueue = this.jobQueue.filter((id) => id !== jobId);\n job.status = PipelineJobStatus.CANCELLED;\n job.finishedAt = new Date();\n logger.info(`🚫 Job cancelled (was queued): ${jobId}`);\n await this.callbacks.onJobStatusChange?.(job);\n job.rejectCompletion(new PipelineStateError(\"Job cancelled before starting\"));\n break;\n\n case PipelineJobStatus.RUNNING:\n // Signal cancellation via AbortController\n job.status = PipelineJobStatus.CANCELLING;\n job.abortController.abort();\n logger.info(`🚫 Signalling cancellation for running job: ${jobId}`);\n await this.callbacks.onJobStatusChange?.(job);\n // The worker is responsible for transitioning to CANCELLED and rejecting\n break;\n\n case PipelineJobStatus.COMPLETED:\n case PipelineJobStatus.FAILED:\n case PipelineJobStatus.CANCELLED:\n case PipelineJobStatus.CANCELLING:\n logger.warn(\n `⚠️ Job ${jobId} cannot be cancelled in its current state: ${job.status}`,\n );\n break;\n\n default:\n logger.error(`❌ Unhandled job status for cancellation: ${job.status}`);\n break;\n }\n }\n\n /**\n * Removes all jobs that are in a final state (completed, cancelled, or failed).\n * Only removes jobs that are not currently in the queue or actively running.\n * @returns The number of jobs that were cleared.\n */\n async clearCompletedJobs(): Promise<number> {\n const completedStatuses = [\n PipelineJobStatus.COMPLETED,\n PipelineJobStatus.CANCELLED,\n PipelineJobStatus.FAILED,\n ];\n\n let clearedCount = 0;\n const jobsToRemove: string[] = [];\n\n // Find all jobs that can be cleared\n for (const [jobId, job] of this.jobMap.entries()) {\n if (completedStatuses.includes(job.status)) {\n jobsToRemove.push(jobId);\n clearedCount++;\n }\n }\n\n // Remove the jobs from the map\n for (const jobId of jobsToRemove) {\n this.jobMap.delete(jobId);\n }\n\n if (clearedCount > 0) {\n logger.info(`🧹 Cleared ${clearedCount} completed job(s) from the queue`);\n } else {\n logger.debug(\"No completed jobs to clear\");\n }\n\n return clearedCount;\n }\n\n // --- Private Methods ---\n\n /**\n * Processes the job queue, starting new workers if capacity allows.\n */\n private _processQueue(): void {\n if (!this.isRunning) return;\n\n while (this.activeWorkers.size < this.concurrency && this.jobQueue.length > 0) {\n const jobId = this.jobQueue.shift();\n if (!jobId) continue; // Should not happen, but safety check\n\n const job = this.jobMap.get(jobId);\n if (!job || job.status !== PipelineJobStatus.QUEUED) {\n logger.warn(`⏭️ Skipping job ${jobId} in queue (not found or not queued).`);\n continue;\n }\n\n this.activeWorkers.add(jobId);\n job.status = PipelineJobStatus.RUNNING;\n job.startedAt = new Date();\n this.callbacks.onJobStatusChange?.(job); // Fire and forget status update\n\n // Start the actual job execution asynchronously\n this._runJob(job).catch((error) => {\n // Catch unexpected errors during job setup/execution not handled by _runJob itself\n logger.error(`❌ Unhandled error during job ${jobId} execution: ${error}`);\n if (\n job.status !== PipelineJobStatus.FAILED &&\n job.status !== PipelineJobStatus.CANCELLED\n ) {\n job.status = PipelineJobStatus.FAILED;\n job.error = error instanceof Error ? error : new Error(String(error));\n job.finishedAt = new Date();\n this.callbacks.onJobStatusChange?.(job); // Fire and forget\n job.rejectCompletion(job.error);\n }\n this.activeWorkers.delete(jobId);\n this._processQueue(); // Check if another job can start\n });\n }\n }\n\n /**\n * Executes a single pipeline job by delegating to a PipelineWorker.\n * Handles final status updates and promise resolution/rejection.\n */\n private async _runJob(job: PipelineJob): Promise<void> {\n const { id: jobId, abortController } = job;\n const signal = abortController.signal; // Get signal for error checking\n\n // Instantiate a worker for this job.\n // Dependencies (store, scraperService) are held by the manager.\n const worker = new PipelineWorker(this.store, this.scraperService);\n\n try {\n // Delegate the actual work to the worker\n await worker.executeJob(job, this.callbacks);\n\n // If executeJob completes without throwing, and we weren't cancelled meanwhile...\n if (signal.aborted) {\n // Check signal again in case cancellation happened *during* the very last await in executeJob\n throw new CancellationError(\"Job cancelled just before completion\");\n }\n\n // Mark as completed\n job.status = PipelineJobStatus.COMPLETED;\n job.finishedAt = new Date();\n await this.callbacks.onJobStatusChange?.(job);\n job.resolveCompletion();\n\n logger.info(`✅ Job completed: ${jobId}`);\n } catch (error) {\n // Handle errors thrown by the worker, including CancellationError\n if (error instanceof CancellationError || signal.aborted) {\n // Explicitly check for CancellationError or if the signal was aborted\n job.status = PipelineJobStatus.CANCELLED;\n job.finishedAt = new Date();\n // Don't set job.error for cancellations - cancellation is not an error condition\n const cancellationError =\n error instanceof CancellationError\n ? error\n : new CancellationError(\"Job cancelled by signal\");\n logger.info(`🚫 Job execution cancelled: ${jobId}: ${cancellationError.message}`);\n await this.callbacks.onJobStatusChange?.(job);\n job.rejectCompletion(cancellationError);\n } else {\n // Handle other errors\n job.status = PipelineJobStatus.FAILED;\n job.error = error instanceof Error ? error : new Error(String(error));\n job.finishedAt = new Date();\n logger.error(`❌ Job failed: ${jobId}: ${job.error}`);\n await this.callbacks.onJobStatusChange?.(job);\n job.rejectCompletion(job.error);\n }\n } finally {\n // Ensure worker slot is freed and queue processing continues\n this.activeWorkers.delete(jobId);\n this._processQueue();\n }\n }\n}\n","/**\n * Thoroughly removes all types of whitespace characters from both ends of a string.\n * Handles spaces, tabs, line breaks, and carriage returns.\n */\nexport const fullTrim = (str: string): string => {\n return str.replace(/^[\\s\\r\\n\\t]+|[\\s\\r\\n\\t]+$/g, \"\");\n};\n","/**\n * Base error class for all splitter-related errors\n */\nexport class SplitterError extends Error {}\n\n/**\n * Thrown when content cannot be split further while maintaining its validity\n * (e.g., markdown tables require headers, code blocks require language and backticks)\n */\nexport class MinimumChunkSizeError extends SplitterError {\n constructor(size: number, maxSize: number) {\n super(\n `Cannot split content any further. Content requires minimum chunk size of ${size} bytes, but maximum allowed is ${maxSize} bytes.`,\n );\n }\n}\n\n/**\n * Generic error for content splitting failures\n */\nexport class ContentSplitterError extends SplitterError {}\n","import { MinimumChunkSizeError } from \"../errors\";\nimport type { ContentSplitter, ContentSplitterOptions } from \"./types\";\n\n/**\n * Splits code content while preserving language information and formatting.\n * Uses line boundaries for splitting and ensures each chunk is properly\n * wrapped with language-specific code block markers.\n */\nexport class CodeContentSplitter implements ContentSplitter {\n constructor(private options: ContentSplitterOptions) {}\n\n async split(content: string): Promise<string[]> {\n // Determine language and strip triple backticks from content\n const language = content.match(/^```(\\w+)\\n/)?.[1];\n const strippedContent = content.replace(/^```(\\w*)\\n/, \"\").replace(/```\\s*$/, \"\");\n\n const lines = strippedContent.split(\"\\n\");\n const chunks: string[] = [];\n let currentChunkLines: string[] = [];\n\n for (const line of lines) {\n // Check if a single line with code block markers exceeds chunkSize\n const singleLineSize = this.wrap(line, language).length;\n if (singleLineSize > this.options.chunkSize) {\n throw new MinimumChunkSizeError(singleLineSize, this.options.chunkSize);\n }\n\n currentChunkLines.push(line);\n const newChunkContent = this.wrap(currentChunkLines.join(\"\\n\"), language);\n const newChunkSize = newChunkContent.length;\n\n if (newChunkSize > this.options.chunkSize && currentChunkLines.length > 1) {\n // remove last item\n const lastLine = currentChunkLines.pop();\n // wrap content and create chunk\n chunks.push(this.wrap(currentChunkLines.join(\"\\n\"), language));\n currentChunkLines = [lastLine as string];\n }\n }\n\n if (currentChunkLines.length > 0) {\n chunks.push(this.wrap(currentChunkLines.join(\"\\n\"), language));\n }\n\n return chunks;\n }\n\n protected wrap(content: string, language?: string | null): string {\n return `\\`\\`\\`${language || \"\"}\\n${content.replace(/\\n+$/, \"\")}\\n\\`\\`\\``;\n }\n}\n","import { MinimumChunkSizeError } from \"../errors\";\nimport type { ContentSplitter, ContentSplitterOptions } from \"./types\";\n\n/**\n * Interface representing the structure of a parsed markdown table\n */\ninterface ParsedTable {\n headers: string[];\n separator: string;\n rows: string[];\n}\n\n/**\n * Splits table content while preserving headers and table formatting.\n * Each chunk maintains the table structure with headers and separator row.\n */\nexport class TableContentSplitter implements ContentSplitter {\n constructor(private options: ContentSplitterOptions) {}\n\n /**\n * Splits table content into chunks while preserving table structure\n */\n async split(content: string): Promise<string[]> {\n const parsedTable = this.parseTable(content);\n if (!parsedTable) {\n return [content];\n }\n\n const { headers, rows } = parsedTable;\n\n const chunks: string[] = [];\n let currentRows: string[] = [];\n\n for (const row of rows) {\n // Check if a single row with headers exceeds chunkSize\n const singleRowSize = this.wrap(row, headers).length;\n if (singleRowSize > this.options.chunkSize) {\n throw new MinimumChunkSizeError(singleRowSize, this.options.chunkSize);\n }\n\n const newChunkContent = this.wrap([...currentRows, row].join(\"\\n\"), headers);\n const newChunkSize = newChunkContent.length;\n if (newChunkSize > this.options.chunkSize && currentRows.length > 0) {\n // Add current chunk, start new\n chunks.push(this.wrap(currentRows.join(\"\\n\"), headers));\n currentRows = [row];\n } else {\n currentRows.push(row);\n }\n }\n\n if (currentRows.length > 0) {\n chunks.push(this.wrap(currentRows.join(\"\\n\"), headers));\n }\n\n // No merging of table chunks\n return chunks;\n }\n\n protected wrap(content: string, headers: string[]): string {\n const headerRow = `| ${headers.join(\" | \")} |`;\n const separatorRow = `|${headers.map(() => \"---\").join(\"|\")}|`;\n return [headerRow, separatorRow, content].join(\"\\n\");\n }\n\n private parseTable(content: string): ParsedTable | null {\n const lines = content.trim().split(\"\\n\");\n if (lines.length < 3) return null; // Need at least headers, separator, and one data row\n\n const headers = this.parseRow(lines[0]);\n if (!headers) return null;\n\n const separator = lines[1];\n if (!this.isValidSeparator(separator)) return null;\n\n const rows = lines.slice(2).filter((row) => row.trim() !== \"\");\n\n return { headers, separator, rows };\n }\n\n /**\n * Parses a table row into cells\n */\n private parseRow(row: string): string[] | null {\n if (!row.includes(\"|\")) return null;\n return row\n .split(\"|\")\n .map((cell) => cell.trim())\n .filter((cell) => cell !== \"\");\n }\n\n /**\n * Validates the separator row of the table\n */\n private isValidSeparator(separator: string): boolean {\n return separator.includes(\"|\") && /^\\|?[\\s-|]+\\|?$/.test(separator);\n }\n}\n","import { RecursiveCharacterTextSplitter } from \"langchain/text_splitter\";\nimport { fullTrim } from \"../../utils/string\";\nimport { MinimumChunkSizeError } from \"../errors\";\nimport type { ContentSplitter, ContentSplitterOptions } from \"./types\";\n\n/**\n * Splits text content using a hierarchical approach:\n * 1. Try splitting by paragraphs (double newlines)\n * 2. If chunks still too large, split by single newlines\n * 3. Finally, use word boundaries via LangChain's splitter\n */\nexport class TextContentSplitter implements ContentSplitter {\n constructor(private options: ContentSplitterOptions) {}\n\n /**\n * Splits text content into chunks while trying to preserve semantic boundaries.\n * Prefers paragraph breaks, then line breaks, finally falling back to word boundaries.\n */\n async split(content: string): Promise<string[]> {\n const trimmedContent = fullTrim(content);\n\n if (trimmedContent.length <= this.options.chunkSize) {\n return [trimmedContent];\n }\n\n // Check for unsplittable content (e.g., a single word longer than chunkSize)\n const words = trimmedContent.split(/\\s+/);\n const longestWord = words.reduce((max, word) =>\n word.length > max.length ? word : max,\n );\n if (longestWord.length > this.options.chunkSize) {\n throw new MinimumChunkSizeError(longestWord.length, this.options.chunkSize);\n }\n\n // First try splitting by paragraphs (double newlines)\n const paragraphChunks = this.splitByParagraphs(trimmedContent);\n if (this.areChunksValid(paragraphChunks)) {\n // No merging for paragraph chunks; they are already semantically separated\n return paragraphChunks;\n }\n\n // If that doesn't work, try splitting by single newlines\n const lineChunks = this.splitByLines(trimmedContent);\n if (this.areChunksValid(lineChunks)) {\n return this.mergeChunks(lineChunks, \"\\n\");\n }\n\n // Finally, fall back to word-based splitting using LangChain\n const wordChunks = await this.splitByWords(trimmedContent);\n return this.mergeChunks(wordChunks, \" \");\n }\n\n /**\n * Checks if all chunks are within the maximum size limit\n */\n private areChunksValid(chunks: string[]): boolean {\n return chunks.every((chunk) => chunk.length <= this.options.chunkSize);\n }\n\n /**\n * Splits text into chunks by paragraph boundaries (double newlines)\n */\n private splitByParagraphs(text: string): string[] {\n const paragraphs = text\n .split(/\\n\\s*\\n/)\n .map((p) => fullTrim(p))\n .filter(Boolean);\n\n return paragraphs.filter((chunk) => chunk.length > 2);\n }\n\n /**\n * Splits text into chunks by line boundaries\n */\n private splitByLines(text: string): string[] {\n const lines = text\n .split(/\\n/)\n .map((line) => fullTrim(line))\n .filter(Boolean);\n\n return lines.filter((chunk) => chunk.length > 1);\n }\n\n /**\n * Uses LangChain's recursive splitter for word-based splitting as a last resort\n */\n private async splitByWords(text: string): Promise<string[]> {\n const splitter = new RecursiveCharacterTextSplitter({\n chunkSize: this.options.chunkSize,\n chunkOverlap: 0,\n });\n\n const chunks = await splitter.splitText(text);\n return chunks;\n }\n\n /**\n * Attempts to merge small chunks with previous chunks to minimize fragmentation.\n * Only merges if combined size is within maxChunkSize.\n */\n protected mergeChunks(chunks: string[], separator: string): string[] {\n const mergedChunks: string[] = [];\n let currentChunk: string | null = null;\n\n for (const chunk of chunks) {\n if (currentChunk === null) {\n currentChunk = chunk;\n continue;\n }\n\n const currentChunkSize = this.getChunkSize(currentChunk);\n const nextChunkSize = this.getChunkSize(chunk);\n\n if (currentChunkSize + nextChunkSize + separator.length <= this.options.chunkSize) {\n // Merge chunks\n currentChunk = `${currentChunk}${separator}${chunk}`;\n } else {\n // Add the current chunk to the result and start a new one\n mergedChunks.push(currentChunk);\n currentChunk = chunk;\n }\n }\n\n if (currentChunk) {\n mergedChunks.push(currentChunk);\n }\n\n return mergedChunks;\n }\n\n protected getChunkSize(chunk: string): number {\n return chunk.length;\n }\n\n protected wrap(content: string): string {\n return content;\n }\n}\n","import { JSDOM } from \"jsdom\";\nimport { RecursiveCharacterTextSplitter } from \"langchain/text_splitter\";\nimport remarkGfm from \"remark-gfm\";\nimport remarkHtml from \"remark-html\";\nimport remarkParse from \"remark-parse\";\nimport TurndownService from \"turndown\";\nimport { unified } from \"unified\";\nimport { createJSDOM } from \"../utils/dom\";\nimport { logger } from \"../utils/logger\";\nimport { fullTrim } from \"../utils/string\";\nimport { ContentSplitterError, MinimumChunkSizeError } from \"./errors\";\nimport { CodeContentSplitter } from \"./splitters/CodeContentSplitter\";\nimport { TableContentSplitter } from \"./splitters/TableContentSplitter\";\nimport { TextContentSplitter } from \"./splitters/TextContentSplitter\";\nimport type { ContentChunk, DocumentSplitter, SectionContentType } from \"./types\";\n\n/**\n * Represents a section of content within a document,\n * typically defined by a heading\n */\ninterface DocumentSection {\n level: number;\n path: string[]; // Full path including parent headings\n content: {\n type: SectionContentType;\n text: string;\n }[];\n}\n\n/**\n * Splits markdown documents into semantic chunks while preserving\n * structure and distinguishing between different content types.\n *\n * The splitting process happens in two steps:\n * 1. Split document into sections based on headings (H1-H3 only)\n * 2. Split section content into smaller chunks based on preferredChunkSize\n */\nexport class SemanticMarkdownSplitter implements DocumentSplitter {\n private turndownService: TurndownService;\n public textSplitter: TextContentSplitter;\n public codeSplitter: CodeContentSplitter;\n public tableSplitter: TableContentSplitter;\n\n constructor(\n private preferredChunkSize: number,\n private maxChunkSize: number,\n ) {\n this.turndownService = new TurndownService({\n headingStyle: \"atx\",\n hr: \"---\",\n bulletListMarker: \"-\",\n codeBlockStyle: \"fenced\",\n emDelimiter: \"_\",\n strongDelimiter: \"**\",\n linkStyle: \"inlined\",\n });\n\n // Add table rule to preserve markdown table format\n this.turndownService.addRule(\"table\", {\n filter: [\"table\"],\n replacement: (content, node) => {\n const table = node as HTMLTableElement;\n const headers = Array.from(table.querySelectorAll(\"th\")).map(\n (th) => th.textContent?.trim() || \"\",\n );\n const rows = Array.from(table.querySelectorAll(\"tr\")).filter(\n (tr) => !tr.querySelector(\"th\"),\n );\n\n if (headers.length === 0 && rows.length === 0) return \"\";\n\n let markdown = \"\\n\";\n if (headers.length > 0) {\n markdown += `| ${headers.join(\" | \")} |\\n`;\n markdown += `|${headers.map(() => \"---\").join(\"|\")}|\\n`;\n }\n\n for (const row of rows) {\n const cells = Array.from(row.querySelectorAll(\"td\")).map(\n (td) => td.textContent?.trim() || \"\",\n );\n markdown += `| ${cells.join(\" | \")} |\\n`;\n }\n\n return markdown;\n },\n });\n\n // Text splitter uses preferred chunk size (keeps paragraphs together if possible)\n this.textSplitter = new TextContentSplitter({\n chunkSize: this.preferredChunkSize,\n });\n // Code/table splitters use the hard chunk size (avoid splitting unless necessary)\n this.codeSplitter = new CodeContentSplitter({\n chunkSize: this.maxChunkSize,\n });\n this.tableSplitter = new TableContentSplitter({\n chunkSize: this.maxChunkSize,\n });\n }\n\n /**\n * Main entry point for splitting markdown content\n */\n async splitText(markdown: string): Promise<ContentChunk[]> {\n const html = await this.markdownToHtml(markdown);\n const dom = await this.parseHtml(html);\n const sections = await this.splitIntoSections(dom);\n return this.splitSectionContent(sections);\n }\n\n /**\n * Step 1: Split document into sections based on H1-H6 headings,\n * as well as code blocks and tables.\n */\n private async splitIntoSections(dom: Document): Promise<DocumentSection[]> {\n const body = dom.querySelector(\"body\");\n if (!body) {\n throw new Error(\"Invalid HTML structure: no body element found\");\n }\n\n let currentSection = this.createRootSection();\n const sections: DocumentSection[] = [];\n const stack: DocumentSection[] = [currentSection];\n\n // Process each child of the body\n for (const element of Array.from(body.children)) {\n const headingMatch = element.tagName.match(/H([1-6])/);\n\n if (headingMatch) {\n // Create new section for H1-H6 heading\n const level = Number.parseInt(headingMatch[1], 10);\n const title = fullTrim(element.textContent || \"\");\n\n // Pop sections from stack until we find the parent level\n while (stack.length > 1 && stack[stack.length - 1].level >= level) {\n stack.pop();\n }\n\n // Start new section with the header\n currentSection = {\n level,\n path: [\n ...stack.slice(1).reduce((acc: string[], s) => {\n const lastPath = s.path[s.path.length - 1];\n if (lastPath) acc.push(lastPath);\n return acc;\n }, []),\n title,\n ],\n content: [\n {\n type: \"heading\",\n text: `${\"#\".repeat(level)} ${title}`,\n },\n ],\n };\n\n sections.push(currentSection);\n stack.push(currentSection);\n } else if (element.tagName === \"PRE\") {\n // Code blocks are kept as separate chunks\n const code = element.querySelector(\"code\");\n const language = code?.className.replace(\"language-\", \"\") || \"\";\n const content = code?.textContent || element.textContent || \"\";\n const markdown = `${\"```\"}${language}\\n${content}\\n${\"```\"}`;\n\n currentSection = {\n level: currentSection.level,\n path: currentSection.path,\n content: [\n {\n type: \"code\",\n text: markdown,\n },\n ],\n } satisfies DocumentSection;\n sections.push(currentSection);\n } else if (element.tagName === \"TABLE\") {\n // Tables are kept as separate chunks\n const markdown = fullTrim(this.turndownService.turndown(element.outerHTML));\n\n currentSection = {\n level: currentSection.level,\n path: currentSection.path,\n content: [\n {\n type: \"table\",\n text: markdown,\n },\n ],\n } satisfies DocumentSection;\n sections.push(currentSection);\n } else {\n const markdown = fullTrim(this.turndownService.turndown(element.innerHTML));\n if (markdown) {\n // Create a new section for the text content\n currentSection = {\n level: currentSection.level,\n path: currentSection.path,\n content: [\n {\n type: \"text\",\n text: markdown,\n },\n ],\n } satisfies DocumentSection;\n sections.push(currentSection);\n }\n }\n }\n\n return sections;\n }\n\n /**\n * Step 2: Split section content into smaller chunks\n */\n private async splitSectionContent(\n sections: DocumentSection[],\n ): Promise<ContentChunk[]> {\n const chunks: ContentChunk[] = [];\n\n for (const section of sections) {\n for (const content of section.content) {\n let splitContent: string[] = [];\n\n try {\n switch (content.type) {\n case \"heading\":\n case \"text\": {\n splitContent = await this.textSplitter.split(content.text);\n break;\n }\n case \"code\": {\n splitContent = await this.codeSplitter.split(content.text);\n break;\n }\n case \"table\": {\n splitContent = await this.tableSplitter.split(content.text);\n break;\n }\n }\n } catch (err) {\n // If it's a MinimumChunkSizeError, use RecursiveCharacterTextSplitter directly\n if (err instanceof MinimumChunkSizeError) {\n logger.warn(\n `⚠ Cannot split ${content.type} chunk normally, using RecursiveCharacterTextSplitter: ${err.message}`,\n );\n\n // Create a RecursiveCharacterTextSplitter with aggressive settings to ensure splitting\n const splitter = new RecursiveCharacterTextSplitter({\n chunkSize: this.maxChunkSize,\n chunkOverlap: Math.min(20, Math.floor(this.maxChunkSize * 0.1)),\n // Use more aggressive separators including empty string as last resort\n separators: [\n \"\\n\\n\",\n \"\\n\",\n \" \",\n \"\\t\",\n \".\",\n \",\",\n \";\",\n \":\",\n \"-\",\n \"(\",\n \")\",\n \"[\",\n \"]\",\n \"{\",\n \"}\",\n \"\",\n ],\n });\n\n const chunks = await splitter.splitText(content.text);\n if (chunks.length === 0) {\n // If still no chunks, use the most extreme approach: just truncate\n splitContent = [content.text.substring(0, this.maxChunkSize)];\n } else {\n splitContent = chunks;\n }\n } else {\n // Convert other error message to string, handling non-Error objects\n const errMessage = err instanceof Error ? err.message : String(err);\n throw new ContentSplitterError(\n `Failed to split ${content.type} content: ${errMessage}`,\n );\n }\n }\n\n // Create chunks from split content\n chunks.push(\n ...splitContent.map(\n (text): ContentChunk => ({\n types: [content.type],\n content: text,\n section: {\n level: section.level,\n path: section.path,\n },\n }),\n ),\n );\n }\n }\n\n return chunks;\n }\n\n /**\n * Helper to create the root section\n */\n private createRootSection(): DocumentSection {\n return {\n level: 0,\n path: [],\n content: [],\n };\n }\n\n /**\n * Convert markdown to HTML using remark\n */\n private async markdownToHtml(markdown: string): Promise<string> {\n const html = await unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkHtml)\n .process(markdown);\n\n return `<!DOCTYPE html>\n <html>\n <body>\n ${String(html)}\n </body>\n </html>`;\n }\n\n /**\n * Parse HTML\n */\n private async parseHtml(html: string): Promise<Document> {\n // Use createJSDOM which includes default options like virtualConsole\n const { window } = createJSDOM(html);\n return window.document;\n }\n}\n","import type { ContentChunk, DocumentSplitter, SectionContentType } from \"./types\";\n\n/**\n * Takes small document chunks and greedily concatenates them into larger, more meaningful units\n * while preserving document structure and semantic boundaries.\n *\n * This approach improves embedding quality by:\n * - Maintaining context by keeping related content together\n * - Respecting natural document breaks at major section boundaries (H1/H2)\n * - Ensuring chunks are large enough to capture meaningful relationships\n * - Preventing chunks from becoming too large for effective embedding\n */\nexport class GreedySplitter implements DocumentSplitter {\n private baseSplitter: DocumentSplitter;\n private minChunkSize: number;\n private preferredChunkSize: number;\n\n /**\n * Combines a base document splitter with size constraints to produce optimally-sized chunks.\n * The base splitter handles the initial semantic splitting, while this class handles\n * the concatenation strategy.\n */\n constructor(\n baseSplitter: DocumentSplitter,\n minChunkSize: number,\n preferredChunkSize: number,\n ) {\n this.baseSplitter = baseSplitter;\n this.minChunkSize = minChunkSize;\n this.preferredChunkSize = preferredChunkSize;\n }\n\n /**\n * Uses a greedy concatenation strategy to build optimally-sized chunks. Small chunks\n * are combined until they reach the minimum size, but splits are preserved at major\n * section boundaries to maintain document structure. This balances the need for\n * context with semantic coherence.\n */\n async splitText(markdown: string): Promise<ContentChunk[]> {\n const initialChunks = await this.baseSplitter.splitText(markdown);\n const concatenatedChunks: ContentChunk[] = [];\n let currentChunk: ContentChunk | null = null;\n\n for (const nextChunk of initialChunks) {\n if (currentChunk) {\n if (this.wouldExceedMaxSize(currentChunk, nextChunk)) {\n concatenatedChunks.push(currentChunk);\n currentChunk = this.cloneChunk(nextChunk);\n continue;\n }\n if (\n currentChunk.content.length >= this.minChunkSize &&\n this.startsNewMajorSection(nextChunk)\n ) {\n concatenatedChunks.push(currentChunk);\n currentChunk = this.cloneChunk(nextChunk);\n continue;\n }\n currentChunk.content += `\\n${nextChunk.content}`;\n currentChunk.section = this.mergeSectionInfo(currentChunk, nextChunk);\n currentChunk.types = this.mergeTypes(currentChunk.types, nextChunk.types);\n } else {\n currentChunk = this.cloneChunk(nextChunk);\n }\n }\n\n if (currentChunk) {\n concatenatedChunks.push(currentChunk);\n }\n\n return concatenatedChunks;\n }\n\n private cloneChunk(chunk: ContentChunk): ContentChunk {\n return {\n types: [...chunk.types],\n content: chunk.content,\n section: {\n level: chunk.section.level,\n path: [...chunk.section.path],\n },\n };\n }\n\n /**\n * H1 and H2 headings represent major conceptual breaks in the document.\n * Preserving these splits helps maintain the document's logical structure.\n */\n private startsNewMajorSection(chunk: ContentChunk): boolean {\n return chunk.section.level === 1 || chunk.section.level === 2;\n }\n\n /**\n * Size limit check to ensure chunks remain within embedding model constraints.\n * Essential for maintaining consistent embedding quality and avoiding truncation.\n */\n private wouldExceedMaxSize(\n currentChunk: ContentChunk | null,\n nextChunk: ContentChunk,\n ): boolean {\n if (!currentChunk) {\n return false;\n }\n return (\n currentChunk.content.length + nextChunk.content.length > this.preferredChunkSize\n );\n }\n\n /**\n * Checks if one path is a prefix of another path, indicating a parent-child relationship\n */\n private isPathIncluded(parentPath: string[], childPath: string[]): boolean {\n if (parentPath.length >= childPath.length) return false;\n return parentPath.every((part, i) => part === childPath[i]);\n }\n\n /**\n * Merges section metadata when concatenating chunks, following these rules:\n * 1. Level: Always uses the lowest (most general) level between chunks\n * 2. Path selection:\n * - For parent-child relationships (one path includes the other), uses the child's path\n * - For siblings/unrelated sections, uses the common parent path\n * - If no common path exists, uses the root path ([])\n */\n private mergeSectionInfo(\n currentChunk: ContentChunk,\n nextChunk: ContentChunk,\n ): ContentChunk[\"section\"] {\n // Always use the lowest level\n const level = Math.min(currentChunk.section.level, nextChunk.section.level);\n\n // If sections are exactly equal, preserve all metadata\n if (\n currentChunk.section.level === nextChunk.section.level &&\n currentChunk.section.path.length === nextChunk.section.path.length &&\n currentChunk.section.path.every((p, i) => p === nextChunk.section.path[i])\n ) {\n return currentChunk.section;\n }\n\n // Check if one path includes the other\n if (this.isPathIncluded(currentChunk.section.path, nextChunk.section.path)) {\n return {\n path: nextChunk.section.path,\n level,\n };\n }\n\n if (this.isPathIncluded(nextChunk.section.path, currentChunk.section.path)) {\n return {\n path: currentChunk.section.path,\n level,\n };\n }\n\n // Find common parent path\n const commonPath = this.findCommonPrefix(\n currentChunk.section.path,\n nextChunk.section.path,\n );\n\n return {\n path: commonPath,\n level,\n };\n }\n\n private mergeTypes(\n currentTypes: SectionContentType[],\n nextTypes: SectionContentType[],\n ): SectionContentType[] {\n return [...new Set([...currentTypes, ...nextTypes])];\n }\n\n /**\n * Returns longest common prefix between two paths\n */\n private findCommonPrefix(path1: string[], path2: string[]): string[] {\n const common: string[] = [];\n for (let i = 0; i < Math.min(path1.length, path2.length); i++) {\n if (path1[i] === path2[i]) {\n common.push(path1[i]);\n } else {\n break;\n }\n }\n return common;\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nlet projectRoot: string | null = null;\n\n/**\n * Finds the project root directory by searching upwards from the current file\n * for a directory containing 'package.json'. Caches the result.\n *\n * @returns {string} The absolute path to the project root.\n * @throws {Error} If package.json cannot be found.\n */\nexport function getProjectRoot(): string {\n // Return cached result if available\n if (projectRoot) {\n return projectRoot;\n }\n\n // Start from the directory of the current module\n const currentFilePath = fileURLToPath(import.meta.url);\n let currentDir = path.dirname(currentFilePath);\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const packageJsonPath = path.join(currentDir, \"package.json\");\n if (fs.existsSync(packageJsonPath)) {\n projectRoot = currentDir; // Cache the result\n return projectRoot;\n }\n\n const parentDir = path.dirname(currentDir);\n // Check if we have reached the filesystem root\n if (parentDir === currentDir) {\n throw new Error(\"Could not find project root containing package.json.\");\n }\n currentDir = parentDir;\n }\n}\n","import type { Document } from \"@langchain/core/documents\";\nimport type { DocumentStore } from \"./DocumentStore\";\nimport type { StoreSearchResult } from \"./types\";\n\nconst CHILD_LIMIT = 5;\nconst SIBLING_LIMIT = 2;\n\nexport class DocumentRetrieverService {\n private documentStore: DocumentStore;\n\n constructor(documentStore: DocumentStore) {\n this.documentStore = documentStore;\n }\n\n /**\n * Collects all related chunk IDs for a given initial hit.\n * Returns an object with url, hitId, relatedIds (Set), and score.\n */\n private async getRelatedChunkIds(\n library: string,\n version: string,\n doc: Document,\n siblingLimit = SIBLING_LIMIT,\n childLimit = CHILD_LIMIT,\n ): Promise<{\n url: string;\n hitId: string;\n relatedIds: Set<string>;\n score: number;\n }> {\n const id = doc.id as string;\n const url = doc.metadata.url as string;\n const score = doc.metadata.score as number;\n const relatedIds = new Set<string>();\n relatedIds.add(id);\n\n // Parent\n const parent = await this.documentStore.findParentChunk(library, version, id);\n if (parent) {\n relatedIds.add(parent.id as string);\n }\n\n // Preceding Siblings\n const precedingSiblings = await this.documentStore.findPrecedingSiblingChunks(\n library,\n version,\n id,\n siblingLimit,\n );\n for (const sib of precedingSiblings) {\n relatedIds.add(sib.id as string);\n }\n\n // Child Chunks\n const childChunks = await this.documentStore.findChildChunks(\n library,\n version,\n id,\n childLimit,\n );\n for (const child of childChunks) {\n relatedIds.add(child.id as string);\n }\n\n // Subsequent Siblings\n const subsequentSiblings = await this.documentStore.findSubsequentSiblingChunks(\n library,\n version,\n id,\n siblingLimit,\n );\n for (const sib of subsequentSiblings) {\n relatedIds.add(sib.id as string);\n }\n\n return { url, hitId: id, relatedIds, score };\n }\n\n /**\n * Groups related chunk info by URL, deduplicates IDs, and finds max score per URL.\n */\n private groupAndPrepareFetch(\n relatedInfos: Array<{\n url: string;\n hitId: string;\n relatedIds: Set<string>;\n score: number;\n }>,\n ): Map<string, { uniqueChunkIds: Set<string>; maxScore: number }> {\n const urlMap = new Map<string, { uniqueChunkIds: Set<string>; maxScore: number }>();\n for (const info of relatedInfos) {\n let entry = urlMap.get(info.url);\n if (!entry) {\n entry = { uniqueChunkIds: new Set(), maxScore: info.score };\n urlMap.set(info.url, entry);\n }\n for (const id of info.relatedIds) {\n entry.uniqueChunkIds.add(id);\n }\n if (info.score > entry.maxScore) {\n entry.maxScore = info.score;\n }\n }\n return urlMap;\n }\n\n /**\n * Finalizes the merged result for a URL group by fetching, sorting, and joining content.\n */\n private async finalizeResult(\n library: string,\n version: string,\n url: string,\n uniqueChunkIds: Set<string>,\n maxScore: number,\n ): Promise<StoreSearchResult> {\n const ids = Array.from(uniqueChunkIds);\n const docs = await this.documentStore.findChunksByIds(library, version, ids);\n // Already sorted by sort_order in findChunksByIds\n const content = docs.map((d) => d.pageContent).join(\"\\n\\n\");\n // TODO: Apply code block merging here if/when implemented\n return {\n url,\n content,\n score: maxScore,\n };\n }\n\n /**\n * Searches for documents and expands the context around the matches.\n * @param library The library name.\n * @param version The library version.\n * @param query The search query.\n * @param version The library version (optional, defaults to searching documents without a version).\n * @param query The search query.\n * @param limit The optional limit for the initial search results.\n * @returns An array of strings representing the aggregated content of the retrieved chunks.\n */\n async search(\n library: string,\n version: string | null | undefined,\n query: string,\n limit?: number,\n ): Promise<StoreSearchResult[]> {\n // Normalize version: null/undefined becomes empty string, then lowercase\n const normalizedVersion = (version ?? \"\").toLowerCase();\n\n const initialResults = await this.documentStore.findByContent(\n library,\n normalizedVersion,\n query,\n limit ?? 10,\n );\n\n // Step 1: Expand context for each initial hit (collect related chunk IDs)\n const relatedInfos = await Promise.all(\n initialResults.map((doc) =>\n this.getRelatedChunkIds(library, normalizedVersion, doc),\n ),\n );\n\n // Step 2: Group by URL, deduplicate, and find max score\n const urlMap = this.groupAndPrepareFetch(relatedInfos);\n\n // Step 3: For each URL group, fetch, sort, and format the merged result\n const results: StoreSearchResult[] = [];\n for (const [url, { uniqueChunkIds, maxScore }] of urlMap.entries()) {\n const result = await this.finalizeResult(\n library,\n normalizedVersion,\n url,\n uniqueChunkIds,\n maxScore,\n );\n results.push(result);\n }\n\n return results;\n }\n}\n","class StoreError extends Error {\n constructor(\n message: string,\n public readonly cause?: unknown,\n ) {\n super(cause ? `${message} caused by ${cause}` : message);\n this.name = this.constructor.name;\n\n const causeError =\n cause instanceof Error ? cause : cause ? new Error(String(cause)) : undefined;\n if (causeError?.stack) {\n this.stack = causeError.stack;\n }\n }\n}\n\nclass DimensionError extends StoreError {\n constructor(\n public readonly modelName: string,\n public readonly modelDimension: number,\n public readonly dbDimension: number,\n ) {\n super(\n `Model \"${modelName}\" produces ${modelDimension}-dimensional vectors, ` +\n `which exceeds the database's fixed dimension of ${dbDimension}. ` +\n `Please use a model with dimension ≤ ${dbDimension}.`,\n );\n }\n}\n\nclass ConnectionError extends StoreError {}\n\nclass DocumentNotFoundError extends StoreError {\n constructor(public readonly id: string) {\n super(`Document ${id} not found`);\n }\n}\n\nexport { StoreError, ConnectionError, DocumentNotFoundError, DimensionError };\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Database } from \"better-sqlite3\";\nimport { MIGRATION_MAX_RETRIES, MIGRATION_RETRY_DELAY_MS } from \"../utils/config\";\nimport { logger } from \"../utils/logger\";\nimport { getProjectRoot } from \"../utils/paths\";\nimport { StoreError } from \"./errors\";\n\n// Construct the absolute path to the migrations directory using the project root\nconst MIGRATIONS_DIR = path.join(getProjectRoot(), \"db\", \"migrations\");\nconst MIGRATIONS_TABLE = \"_schema_migrations\";\n\n/**\n * Ensures the migration tracking table exists in the database.\n * @param db The database instance.\n */\nfunction ensureMigrationsTable(db: Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (\n id TEXT PRIMARY KEY,\n applied_at DATETIME DEFAULT CURRENT_TIMESTAMP\n );\n `);\n}\n\n/**\n * Retrieves the set of already applied migration IDs (filenames) from the tracking table.\n * @param db The database instance.\n * @returns A Set containing the IDs of applied migrations.\n */\nfunction getAppliedMigrations(db: Database): Set<string> {\n const stmt = db.prepare(`SELECT id FROM ${MIGRATIONS_TABLE}`);\n const rows = stmt.all() as Array<{ id: string }>;\n return new Set(rows.map((row) => row.id));\n}\n\n/**\n * Applies pending database migrations found in the migrations directory.\n * Migrations are expected to be .sql files with sequential prefixes (e.g., 001-, 002-).\n * It tracks applied migrations in the _schema_migrations table.\n *\n * @param db The better-sqlite3 database instance.\n * @throws {StoreError} If any migration fails.\n */\nexport async function applyMigrations(db: Database): Promise<void> {\n const overallTransaction = db.transaction(() => {\n logger.debug(\"Applying database migrations...\");\n ensureMigrationsTable(db);\n const appliedMigrations = getAppliedMigrations(db);\n\n if (!fs.existsSync(MIGRATIONS_DIR)) {\n throw new StoreError(\"Migrations directory not found\");\n }\n\n const migrationFiles = fs\n .readdirSync(MIGRATIONS_DIR)\n .filter((file) => file.endsWith(\".sql\"))\n .sort(); // Sort alphabetically, relying on naming convention (001-, 002-)\n\n let appliedCount = 0;\n for (const filename of migrationFiles) {\n if (!appliedMigrations.has(filename)) {\n logger.debug(`Applying migration: ${filename}`);\n const filePath = path.join(MIGRATIONS_DIR, filename);\n const sql = fs.readFileSync(filePath, \"utf8\");\n\n // Execute migration and record it directly within the overall transaction\n try {\n db.exec(sql);\n const insertStmt = db.prepare(\n `INSERT INTO ${MIGRATIONS_TABLE} (id) VALUES (?)`,\n );\n insertStmt.run(filename);\n logger.debug(`Successfully applied migration: ${filename}`);\n appliedCount++;\n } catch (error) {\n logger.error(`❌ Failed to apply migration: ${filename} - ${error}`);\n // Re-throw to ensure the overall transaction rolls back\n throw new StoreError(`Migration failed: ${filename}`, error);\n }\n }\n }\n\n if (appliedCount > 0) {\n logger.debug(`Applied ${appliedCount} new migration(s).`);\n } else {\n logger.debug(\"Database schema is up to date.\");\n }\n });\n\n let retries = 0;\n\n while (true) {\n try {\n // Start a single IMMEDIATE transaction for the entire migration process\n overallTransaction.immediate(); // Execute the encompassing transaction\n logger.debug(\n \"Migrations applied successfully or schema up to date after potential retries.\",\n );\n break; // Success\n } catch (error) {\n // biome-ignore lint/suspicious/noExplicitAny: error can be any\n if ((error as any)?.code === \"SQLITE_BUSY\" && retries < MIGRATION_MAX_RETRIES) {\n retries++;\n logger.warn(\n `⚠️ Migrations busy (SQLITE_BUSY), retrying attempt ${retries}/${MIGRATION_MAX_RETRIES} in ${MIGRATION_RETRY_DELAY_MS}ms...`,\n );\n await new Promise((resolve) => setTimeout(resolve, MIGRATION_RETRY_DELAY_MS));\n } else {\n // biome-ignore lint/suspicious/noExplicitAny: error can be any\n if ((error as any)?.code === \"SQLITE_BUSY\") {\n logger.error(\n `❌ Migrations still busy after ${MIGRATION_MAX_RETRIES} retries. Giving up: ${error}`,\n );\n }\n // Ensure StoreError is thrown for consistent handling\n if (error instanceof StoreError) {\n throw error;\n }\n throw new StoreError(\"Failed during migration process\", error);\n }\n }\n }\n}\n","import type { DocumentMetadata } from \"../types\";\n\n/** Default vector dimension used across the application */\nexport const VECTOR_DIMENSION = 1536;\n\n/**\n * Database document record type matching the documents table schema\n */\nexport interface DbDocument {\n id: string;\n library: string;\n version: string;\n url: string;\n content: string;\n metadata: string; // JSON string of DocumentMetadata\n embedding: string | null; // JSON string of number[]\n sort_order: number;\n score: number | null;\n}\n\n/**\n * Utility type for handling SQLite query results that may be undefined\n */\nexport type DbQueryResult<T> = T | undefined;\n\n/**\n * Maps raw database document to the Document type used by the application\n */\nexport function mapDbDocumentToDocument(doc: DbDocument) {\n return {\n id: doc.id,\n pageContent: doc.content,\n metadata: JSON.parse(doc.metadata) as DocumentMetadata,\n };\n}\n\n/**\n * Search result type returned by the DocumentRetrieverService\n */\nexport interface StoreSearchResult {\n url: string;\n content: string;\n score: number | null;\n}\n\n/**\n * Represents a library and its indexed versions.\n */\nexport interface LibraryVersion {\n version: string;\n}\n\n/**\n * Detailed information about a specific indexed library version.\n */\nexport interface LibraryVersionDetails {\n version: string;\n documentCount: number;\n uniqueUrlCount: number;\n indexedAt: string | null; // ISO 8601 format from MIN(indexed_at)\n}\n\n/**\n * Result type for findBestVersion, indicating the best semver match\n * and whether unversioned documents exist.\n */\nexport interface FindVersionResult {\n bestMatch: string | null;\n hasUnversioned: boolean;\n}\n","import type { Document } from \"@langchain/core/documents\";\nimport type { Embeddings } from \"@langchain/core/embeddings\";\nimport Database, { type Database as DatabaseType } from \"better-sqlite3\";\nimport semver from \"semver\";\nimport * as sqliteVec from \"sqlite-vec\";\nimport type { DocumentMetadata } from \"../types\";\nimport { EMBEDDING_BATCH_SIZE } from \"../utils/config\";\nimport { applyMigrations } from \"./applyMigrations\";\nimport { ConnectionError, DimensionError, StoreError } from \"./errors\";\nimport { VECTOR_DIMENSION } from \"./types\";\nimport {\n type DbDocument,\n type DbQueryResult,\n type LibraryVersionDetails,\n mapDbDocumentToDocument,\n} from \"./types\";\n\ninterface RawSearchResult extends DbDocument {\n vec_score?: number;\n fts_score?: number;\n}\n\ninterface RankedResult extends RawSearchResult {\n vec_rank?: number;\n fts_rank?: number;\n rrf_score: number;\n}\n\n/**\n * Manages document storage and retrieval using SQLite with vector and full-text search capabilities.\n * Provides direct access to SQLite with prepared statements to store and query document\n * embeddings along with their metadata. Supports versioned storage of documents for different\n * libraries, enabling version-specific document retrieval and searches.\n */\nexport class DocumentStore {\n private readonly db: DatabaseType;\n private embeddings!: Embeddings;\n private readonly dbDimension: number = VECTOR_DIMENSION;\n private modelDimension!: number;\n private statements!: {\n getById: Database.Statement;\n insertDocument: Database.Statement;\n insertEmbedding: Database.Statement;\n deleteDocuments: Database.Statement;\n queryVersions: Database.Statement;\n checkExists: Database.Statement;\n queryLibraryVersions: Database.Statement<[]>; // Updated type\n getChildChunks: Database.Statement;\n getPrecedingSiblings: Database.Statement;\n getSubsequentSiblings: Database.Statement;\n getParentChunk: Database.Statement;\n };\n\n /**\n * Calculates Reciprocal Rank Fusion score for a result\n */\n private calculateRRF(vecRank?: number, ftsRank?: number, k = 60): number {\n let rrf = 0;\n if (vecRank !== undefined) {\n rrf += 1 / (k + vecRank);\n }\n if (ftsRank !== undefined) {\n rrf += 1 / (k + ftsRank);\n }\n return rrf;\n }\n\n /**\n * Assigns ranks to search results based on their scores\n */\n private assignRanks(results: RawSearchResult[]): RankedResult[] {\n // Create maps to store ranks\n const vecRanks = new Map<number, number>();\n const ftsRanks = new Map<number, number>();\n\n // Sort by vector scores and assign ranks\n results\n .filter((r) => r.vec_score !== undefined)\n .sort((a, b) => (a.vec_score ?? 0) - (b.vec_score ?? 0))\n .forEach((result, index) => {\n vecRanks.set(Number(result.id), index + 1);\n });\n\n // Sort by BM25 scores and assign ranks\n results\n .filter((r) => r.fts_score !== undefined)\n .sort((a, b) => (a.fts_score ?? 0) - (b.fts_score ?? 0))\n .forEach((result, index) => {\n ftsRanks.set(Number(result.id), index + 1);\n });\n\n // Combine results with ranks and calculate RRF\n return results.map((result) => ({\n ...result,\n vec_rank: vecRanks.get(Number(result.id)),\n fts_rank: ftsRanks.get(Number(result.id)),\n rrf_score: this.calculateRRF(\n vecRanks.get(Number(result.id)),\n ftsRanks.get(Number(result.id)),\n ),\n }));\n }\n\n constructor(dbPath: string) {\n if (!dbPath) {\n throw new StoreError(\"Missing required database path\");\n }\n\n // Only establish database connection in constructor\n this.db = new Database(dbPath);\n }\n\n /**\n * Sets up prepared statements for database queries\n */\n private prepareStatements(): void {\n const statements = {\n getById: this.db.prepare(\"SELECT * FROM documents WHERE id = ?\"),\n insertDocument: this.db.prepare(\n \"INSERT INTO documents (library, version, url, content, metadata, sort_order, indexed_at) VALUES (?, ?, ?, ?, ?, ?, ?)\", // Added indexed_at\n ),\n insertEmbedding: this.db.prepare<[number, string]>(\n \"INSERT INTO documents_vec (rowid, library, version, embedding) VALUES (?, ?, ?, ?)\",\n ),\n deleteDocuments: this.db.prepare(\n \"DELETE FROM documents WHERE library = ? AND version = ?\",\n ),\n queryVersions: this.db.prepare(\n \"SELECT DISTINCT version FROM documents WHERE library = ? ORDER BY version\",\n ),\n checkExists: this.db.prepare(\n \"SELECT id FROM documents WHERE library = ? AND version = ? LIMIT 1\",\n ),\n queryLibraryVersions: this.db.prepare(\n `SELECT\n library,\n version,\n COUNT(*) as documentCount,\n COUNT(DISTINCT url) as uniqueUrlCount,\n MIN(indexed_at) as indexedAt\n FROM documents\n GROUP BY library, version\n ORDER BY library, version`,\n ),\n getChildChunks: this.db.prepare(`\n SELECT * FROM documents\n WHERE library = ? \n AND version = ? \n AND url = ?\n AND json_array_length(json_extract(metadata, '$.path')) = ?\n AND json_extract(metadata, '$.path') LIKE ? || '%'\n AND sort_order > (SELECT sort_order FROM documents WHERE id = ?)\n ORDER BY sort_order\n LIMIT ?\n `),\n getPrecedingSiblings: this.db.prepare(`\n SELECT * FROM documents \n WHERE library = ? \n AND version = ? \n AND url = ?\n AND sort_order < (SELECT sort_order FROM documents WHERE id = ?)\n AND json_extract(metadata, '$.path') = ?\n ORDER BY sort_order DESC\n LIMIT ?\n `),\n getSubsequentSiblings: this.db.prepare(`\n SELECT * FROM documents \n WHERE library = ? \n AND version = ? \n AND url = ?\n AND sort_order > (SELECT sort_order FROM documents WHERE id = ?)\n AND json_extract(metadata, '$.path') = ?\n ORDER BY sort_order\n LIMIT ?\n `),\n getParentChunk: this.db.prepare(`\n SELECT * FROM documents \n WHERE library = ? \n AND version = ? \n AND url = ?\n AND json_extract(metadata, '$.path') = ?\n AND sort_order < (SELECT sort_order FROM documents WHERE id = ?)\n ORDER BY sort_order DESC\n LIMIT 1\n `),\n };\n this.statements = statements;\n }\n\n /**\n * Pads a vector to the fixed database dimension by appending zeros.\n * Throws an error if the input vector is longer than the database dimension.\n */\n private padVector(vector: number[]): number[] {\n if (vector.length > this.dbDimension) {\n throw new Error(\n `Vector dimension ${vector.length} exceeds database dimension ${this.dbDimension}`,\n );\n }\n if (vector.length === this.dbDimension) {\n return vector;\n }\n return [...vector, ...new Array(this.dbDimension - vector.length).fill(0)];\n }\n\n /**\n * Initializes embeddings client using environment variables for configuration.\n *\n * The embedding model is configured using DOCS_MCP_EMBEDDING_MODEL environment variable.\n * Format: \"provider:model_name\" (e.g., \"google:text-embedding-004\") or just \"model_name\"\n * for OpenAI (default).\n *\n * Supported providers and their required environment variables:\n * - openai: OPENAI_API_KEY (and optionally OPENAI_API_BASE, OPENAI_ORG_ID)\n * - google: GOOGLE_APPLICATION_CREDENTIALS (path to service account JSON)\n * - aws: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION (or BEDROCK_AWS_REGION)\n * - microsoft: Azure OpenAI credentials (AZURE_OPENAI_API_*)\n */\n private async initializeEmbeddings(): Promise<void> {\n const modelSpec = process.env.DOCS_MCP_EMBEDDING_MODEL || \"text-embedding-3-small\";\n\n // Import dynamically to avoid circular dependencies\n const { createEmbeddingModel } = await import(\"./embeddings/EmbeddingFactory\");\n this.embeddings = createEmbeddingModel(modelSpec);\n\n // Determine the model's actual dimension by embedding a test string\n const testVector = await this.embeddings.embedQuery(\"test\");\n this.modelDimension = testVector.length;\n\n if (this.modelDimension > this.dbDimension) {\n throw new DimensionError(modelSpec, this.modelDimension, this.dbDimension);\n }\n }\n\n /**\n * Escapes a query string for use with SQLite FTS5 MATCH operator.\n * Wraps the query in double quotes and escapes internal double quotes.\n */\n private escapeFtsQuery(query: string): string {\n // Escape internal double quotes by doubling them\n const escapedQuotes = query.replace(/\"/g, '\"\"');\n // Wrap the entire string in double quotes\n return `\"${escapedQuotes}\"`;\n }\n\n /**\n * Initializes database connection and ensures readiness\n */\n async initialize(): Promise<void> {\n try {\n // 1. Load extensions first (moved before migrations)\n sqliteVec.load(this.db);\n\n // 2. Apply migrations (after extensions are loaded)\n applyMigrations(this.db);\n\n // 3. Initialize prepared statements\n this.prepareStatements();\n\n // 4. Initialize embeddings client (await to catch errors)\n await this.initializeEmbeddings();\n } catch (error) {\n // Re-throw StoreError directly, wrap others in ConnectionError\n if (error instanceof StoreError) {\n throw error;\n }\n throw new ConnectionError(\"Failed to initialize database connection\", error);\n }\n }\n\n /**\n * Gracefully closes database connections\n */\n async shutdown(): Promise<void> {\n this.db.close();\n }\n\n /**\n * Retrieves all unique versions for a specific library\n */\n async queryUniqueVersions(library: string): Promise<string[]> {\n try {\n const rows = this.statements.queryVersions.all(library.toLowerCase()) as Array<\n Pick<DbDocument, \"version\">\n >;\n return rows.map((row) => row.version);\n } catch (error) {\n throw new ConnectionError(\"Failed to query versions\", error);\n }\n }\n\n /**\n * Verifies existence of documents for a specific library version\n */\n async checkDocumentExists(library: string, version: string): Promise<boolean> {\n try {\n const result = this.statements.checkExists.get(\n library.toLowerCase(),\n version.toLowerCase(),\n );\n return result !== undefined;\n } catch (error) {\n throw new ConnectionError(\"Failed to check document existence\", error);\n }\n }\n\n /**\n * Retrieves a mapping of all libraries to their available versions with details.\n */\n async queryLibraryVersions(): Promise<Map<string, LibraryVersionDetails[]>> {\n try {\n // Define the expected row structure from the GROUP BY query\n interface LibraryVersionRow {\n library: string;\n version: string;\n documentCount: number;\n uniqueUrlCount: number;\n indexedAt: string | null; // SQLite MIN might return string or null\n }\n\n const rows = this.statements.queryLibraryVersions.all() as LibraryVersionRow[];\n const libraryMap = new Map<string, LibraryVersionDetails[]>();\n\n for (const row of rows) {\n // Process all rows, including those where version is \"\" (unversioned)\n const library = row.library;\n if (!libraryMap.has(library)) {\n libraryMap.set(library, []);\n }\n\n // Format indexedAt to ISO string if available\n const indexedAtISO = row.indexedAt ? new Date(row.indexedAt).toISOString() : null;\n\n libraryMap.get(library)?.push({\n version: row.version,\n documentCount: row.documentCount,\n uniqueUrlCount: row.uniqueUrlCount,\n indexedAt: indexedAtISO,\n });\n }\n\n // Sort versions within each library: unversioned first, then semantically\n for (const versions of libraryMap.values()) {\n versions.sort((a, b) => {\n if (a.version === \"\" && b.version !== \"\") {\n return -1; // a (unversioned) comes first\n }\n if (a.version !== \"\" && b.version === \"\") {\n return 1; // b (unversioned) comes first\n }\n if (a.version === \"\" && b.version === \"\") {\n return 0; // Should not happen with GROUP BY, but handle anyway\n }\n // Both are non-empty, use semver compare\n return semver.compare(a.version, b.version);\n });\n }\n\n return libraryMap;\n } catch (error) {\n throw new ConnectionError(\"Failed to query library versions\", error);\n }\n }\n\n /**\n * Stores documents with library and version metadata, generating embeddings\n * for vector similarity search\n */\n async addDocuments(\n library: string,\n version: string,\n documents: Document[],\n ): Promise<void> {\n try {\n // Generate embeddings in batch\n const texts = documents.map((doc) => {\n const header = `<title>${doc.metadata.title}</title>\\n<url>${doc.metadata.url}</url>\\n<path>${doc.metadata.path.join(\" / \")}</path>\\n`;\n return `${header}${doc.pageContent}`;\n });\n\n // Batch embedding creation to avoid token limit errors\n const rawEmbeddings: number[][] = [];\n for (let i = 0; i < texts.length; i += EMBEDDING_BATCH_SIZE) {\n const batchTexts = texts.slice(i, i + EMBEDDING_BATCH_SIZE);\n const batchEmbeddings = await this.embeddings.embedDocuments(batchTexts);\n rawEmbeddings.push(...batchEmbeddings);\n }\n const paddedEmbeddings = rawEmbeddings.map((vector) => this.padVector(vector));\n\n // Insert documents in a transaction\n const transaction = this.db.transaction((docs: typeof documents) => {\n for (let i = 0; i < docs.length; i++) {\n const doc = docs[i];\n const url = doc.metadata.url as string;\n if (!url || typeof url !== \"string\" || !url.trim()) {\n throw new StoreError(\"Document metadata must include a valid URL\");\n }\n\n // Insert into main documents table\n const result = this.statements.insertDocument.run(\n library.toLowerCase(),\n version.toLowerCase(),\n url,\n doc.pageContent,\n JSON.stringify(doc.metadata),\n i,\n new Date().toISOString(), // Pass current timestamp for indexed_at\n );\n const rowId = result.lastInsertRowid;\n\n // Insert into vector table\n this.statements.insertEmbedding.run(\n BigInt(rowId),\n library.toLowerCase(),\n version.toLowerCase(),\n JSON.stringify(paddedEmbeddings[i]),\n );\n }\n });\n\n transaction(documents);\n } catch (error) {\n throw new ConnectionError(\"Failed to add documents to store\", error);\n }\n }\n\n /**\n * Removes documents matching specified library and version\n * @returns Number of documents deleted\n */\n async deleteDocuments(library: string, version: string): Promise<number> {\n try {\n const result = this.statements.deleteDocuments.run(\n library.toLowerCase(),\n version.toLowerCase(),\n );\n return result.changes;\n } catch (error) {\n throw new ConnectionError(\"Failed to delete documents\", error);\n }\n }\n\n /**\n * Retrieves a document by its ID.\n * @param id The ID of the document.\n * @returns The document, or null if not found.\n */\n async getById(id: string): Promise<Document | null> {\n try {\n const row = this.statements.getById.get(id) as DbQueryResult<DbDocument>;\n if (!row) {\n return null;\n }\n\n return mapDbDocumentToDocument(row);\n } catch (error) {\n throw new ConnectionError(`Failed to get document by ID ${id}`, error);\n }\n }\n\n /**\n * Finds documents matching a text query using hybrid search.\n * Combines vector similarity search with full-text search using Reciprocal Rank Fusion.\n */\n async findByContent(\n library: string,\n version: string,\n query: string,\n limit: number,\n ): Promise<Document[]> {\n try {\n const rawEmbedding = await this.embeddings.embedQuery(query);\n const embedding = this.padVector(rawEmbedding);\n const ftsQuery = this.escapeFtsQuery(query); // Escape the query for FTS\n\n const stmt = this.db.prepare(`\n WITH vec_scores AS (\n SELECT\n rowid as id,\n distance as vec_score\n FROM documents_vec\n WHERE library = ?\n AND version = ?\n AND embedding MATCH ?\n ORDER BY vec_score\n LIMIT ?\n ),\n fts_scores AS (\n SELECT\n f.rowid as id,\n bm25(documents_fts, 10.0, 1.0, 5.0, 1.0) as fts_score\n FROM documents_fts f\n JOIN documents d ON f.rowid = d.rowid\n WHERE d.library = ?\n AND d.version = ?\n AND documents_fts MATCH ?\n ORDER BY fts_score\n LIMIT ?\n )\n SELECT\n d.id,\n d.content,\n d.metadata,\n COALESCE(1 / (1 + v.vec_score), 0) as vec_score,\n COALESCE(1 / (1 + f.fts_score), 0) as fts_score\n FROM documents d\n LEFT JOIN vec_scores v ON d.id = v.id\n LEFT JOIN fts_scores f ON d.id = f.id\n WHERE v.id IS NOT NULL OR f.id IS NOT NULL\n `);\n\n const rawResults = stmt.all(\n library.toLowerCase(),\n version.toLowerCase(),\n JSON.stringify(embedding),\n limit,\n library.toLowerCase(),\n version.toLowerCase(),\n ftsQuery, // Use the escaped query\n limit,\n ) as RawSearchResult[];\n\n // Apply RRF ranking\n const rankedResults = this.assignRanks(rawResults);\n\n // Sort by RRF score and take top results\n const topResults = rankedResults\n .sort((a, b) => b.rrf_score - a.rrf_score)\n .slice(0, limit);\n\n return topResults.map((row) => ({\n ...mapDbDocumentToDocument(row),\n metadata: {\n ...JSON.parse(row.metadata),\n score: row.rrf_score,\n vec_rank: row.vec_rank,\n fts_rank: row.fts_rank,\n },\n }));\n } catch (error) {\n throw new ConnectionError(\n `Failed to find documents by content with query \"${query}\"`,\n error,\n );\n }\n }\n\n /**\n * Finds child chunks of a given document based on path hierarchy.\n */\n async findChildChunks(\n library: string,\n version: string,\n id: string,\n limit: number,\n ): Promise<Document[]> {\n try {\n const parent = await this.getById(id);\n if (!parent) {\n return [];\n }\n\n const parentPath = (parent.metadata as DocumentMetadata).path ?? [];\n const parentUrl = (parent.metadata as DocumentMetadata).url;\n\n const result = this.statements.getChildChunks.all(\n library.toLowerCase(),\n version.toLowerCase(),\n parentUrl,\n parentPath.length + 1,\n JSON.stringify(parentPath),\n id,\n limit,\n ) as Array<DbDocument>;\n\n return result.map((row) => mapDbDocumentToDocument(row));\n } catch (error) {\n throw new ConnectionError(`Failed to find child chunks for ID ${id}`, error);\n }\n }\n\n /**\n * Finds preceding sibling chunks of a given document.\n */\n async findPrecedingSiblingChunks(\n library: string,\n version: string,\n id: string,\n limit: number,\n ): Promise<Document[]> {\n try {\n const reference = await this.getById(id);\n if (!reference) {\n return [];\n }\n\n const refMetadata = reference.metadata as DocumentMetadata;\n\n const result = this.statements.getPrecedingSiblings.all(\n library.toLowerCase(),\n version.toLowerCase(),\n refMetadata.url,\n id,\n JSON.stringify(refMetadata.path),\n limit,\n ) as Array<DbDocument>;\n\n return result.reverse().map((row) => mapDbDocumentToDocument(row));\n } catch (error) {\n throw new ConnectionError(\n `Failed to find preceding sibling chunks for ID ${id}`,\n error,\n );\n }\n }\n\n /**\n * Finds subsequent sibling chunks of a given document.\n */\n async findSubsequentSiblingChunks(\n library: string,\n version: string,\n id: string,\n limit: number,\n ): Promise<Document[]> {\n try {\n const reference = await this.getById(id);\n if (!reference) {\n return [];\n }\n\n const refMetadata = reference.metadata;\n\n const result = this.statements.getSubsequentSiblings.all(\n library.toLowerCase(),\n version.toLowerCase(),\n refMetadata.url,\n id,\n JSON.stringify(refMetadata.path),\n limit,\n ) as Array<DbDocument>;\n\n return result.map((row) => mapDbDocumentToDocument(row));\n } catch (error) {\n throw new ConnectionError(\n `Failed to find subsequent sibling chunks for ID ${id}`,\n error,\n );\n }\n }\n\n /**\n * Finds the parent chunk of a given document.\n */\n async findParentChunk(\n library: string,\n version: string,\n id: string,\n ): Promise<Document | null> {\n try {\n const child = await this.getById(id);\n if (!child) {\n return null;\n }\n\n const childMetadata = child.metadata as DocumentMetadata;\n const path = childMetadata.path ?? [];\n const parentPath = path.slice(0, -1);\n\n if (parentPath.length === 0) {\n return null;\n }\n\n const result = this.statements.getParentChunk.get(\n library.toLowerCase(),\n version.toLowerCase(),\n childMetadata.url,\n JSON.stringify(parentPath),\n id,\n ) as DbQueryResult<DbDocument>;\n\n if (!result) {\n return null;\n }\n\n return mapDbDocumentToDocument(result);\n } catch (error) {\n throw new ConnectionError(`Failed to find parent chunk for ID ${id}`, error);\n }\n }\n\n /**\n * Fetches multiple documents by their IDs in a single call.\n * Returns an array of Document objects, sorted by their sort_order.\n */\n async findChunksByIds(\n library: string,\n version: string,\n ids: string[],\n ): Promise<Document[]> {\n if (!ids.length) return [];\n try {\n // Use parameterized query for variable number of IDs\n const placeholders = ids.map(() => \"?\").join(\",\");\n const stmt = this.db.prepare(\n `SELECT * FROM documents WHERE library = ? AND version = ? AND id IN (${placeholders}) ORDER BY sort_order`,\n );\n const rows = stmt.all(\n library.toLowerCase(),\n version.toLowerCase(),\n ...ids,\n ) as DbDocument[];\n return rows.map((row) => mapDbDocumentToDocument(row));\n } catch (error) {\n throw new ConnectionError(\"Failed to fetch documents by IDs\", error);\n }\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Document } from \"@langchain/core/documents\";\nimport envPaths from \"env-paths\";\nimport Fuse from \"fuse.js\";\nimport semver from \"semver\";\nimport { GreedySplitter, SemanticMarkdownSplitter } from \"../splitter\";\nimport type { ContentChunk, DocumentSplitter } from \"../splitter/types\";\nimport { LibraryNotFoundError, VersionNotFoundError } from \"../tools\";\nimport {\n SPLITTER_MAX_CHUNK_SIZE,\n SPLITTER_MIN_CHUNK_SIZE,\n SPLITTER_PREFERRED_CHUNK_SIZE,\n} from \"../utils/config\";\nimport { logger } from \"../utils/logger\";\nimport { getProjectRoot } from \"../utils/paths\";\nimport { DocumentRetrieverService } from \"./DocumentRetrieverService\";\nimport { DocumentStore } from \"./DocumentStore\";\nimport { StoreError } from \"./errors\";\nimport type {\n FindVersionResult,\n LibraryVersion,\n LibraryVersionDetails,\n StoreSearchResult,\n} from \"./types\";\n\n/**\n * Provides semantic search capabilities across different versions of library documentation.\n */\nexport class DocumentManagementService {\n private readonly store: DocumentStore;\n private readonly documentRetriever: DocumentRetrieverService;\n private readonly splitter: DocumentSplitter;\n\n /**\n * Normalizes a version string, converting null or undefined to an empty string\n * and converting to lowercase.\n */\n private normalizeVersion(version?: string | null): string {\n return (version ?? \"\").toLowerCase();\n }\n\n constructor() {\n let dbPath: string;\n let dbDir: string;\n\n // 1. Check Environment Variable\n const envStorePath = process.env.DOCS_MCP_STORE_PATH;\n if (envStorePath) {\n dbDir = envStorePath;\n dbPath = path.join(dbDir, \"documents.db\");\n logger.debug(`💾 Using database directory from DOCS_MCP_STORE_PATH: ${dbDir}`);\n } else {\n // 2. Check Old Local Path\n const projectRoot = getProjectRoot();\n const oldDbDir = path.join(projectRoot, \".store\");\n const oldDbPath = path.join(oldDbDir, \"documents.db\");\n const oldDbExists = fs.existsSync(oldDbPath); // Check file existence specifically\n\n if (oldDbExists) {\n dbPath = oldDbPath;\n dbDir = oldDbDir;\n logger.debug(`💾 Using legacy database path: ${dbPath}`);\n } else {\n // 3. Use Standard Path\n const standardPaths = envPaths(\"docs-mcp-server\", { suffix: \"\" });\n dbDir = standardPaths.data;\n dbPath = path.join(dbDir, \"documents.db\");\n logger.debug(`💾 Using standard database directory: ${dbDir}`);\n }\n }\n\n // Ensure the chosen directory exists\n try {\n fs.mkdirSync(dbDir, { recursive: true });\n } catch (error) {\n // Log potential error during directory creation but proceed\n // The DocumentStore constructor might handle DB file creation errors\n logger.error(`⚠️ Failed to create database directory ${dbDir}: ${error}`);\n }\n\n this.store = new DocumentStore(dbPath);\n this.documentRetriever = new DocumentRetrieverService(this.store);\n\n const semanticSplitter = new SemanticMarkdownSplitter(\n SPLITTER_PREFERRED_CHUNK_SIZE,\n SPLITTER_MAX_CHUNK_SIZE,\n );\n const greedySplitter = new GreedySplitter(\n semanticSplitter,\n SPLITTER_MIN_CHUNK_SIZE,\n SPLITTER_PREFERRED_CHUNK_SIZE,\n );\n\n this.splitter = greedySplitter;\n }\n\n /**\n * Initializes the underlying document store.\n */\n async initialize(): Promise<void> {\n await this.store.initialize();\n }\n\n /**\n * Shuts down the underlying document store.\n */\n\n async shutdown(): Promise<void> {\n logger.debug(\"Shutting down store manager\");\n await this.store.shutdown();\n }\n\n /**\n * Validates if a library exists in the store (either versioned or unversioned).\n * Throws LibraryNotFoundError with suggestions if the library is not found.\n * @param library The name of the library to validate.\n * @throws {LibraryNotFoundError} If the library does not exist.\n */\n async validateLibraryExists(library: string): Promise<void> {\n logger.info(`🔎 Validating existence of library: ${library}`);\n const normalizedLibrary = library.toLowerCase(); // Ensure consistent casing\n\n // Check for both versioned and unversioned documents\n const versions = await this.listVersions(normalizedLibrary);\n const hasUnversioned = await this.exists(normalizedLibrary, \"\"); // Check explicitly for unversioned\n\n if (versions.length === 0 && !hasUnversioned) {\n logger.warn(`⚠️ Library '${library}' not found.`);\n\n // Library doesn't exist, fetch all libraries to provide suggestions\n const allLibraries = await this.listLibraries();\n const libraryNames = allLibraries.map((lib) => lib.library);\n\n let suggestions: string[] = [];\n if (libraryNames.length > 0) {\n const fuse = new Fuse(libraryNames, {\n // Configure fuse.js options if needed (e.g., threshold)\n // isCaseSensitive: false, // Handled by normalizing library names\n // includeScore: true,\n threshold: 0.4, // Adjust threshold for desired fuzziness (0=exact, 1=match anything)\n });\n const results = fuse.search(normalizedLibrary);\n // Take top 3 suggestions\n suggestions = results.slice(0, 3).map((result) => result.item);\n logger.info(`🔍 Found suggestions: ${suggestions.join(\", \")}`);\n }\n\n throw new LibraryNotFoundError(library, suggestions);\n }\n\n logger.info(`✅ Library '${library}' confirmed to exist.`);\n }\n\n /**\n * Returns a list of all available semantic versions for a library.\n */\n async listVersions(library: string): Promise<LibraryVersion[]> {\n const versions = await this.store.queryUniqueVersions(library);\n return versions.filter((v) => semver.valid(v)).map((version) => ({ version }));\n }\n\n /**\n * Checks if documents exist for a given library and optional version.\n * If version is omitted, checks for documents without a specific version.\n */\n async exists(library: string, version?: string | null): Promise<boolean> {\n const normalizedVersion = this.normalizeVersion(version);\n return this.store.checkDocumentExists(library, normalizedVersion);\n }\n\n /**\n * Finds the most appropriate version of documentation based on the requested version.\n * When no target version is specified, returns the latest version.\n *\n * Version matching behavior:\n * - Exact versions (e.g., \"18.0.0\"): Matches that version or any earlier version\n * - X-Range patterns (e.g., \"5.x\", \"5.2.x\"): Matches within the specified range\n * - \"latest\" or no version: Returns the latest available version\n *\n * For documentation, we prefer matching older versions over no match at all,\n * since older docs are often still relevant and useful.\n * Also checks if unversioned documents exist for the library.\n */\n async findBestVersion(\n library: string,\n targetVersion?: string,\n ): Promise<FindVersionResult> {\n logger.info(\n `🔍 Finding best version for ${library}${targetVersion ? `@${targetVersion}` : \"\"}`,\n );\n\n // Check if unversioned documents exist *before* filtering for valid semver\n const hasUnversioned = await this.store.checkDocumentExists(library, \"\");\n const validSemverVersions = await this.listVersions(library);\n\n if (validSemverVersions.length === 0) {\n if (hasUnversioned) {\n logger.info(`ℹ️ Unversioned documents exist for ${library}`);\n return { bestMatch: null, hasUnversioned: true };\n }\n // Throw error only if NO versions (semver or unversioned) exist\n logger.warn(`⚠️ No valid versions found for ${library}`);\n // Fetch detailed versions to pass to the error constructor\n const allLibraryDetails = await this.store.queryLibraryVersions();\n const libraryDetails = allLibraryDetails.get(library) ?? [];\n throw new VersionNotFoundError(library, targetVersion ?? \"\", libraryDetails);\n }\n\n const versionStrings = validSemverVersions.map((v) => v.version);\n let bestMatch: string | null = null;\n\n if (!targetVersion || targetVersion === \"latest\") {\n bestMatch = semver.maxSatisfying(versionStrings, \"*\");\n } else {\n const versionRegex = /^(\\d+)(?:\\.(?:x(?:\\.x)?|\\d+(?:\\.(?:x|\\d+))?))?$|^$/;\n if (!versionRegex.test(targetVersion)) {\n logger.warn(`⚠️ Invalid target version format: ${targetVersion}`);\n // Don't throw yet, maybe unversioned exists\n } else {\n // Restore the previous logic with fallback\n let range = targetVersion;\n if (!semver.validRange(targetVersion)) {\n // If it's not a valid range (like '1.2' or '1'), treat it like a tilde range\n range = `~${targetVersion}`;\n } else if (semver.valid(targetVersion)) {\n // If it's an exact version, allow matching it OR any older version\n range = `${range} || <=${targetVersion}`;\n }\n // If it was already a valid range (like '1.x'), use it directly\n bestMatch = semver.maxSatisfying(versionStrings, range);\n }\n }\n\n if (bestMatch) {\n logger.info(\n `✅ Found best match version ${bestMatch} for ${library}@${targetVersion}`,\n );\n } else {\n logger.warn(`⚠️ No matching semver version found for ${library}@${targetVersion}`);\n }\n\n // If no semver match found, but unversioned exists, return that info.\n // If a semver match was found, return it along with unversioned status.\n // If no semver match AND no unversioned, throw error.\n if (!bestMatch && !hasUnversioned) {\n // Fetch detailed versions to pass to the error constructor\n const allLibraryDetails = await this.store.queryLibraryVersions();\n const libraryDetails = allLibraryDetails.get(library) ?? [];\n throw new VersionNotFoundError(library, targetVersion ?? \"\", libraryDetails);\n }\n\n return { bestMatch, hasUnversioned };\n }\n\n /**\n * Removes all documents for a specific library and optional version.\n * If version is omitted, removes documents without a specific version.\n */\n async removeAllDocuments(library: string, version?: string | null): Promise<void> {\n const normalizedVersion = this.normalizeVersion(version);\n logger.info(\n `🗑️ Removing all documents from ${library}@${normalizedVersion || \"[no version]\"} store`,\n );\n const count = await this.store.deleteDocuments(library, normalizedVersion);\n logger.info(`📊 Deleted ${count} documents`);\n }\n\n /**\n * Adds a document to the store, splitting it into smaller chunks for better search results.\n * Uses SemanticMarkdownSplitter to maintain markdown structure and content types during splitting.\n * Preserves hierarchical structure of documents and distinguishes between text and code segments.\n * If version is omitted, the document is added without a specific version.\n */\n async addDocument(\n library: string,\n version: string | null | undefined,\n document: Document,\n ): Promise<void> {\n const normalizedVersion = this.normalizeVersion(version);\n const url = document.metadata.url as string;\n if (!url || typeof url !== \"string\" || !url.trim()) {\n throw new StoreError(\"Document metadata must include a valid URL\");\n }\n\n logger.info(`📚 Adding document: ${document.metadata.title}`);\n\n if (!document.pageContent.trim()) {\n throw new Error(\"Document content cannot be empty\");\n }\n\n // Split document into semantic chunks\n const chunks = await this.splitter.splitText(document.pageContent);\n\n // Convert semantic chunks to documents\n const splitDocs = chunks.map((chunk: ContentChunk) => ({\n pageContent: chunk.content,\n metadata: {\n ...document.metadata,\n level: chunk.section.level,\n path: chunk.section.path,\n },\n }));\n logger.info(`✂️ Split document into ${splitDocs.length} chunks`);\n\n // Add split documents to store\n await this.store.addDocuments(library, normalizedVersion, splitDocs);\n }\n\n /**\n * Searches for documentation content across versions.\n * Uses hybrid search (vector + FTS).\n * If version is omitted, searches documents without a specific version.\n */\n async searchStore(\n library: string,\n version: string | null | undefined,\n query: string,\n limit = 5,\n ): Promise<StoreSearchResult[]> {\n const normalizedVersion = this.normalizeVersion(version);\n return this.documentRetriever.search(library, normalizedVersion, query, limit);\n }\n\n async listLibraries(): Promise<\n Array<{ library: string; versions: LibraryVersionDetails[] }>\n > {\n // queryLibraryVersions now returns the detailed map directly\n const libraryMap = await this.store.queryLibraryVersions();\n\n // Transform the map into the desired array structure\n return Array.from(libraryMap.entries()).map(([library, versions]) => ({\n library,\n versions, // The versions array already contains LibraryVersionDetails\n }));\n }\n}\n","import type { PropsWithChildren } from \"@kitajs/html\";\nimport { readFileSync } from \"node:fs\";\n\n/**\n * Props for the Layout component.\n */\ninterface LayoutProps extends PropsWithChildren {\n title: string;\n /** Optional version string to display next to the title. */\n version?: string;\n}\n\n/**\n * Base HTML layout component for all pages.\n * Includes common head elements, header, and scripts.\n * @param props - Component props including title, version, and children.\n */\nconst Layout = ({ title, version, children }: LayoutProps) => {\n let versionString = version;\n if (!versionString) {\n // If no version is provided, use the version from package.json\n // We cannot bake the version into the bundle, as the package.json will\n // be updated by the build process, after the bundle is created.\n try {\n const packageJson = JSON.parse(readFileSync(\"package.json\", \"utf-8\")) as {\n version: string;\n };\n versionString = packageJson.version;\n } catch (error) {\n console.error(\"Error reading package.json:\", error);\n }\n }\n return (\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title safe>{title}</title>\n {/* Bundled CSS (includes Tailwind and Flowbite) */}\n <link rel=\"stylesheet\" href=\"/assets/main.css\" />\n {/* Add style for htmx-indicator behavior (needed globally) */}\n <style>\n {`\n .htmx-indicator {\n display: none;\n }\n .htmx-request .htmx-indicator {\n display: block;\n }\n .htmx-request.htmx-indicator {\n display: block;\n }\n /* Default: Hide skeleton, show results container */\n #searchResultsContainer .search-skeleton { display: none; }\n #searchResultsContainer .search-results { display: block; } /* Or as needed */\n\n /* Request in progress: Show skeleton, hide results */\n #searchResultsContainer.htmx-request .search-skeleton { display: block; } /* Or flex etc. */\n #searchResultsContainer.htmx-request .search-results { display: none; }\n\n /* Keep button spinner logic */\n form .htmx-indicator .spinner { display: flex; }\n form .htmx-indicator .search-text { display: none; }\n form .spinner { display: none; }\n `}\n </style>\n </head>\n <body class=\"bg-gray-50 dark:bg-gray-900\">\n <div class=\"container max-w-2xl mx-auto px-4 py-4\">\n <header class=\"mb-4\">\n <h1 class=\"text-3xl font-bold text-gray-900 dark:text-white\">\n <a href=\"/\">MCP Docs</a>\n {versionString ? (\n <span\n safe\n class=\"ml-2 text-base font-normal text-gray-500 dark:text-gray-400 align-baseline\"\n title={`Version ${versionString}`}\n >\n v{versionString}\n </span>\n ) : null}\n </h1>\n </header>\n\n <main>{children}</main>\n </div>\n\n {/* Bundled JS (includes Flowbite, HTMX, AlpineJS, and initialization) */}\n <script type=\"module\" src=\"/assets/main.js\"></script>\n </body>\n </html>\n );\n};\n\nexport default Layout;\n","import type { FastifyInstance } from \"fastify\";\nimport Layout from \"../components/Layout\"; // Import the Layout component\n\n/**\n * Registers the root route that serves the main HTML page.\n * @param server - The Fastify instance.\n */\nexport function registerIndexRoute(server: FastifyInstance) {\n server.get(\"/\", async (_, reply) => {\n reply.type(\"text/html\");\n // Use the Layout component and define the main content within it\n return (\n \"<!DOCTYPE html>\" +\n (\n <Layout title=\"MCP Docs\">\n {/* Job Queue Section */}\n <section class=\"mb-4 p-4 bg-white rounded-lg shadow dark:bg-gray-800 border border-gray-300 dark:border-gray-600\">\n <div class=\"flex items-center justify-between mb-2\">\n <h2 class=\"text-xl font-semibold text-gray-900 dark:text-white\">\n Job Queue\n </h2>\n <button\n type=\"button\"\n class=\"text-xs px-3 py-1.5 text-gray-700 bg-gray-100 border border-gray-300 rounded-lg hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-gray-100 dark:bg-gray-600 dark:text-gray-300 dark:border-gray-500 dark:hover:bg-gray-700 dark:focus:ring-gray-700 transition-colors duration-150\"\n title=\"Clear all completed, cancelled, and failed jobs\"\n hx-post=\"/api/jobs/clear-completed\"\n hx-trigger=\"click\"\n hx-on=\"htmx:afterRequest: document.dispatchEvent(new Event('job-list-refresh'))\"\n hx-swap=\"none\"\n >\n Clear Completed Jobs\n </button>\n </div>\n {/* Container for the job list, loaded via HTMX */}\n <div id=\"job-queue\" hx-get=\"/api/jobs\" hx-trigger=\"load, every 1s\">\n {/* Initial loading state */}\n <div class=\"animate-pulse\">\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4\" />\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5\" />\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5\" />\n </div>\n </div>\n </section>\n {/* Add New Job Section */}\n <section class=\"mb-8\">\n {/* Container for the add job form, loaded via HTMX */}\n <div id=\"addJobForm\" hx-get=\"/api/jobs/new\" hx-trigger=\"load\">\n {/* Initial loading state (optional, could just be empty) */}\n <div class=\"p-6 bg-white rounded-lg shadow dark:bg-gray-800 animate-pulse\">\n <div class=\"h-6 bg-gray-200 rounded-full dark:bg-gray-700 w-1/3 mb-4\" />\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5\" />\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5\" />\n </div>\n </div>\n </section>\n {/* Indexed Documentation Section */}\n <div>\n <h2 class=\"text-xl font-semibold mb-2 text-gray-900 dark:text-white\">\n Indexed Documentation\n </h2>\n <div\n id=\"indexed-docs\"\n hx-get=\"/api/libraries\"\n hx-trigger=\"load, every 10s\"\n >\n <div class=\"animate-pulse\">\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4\" />\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5\" />\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5\" />\n </div>\n </div>\n </div>\n </Layout>\n )\n );\n });\n}\n","import type { FastifyInstance } from \"fastify\";\nimport type { CancelJobTool } from \"../../../tools/CancelJobTool\";\n\n/**\n * Registers the API route for cancelling jobs.\n * @param server - The Fastify instance.\n * @param cancelJobTool - The tool instance for cancelling jobs.\n */\nexport function registerCancelJobRoute(\n server: FastifyInstance,\n cancelJobTool: CancelJobTool\n) {\n // POST /api/jobs/:jobId/cancel - Cancel a job by ID\n server.post<{ Params: { jobId: string } }>(\n \"/api/jobs/:jobId/cancel\",\n async (request, reply) => {\n const { jobId } = request.params;\n const result = await cancelJobTool.execute({ jobId });\n if (result.success) {\n return { success: true, message: result.message };\n } else {\n reply.status(400);\n return { success: false, message: result.message };\n }\n }\n );\n}\n","import type { FastifyInstance } from \"fastify\";\nimport type { ClearCompletedJobsTool } from \"../../../tools/ClearCompletedJobsTool\";\n\n/**\n * Registers the API route for clearing completed jobs.\n * @param server - The Fastify instance.\n * @param clearCompletedJobsTool - The tool instance for clearing completed jobs.\n */\nexport function registerClearCompletedJobsRoute(\n server: FastifyInstance,\n clearCompletedJobsTool: ClearCompletedJobsTool\n) {\n // POST /api/jobs/clear-completed - Clear all completed jobs\n server.post(\"/api/jobs/clear-completed\", async (_, reply) => {\n try {\n const result = await clearCompletedJobsTool.execute({});\n\n reply.type(\"application/json\");\n return {\n success: result.success,\n message: result.message,\n };\n } catch (error) {\n reply.code(500);\n return {\n success: false,\n message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n });\n}\n","interface VersionBadgeProps {\n version: string;\n}\n\nconst VersionBadge = ({ version }: VersionBadgeProps) => {\n if (!version) {\n return null; // Don't render if no version is provided\n }\n\n return (\n <span class=\"bg-purple-100 text-purple-800 text-xs font-medium me-2 px-1.5 py-0.5 rounded dark:bg-purple-900 dark:text-purple-300\">\n <span safe>{version}</span>\n </span>\n );\n};\n\nexport default VersionBadge;\n","/**\n * Renders an SVG loading spinner icon.\n * Used for indicating loading states in buttons or other elements.\n */\nconst LoadingSpinner = () => (\n <svg\n class=\"animate-spin h-4 w-4 text-white\" // Adjusted size to h-4 w-4 to match usage\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n class=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"4\"\n ></circle>\n <path\n class=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n);\n\nexport default LoadingSpinner;\n","import type { JobInfo } from \"../../tools/GetJobInfoTool\";\nimport { PipelineJobStatus } from \"../../pipeline/types\";\nimport VersionBadge from \"./VersionBadge\"; // Adjusted import path\nimport LoadingSpinner from \"./LoadingSpinner\";\n\n/**\n * Props for the JobItem component.\n */\ninterface JobItemProps {\n job: JobInfo;\n}\n\n/**\n * Renders a single job item with its details and status.\n * Includes a cancel button with loading spinner for QUEUED/RUNNING jobs.\n * @param props - Component props including the job information.\n */\nconst JobItem = ({ job }: JobItemProps) => (\n // Use Flowbite Card structure with reduced padding and added border\n <div class=\"block p-2 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600\">\n <div class=\"flex items-center justify-between\">\n <div>\n <p class=\"text-sm font-medium text-gray-900 dark:text-white\">\n <span safe>{job.library}</span>{\" \"}\n {/* Display version as badge if exists */}\n <VersionBadge version={job.version} />\n </p>\n <p class=\"text-sm text-gray-500 dark:text-gray-400\">\n Indexed: <span safe>{new Date(job.createdAt).toLocaleString()}</span>\n </p>\n </div>\n <div class=\"flex flex-col items-end gap-1\">\n {/* Status badge with inline stop button for QUEUED/RUNNING jobs */}\n <div class=\"flex items-center gap-2\">\n <span\n class={`px-1.5 py-0.5 text-xs font-medium rounded ${\n job.status === PipelineJobStatus.COMPLETED\n ? \"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300\"\n : job.error\n ? \"bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300\"\n : \"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300\"\n }`}\n >\n {job.status}\n </span>\n {/* Stop button for QUEUED/RUNNING jobs */}\n {(job.status === PipelineJobStatus.QUEUED ||\n job.status === PipelineJobStatus.RUNNING) && (\n <button\n type=\"button\"\n class=\"font-medium rounded-lg text-xs p-1 text-center inline-flex items-center transition-colors duration-150 ease-in-out border border-gray-300 bg-white text-red-600 hover:bg-red-50 focus:ring-4 focus:outline-none focus:ring-red-100 dark:border-gray-600 dark:bg-gray-800 dark:text-red-400 dark:hover:bg-gray-700 dark:focus:ring-red-900\"\n title=\"Stop this job\"\n x-data=\"{}\"\n x-on:click={`\n if ($store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}') {\n $store.confirmingAction.isStopping = true;\n fetch('/api/jobs/' + '${job.id}' + '/cancel', {\n method: 'POST',\n headers: { 'Accept': 'application/json' },\n })\n .then(r => r.json())\n .then(() => {\n $store.confirmingAction.type = null;\n $store.confirmingAction.id = null;\n $store.confirmingAction.isStopping = false;\n if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }\n document.dispatchEvent(new CustomEvent('job-list-refresh'));\n })\n .catch(() => { $store.confirmingAction.isStopping = false; });\n } else {\n if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }\n $store.confirmingAction.type = 'job-cancel';\n $store.confirmingAction.id = '${job.id}';\n $store.confirmingAction.isStopping = false;\n $store.confirmingAction.timeoutId = setTimeout(() => {\n $store.confirmingAction.type = null;\n $store.confirmingAction.id = null;\n $store.confirmingAction.isStopping = false;\n $store.confirmingAction.timeoutId = null;\n }, 3000);\n }\n `}\n x-bind:disabled={`$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}' && $store.confirmingAction.isStopping`}\n >\n <span\n x-show={`$store.confirmingAction.type !== 'job-cancel' || $store.confirmingAction.id !== '${job.id}' || $store.confirmingAction.isStopping`}\n >\n {/* Red Stop Icon */}\n <svg\n class=\"w-4 h-4\"\n aria-hidden=\"true\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <rect x=\"5\" y=\"5\" width=\"10\" height=\"10\" rx=\"2\" />\n </svg>\n <span class=\"sr-only\">Stop job</span>\n </span>\n <span\n x-show={`$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}' && !$store.confirmingAction.isStopping`}\n class=\"px-2\"\n >\n Cancel?\n </span>\n <span\n x-show={`$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}' && $store.confirmingAction.isStopping`}\n >\n <LoadingSpinner />\n <span class=\"sr-only\">Stopping...</span>\n </span>\n </button>\n )}\n </div>\n {job.error && (\n // Keep the error badge for clarity if an error occurred\n <span class=\"bg-red-100 text-red-800 text-xs font-medium px-1.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300\">\n Error\n </span>\n )}\n </div>\n </div>\n </div>\n);\n\nexport default JobItem;\n","import type { JobInfo } from \"../../tools/GetJobInfoTool\";\nimport JobItem from \"./JobItem\"; // Adjusted import path\n\n/**\n * Props for the JobList component.\n */\ninterface JobListProps {\n jobs: JobInfo[];\n}\n\n/**\n * Renders a list of JobItem components or a message if the list is empty.\n * Adds a listener for the 'job-list-refresh' event to trigger a reload of the job list using HTMX.\n * @param props - Component props including the array of jobs.\n */\nconst JobList = ({ jobs }: JobListProps) => (\n <div id=\"job-list\" class=\"space-y-2\">\n {jobs.length === 0 ? (\n <p class=\"text-center text-gray-500 dark:text-gray-400\">\n No pending jobs.\n </p>\n ) : (\n jobs.map((job) => <JobItem job={job} />)\n )}\n {/* NOTE: To enable live job list refresh after stopping a job, add the following script to your main HTML layout or main.client.ts:\n document.addEventListener('job-list-refresh', function () {\n if (window.htmx) {\n window.htmx.ajax('GET', '/api/jobs', '#job-list');\n } else {\n window.location.reload();\n }\n });\n */}\n </div>\n);\n\nexport default JobList;\n","import type { FastifyInstance } from \"fastify\";\nimport type { ListJobsTool } from \"../../../tools/ListJobsTool\"; // Adjusted import path\nimport JobList from \"../../components/JobList\"; // Import the extracted component\n\n/**\n * Registers the API route for listing jobs.\n * @param server - The Fastify instance.\n * @param listJobsTool - The tool instance for listing jobs.\n */\nexport function registerJobListRoutes(\n server: FastifyInstance,\n listJobsTool: ListJobsTool\n) {\n // GET /api/jobs - List current jobs (only the list)\n server.get(\"/api/jobs\", async () => {\n const result = await listJobsTool.execute({});\n return <JobList jobs={result.jobs} />;\n });\n}\n","import type { PropsWithChildren } from \"@kitajs/html\";\n\n/**\n * Defines the possible types for the Alert component.\n */\ntype AlertType = \"success\" | \"error\" | \"warning\" | \"info\";\n\n/**\n * Props for the Alert component.\n */\ninterface AlertProps extends PropsWithChildren {\n type: AlertType;\n title?: string;\n message: string | JSX.Element; // Allow JSX for messages\n}\n\n/**\n * Reusable Alert component using Flowbite styling.\n * Displays messages with appropriate colors and icons based on the type.\n * @param props - Component props including type, title (optional), and message.\n */\nconst Alert = ({ type, title, message }: AlertProps) => {\n let iconSvg: JSX.Element;\n let colorClasses: string;\n let defaultTitle: string;\n\n switch (type) {\n case \"success\":\n defaultTitle = \"Success:\";\n colorClasses =\n \"text-green-800 border-green-300 bg-green-50 dark:bg-gray-800 dark:text-green-400 dark:border-green-800\";\n iconSvg = (\n <svg\n class=\"flex-shrink-0 inline w-4 h-4 me-3\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path d=\"M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm9.5 9.5A9.5 9.5 0 0 1 10 19a9.46 9.46 0 0 1-1.671-.14c-.165-.05-.3-.19-.42-.335l-.165-.165c-.19-.2-.3-.425-.3-.655A4.2 4.2 0 0 1 4.5 10a4.25 4.25 0 0 1 7.462-2.882l1.217 1.217a3.175 3.175 0 0 0 4.5.01l.106-.106a.934.934 0 0 0 .1-.36ZM10 11a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z\" />\n </svg>\n );\n break;\n case \"error\":\n defaultTitle = \"Error:\";\n colorClasses =\n \"text-red-800 border-red-300 bg-red-50 dark:bg-gray-800 dark:text-red-400 dark:border-red-800\";\n iconSvg = (\n <svg\n class=\"flex-shrink-0 inline w-4 h-4 me-3\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path d=\"M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3h-1a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z\" />\n </svg>\n );\n break;\n case \"warning\":\n defaultTitle = \"Warning:\";\n colorClasses =\n \"text-yellow-800 border-yellow-300 bg-yellow-50 dark:bg-gray-800 dark:text-yellow-300 dark:border-yellow-800\";\n iconSvg = (\n <svg\n class=\"flex-shrink-0 inline w-4 h-4 me-3\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path d=\"M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3h-1a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z\" />\n </svg>\n );\n break;\n case \"info\":\n default: // Default to info style\n defaultTitle = \"Info:\";\n colorClasses =\n \"text-blue-800 border-blue-300 bg-blue-50 dark:bg-gray-800 dark:text-blue-400 dark:border-blue-800\";\n iconSvg = (\n <svg\n class=\"flex-shrink-0 inline w-4 h-4 me-3\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path d=\"M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z\" />\n </svg>\n );\n break;\n }\n\n const displayTitle = title ?? defaultTitle;\n\n return (\n <div\n class={`flex items-center p-4 mb-4 text-sm border rounded-lg ${colorClasses}`}\n role=\"alert\"\n >\n {iconSvg}\n <span class=\"sr-only\">Info</span>\n <div>\n {displayTitle ? (\n <span class=\"font-medium\" safe>\n {displayTitle}\n </span>\n ) : null}{\" \"}\n {message}\n </div>\n </div>\n );\n};\n\nexport default Alert;\n","import type { PropsWithChildren } from \"@kitajs/html\";\n\n/**\n * Props for the Tooltip component.\n */\ninterface TooltipProps extends PropsWithChildren {\n text: string | Promise<string> | Element;\n position?: \"top\" | \"right\" | \"bottom\" | \"left\";\n}\n\n/**\n * Reusable Tooltip component using Alpine.js for state management.\n * Displays a help icon that shows a tooltip on hover/focus.\n *\n * @param props - Component props including text and optional position.\n */\nconst Tooltip = ({ text, position = \"top\" }: TooltipProps) => {\n // Map position to Tailwind classes\n const positionClasses = {\n top: \"bottom-full left-1/2 transform -translate-x-1/2 -translate-y-1 mb-1\",\n right: \"left-full top-1/2 transform -translate-y-1/2 translate-x-1 ml-1\",\n bottom: \"top-full left-1/2 transform -translate-x-1/2 translate-y-1 mt-1\",\n left: \"right-full top-1/2 transform -translate-y-1/2 -translate-x-1 mr-1\",\n };\n\n return (\n <div\n class=\"relative ml-1.5 flex items-center\"\n x-data=\"{ isVisible: false }\"\n >\n <button\n type=\"button\"\n class=\"text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400 focus:outline-none flex items-center\"\n aria-label=\"Help\"\n x-on:mouseenter=\"isVisible = true\"\n x-on:mouseleave=\"isVisible = false\"\n x-on:focus=\"isVisible = true\"\n x-on:blur=\"isVisible = false\"\n tabindex=\"0\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n class=\"w-4 h-4\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z\"\n />\n </svg>\n </button>\n <div\n x-show=\"isVisible\"\n x-cloak\n class={`absolute z-10 w-64 p-2 text-sm text-gray-500 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400 ${positionClasses[position]}`}\n >\n {text as \"safe\"}\n </div>\n </div>\n );\n};\n\nexport default Tooltip;\n","import { ScrapeMode } from \"../../scraper/types\"; // Adjusted import path\nimport Alert from \"./Alert\";\nimport Tooltip from \"./Tooltip\";\n\n/**\n * Renders the form fields for queuing a new scrape job.\n * Includes basic fields (URL, Library, Version) and advanced options.\n */\nconst ScrapeFormContent = () => (\n <div class=\"mt-4 p-4 bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-300 dark:border-gray-600\">\n <h3 class=\"text-xl font-semibold text-gray-900 dark:text-white mb-2\">\n Queue New Scrape Job\n </h3>\n <form\n hx-post=\"/api/jobs/scrape\"\n hx-target=\"#job-response\"\n hx-swap=\"innerHTML\"\n class=\"space-y-2\"\n x-data=\"{\n url: '',\n hasPath: false,\n headers: [],\n checkUrlPath() {\n try {\n const url = new URL(this.url);\n this.hasPath = url.pathname !== '/' && url.pathname !== '';\n } catch (e) {\n this.hasPath = false;\n }\n }\n }\"\n >\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"url\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n URL\n </label>\n <Tooltip\n text={\n <div>\n <p>Enter the URL of the documentation you want to scrape.</p>\n <p class=\"mt-2\">\n For local files/folders, you must use the <code>file://</code>{\" \"}\n prefix and ensure the path is accessible to the server.\n </p>\n <p class=\"mt-2\">\n If running in Docker, <b>mount the folder</b> (see README for\n details).\n </p>\n </div>\n }\n />\n </div>\n <input\n type=\"url\"\n name=\"url\"\n id=\"url\"\n required\n x-model=\"url\"\n x-on:input=\"checkUrlPath\"\n x-on:paste=\"$nextTick(() => checkUrlPath())\"\n class=\"mt-0.5 block w-full px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n />\n <div\n x-show=\"hasPath && !(url.startsWith('file://'))\"\n x-cloak\n x-transition:enter=\"transition ease-out duration-300\"\n x-transition:enter-start=\"opacity-0 transform -translate-y-2\"\n x-transition:enter-end=\"opacity-100 transform translate-y-0\"\n class=\"mt-2\"\n >\n <Alert\n type=\"info\"\n message=\"By default, only subpages under the given URL will be scraped. To scrape the whole website, adjust the 'Scope' option in Advanced Options.\"\n />\n </div>\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"library\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Library Name\n </label>\n <Tooltip text=\"The name of the library you're documenting. This will be used when searching.\" />\n </div>\n <input\n type=\"text\"\n name=\"library\"\n id=\"library\"\n required\n class=\"mt-0.5 block w-full px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n />\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"version\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Version (optional)\n </label>\n <Tooltip text=\"Specify the version of the library documentation you're indexing. This allows for version-specific searches.\" />\n </div>\n <input\n type=\"text\"\n name=\"version\"\n id=\"version\"\n class=\"mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n />\n </div>\n\n {/* Consider using Flowbite Accordion here */}\n <details class=\"bg-gray-50 dark:bg-gray-900 p-2 rounded-md\">\n <summary class=\"cursor-pointer text-sm font-medium text-gray-600 dark:text-gray-400\">\n Advanced Options\n </summary>\n <div class=\"mt-2 space-y-2\" x-data=\"{ headers: [] }\">\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"maxPages\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Max Pages\n </label>\n <Tooltip text=\"The maximum number of pages to scrape. Default is 1000. Setting this too high may result in longer processing times.\" />\n </div>\n <input\n type=\"number\"\n name=\"maxPages\"\n id=\"maxPages\"\n min=\"1\"\n placeholder=\"1000\"\n class=\"mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n />\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"maxDepth\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Max Depth\n </label>\n <Tooltip text=\"How many links deep the scraper should follow. Default is 3. Higher values capture more content but increase processing time.\" />\n </div>\n <input\n type=\"number\"\n name=\"maxDepth\"\n id=\"maxDepth\"\n min=\"0\"\n placeholder=\"3\"\n class=\"mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n />\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"scope\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Scope\n </label>\n <Tooltip\n text={\n <div>\n Controls which pages are scraped:\n <ul class=\"list-disc pl-5\">\n <li>'Subpages' only scrapes under the given URL path,</li>\n <li>\n 'Hostname' scrapes all content on the same host (e.g.,\n all of docs.example.com),\n </li>\n <li>\n 'Domain' scrapes all content on the domain and its\n subdomains (e.g., all of example.com).\n </li>\n </ul>\n </div>\n }\n />\n </div>\n <select\n name=\"scope\"\n id=\"scope\"\n class=\"mt-0.5 block w-full max-w-sm pl-2 pr-10 py-1 text-base border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n >\n <option value=\"subpages\" selected>\n Subpages (Default)\n </option>\n <option value=\"hostname\">Hostname</option>\n <option value=\"domain\">Domain</option>\n </select>\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"includePatterns\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Include Patterns\n </label>\n <Tooltip text=\"Glob or regex patterns for URLs to include. One per line or comma-separated. Regex patterns must be wrapped in slashes, e.g. /pattern/.\" />\n </div>\n <textarea\n name=\"includePatterns\"\n id=\"includePatterns\"\n rows=\"2\"\n placeholder=\"e.g. docs/* or /api\\/v1.*/\"\n class=\"mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n ></textarea>\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"excludePatterns\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Exclude Patterns\n </label>\n <Tooltip text=\"Glob or regex patterns for URLs to exclude. One per line or comma-separated. Exclude takes precedence over include. Regex patterns must be wrapped in slashes, e.g. /pattern/.\" />\n </div>\n <textarea\n name=\"excludePatterns\"\n id=\"excludePatterns\"\n rows=\"2\"\n placeholder=\"e.g. private/* or /internal/\"\n class=\"mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n ></textarea>\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"scrapeMode\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Scrape Mode\n </label>\n <Tooltip\n text={\n <div>\n <ul class=\"list-disc pl-5\">\n <li>'Auto' automatically selects the best method,</li>\n <li>\n 'Fetch' uses simple HTTP requests (faster but may miss\n dynamic content),\n </li>\n <li>\n 'Playwright' uses a headless browser (slower but better\n for JS-heavy sites).\n </li>\n </ul>\n </div>\n }\n />\n </div>\n <select\n name=\"scrapeMode\"\n id=\"scrapeMode\"\n class=\"mt-0.5 block w-full max-w-sm pl-2 pr-10 py-1 text-base border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n >\n <option value={ScrapeMode.Auto} selected>\n Auto (Default)\n </option>\n <option value={ScrapeMode.Fetch}>Fetch</option>\n <option value={ScrapeMode.Playwright}>Playwright</option>\n </select>\n </div>\n <div>\n <div class=\"flex items-center mb-1\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\">\n Custom HTTP Headers\n </label>\n <Tooltip text=\"Add custom HTTP headers (e.g., for authentication). These will be sent with every HTTP request.\" />\n </div>\n <div>\n {/* AlpineJS dynamic header rows */}\n <template x-for=\"(header, idx) in headers\">\n <div class=\"flex space-x-2 mb-1\">\n <input\n type=\"text\"\n class=\"w-1/3 px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-xs\"\n placeholder=\"Header Name\"\n x-model=\"header.name\"\n required\n />\n <span class=\"text-gray-500\">:</span>\n <input\n type=\"text\"\n class=\"w-1/2 px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-xs\"\n placeholder=\"Header Value\"\n x-model=\"header.value\"\n required\n />\n <button\n type=\"button\"\n class=\"text-red-500 hover:text-red-700 text-xs\"\n x-on:click=\"headers.splice(idx, 1)\"\n >\n Remove\n </button>\n <input\n type=\"hidden\"\n name=\"header[]\"\n x-bind:value=\"header.name && header.value ? header.name + ':' + header.value : ''\"\n />\n </div>\n </template>\n <button\n type=\"button\"\n class=\"mt-1 px-2 py-0.5 bg-indigo-100 dark:bg-indigo-900 text-indigo-700 dark:text-indigo-200 rounded text-xs\"\n x-on:click=\"headers.push({ name: '', value: '' })\"\n >\n + Add Header\n </button>\n </div>\n </div>\n <div class=\"flex items-center\">\n <input\n id=\"followRedirects\"\n name=\"followRedirects\"\n type=\"checkbox\"\n checked\n class=\"h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700\"\n />\n <label\n for=\"followRedirects\"\n class=\"ml-1 block text-sm text-gray-900 dark:text-gray-300\"\n >\n Follow Redirects\n </label>\n </div>\n <div class=\"flex items-center\">\n <input\n id=\"ignoreErrors\"\n name=\"ignoreErrors\"\n type=\"checkbox\"\n checked\n class=\"h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700\"\n />\n <label\n for=\"ignoreErrors\"\n class=\"ml-1 block text-sm text-gray-900 dark:text-gray-300\"\n >\n Ignore Errors During Scraping\n </label>\n </div>\n </div>\n </details>\n\n <div>\n <button\n type=\"submit\"\n class=\"w-full flex justify-center py-1.5 px-3 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500\"\n >\n Queue Job\n </button>\n </div>\n </form>\n {/* Target div for HTMX response */}\n <div id=\"job-response\" class=\"mt-2 text-sm\"></div>\n </div>\n);\n\nexport default ScrapeFormContent;\n","import ScrapeFormContent from \"./ScrapeFormContent\"; // Adjusted import path\n\n/**\n * Wrapper component for the ScrapeFormContent.\n * Provides a container div, often used as a target for HTMX OOB swaps.\n */\nconst ScrapeForm = () => (\n <div id=\"scrape-form-container\">\n <ScrapeFormContent />\n </div>\n);\n\nexport default ScrapeForm;\n","import type { FastifyInstance, FastifyRequest } from \"fastify\";\nimport type { ScrapeTool } from \"../../../tools/ScrapeTool\"; // Adjusted import path\nimport { ScrapeMode } from \"../../../scraper/types\"; // Adjusted import path\nimport { logger } from \"../../../utils/logger\"; // Adjusted import path\nimport ScrapeForm from \"../../components/ScrapeForm\"; // Import extracted component\nimport Alert from \"../../components/Alert\"; // Import Alert component\nimport ScrapeFormContent from \"../../components/ScrapeFormContent\"; // Import for OOB swap\n\n/**\n * Registers the API routes for creating new jobs.\n * @param server - The Fastify instance.\n * @param scrapeTool - The tool instance for scraping documents.\n */\nexport function registerNewJobRoutes(\n server: FastifyInstance,\n scrapeTool: ScrapeTool\n) {\n // GET /api/jobs/new - Return the form component wrapped in its container\n server.get(\"/api/jobs/new\", async () => {\n // Return the wrapper component which includes the container div\n return <ScrapeForm />;\n });\n\n // POST /api/jobs/scrape - Queue a new scrape job\n server.post(\n \"/api/jobs/scrape\",\n async (\n request: FastifyRequest<{\n Body: {\n url: string;\n library: string;\n version?: string;\n maxPages?: string;\n maxDepth?: string;\n scope?: \"subpages\" | \"hostname\" | \"domain\";\n scrapeMode?: ScrapeMode;\n followRedirects?: \"on\" | undefined; // Checkbox value is 'on' if checked\n ignoreErrors?: \"on\" | undefined;\n includePatterns?: string;\n excludePatterns?: string;\n \"header[]\"?: string[] | string; // Added header field for custom headers\n };\n }>,\n reply\n ) => {\n const body = request.body;\n reply.type(\"text/html\"); // Set content type for all responses from this handler\n try {\n // Basic validation\n if (!body.url || !body.library) {\n reply.status(400);\n // Use Alert component for validation error\n return (\n <Alert\n type=\"error\"\n title=\"Validation Error:\"\n message=\"URL and Library Name are required.\"\n />\n );\n }\n\n // Parse includePatterns and excludePatterns from textarea input\n function parsePatterns(input?: string): string[] | undefined {\n if (!input) return undefined;\n return input\n .split(/\\n|,/)\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n }\n\n // Parse custom headers from repeated header[] fields (format: name:value)\n function parseHeaders(\n input?: string[] | string\n ): Record<string, string> | undefined {\n if (!input) return undefined;\n const arr = Array.isArray(input) ? input : [input];\n const headers: Record<string, string> = {};\n for (const entry of arr) {\n const idx = entry.indexOf(\":\");\n if (idx > 0) {\n const name = entry.slice(0, idx).trim();\n const value = entry.slice(idx + 1).trim();\n if (name) headers[name] = value;\n }\n }\n return Object.keys(headers).length > 0 ? headers : undefined;\n }\n\n // Prepare options for ScrapeTool\n const scrapeOptions = {\n url: body.url,\n library: body.library,\n version: body.version || null, // Handle empty string as null\n waitForCompletion: false, // Don't wait in UI\n options: {\n maxPages: body.maxPages\n ? Number.parseInt(body.maxPages, 10)\n : undefined,\n maxDepth: body.maxDepth\n ? Number.parseInt(body.maxDepth, 10)\n : undefined,\n scope: body.scope,\n scrapeMode: body.scrapeMode,\n // Checkboxes send 'on' when checked, otherwise undefined\n followRedirects: body.followRedirects === \"on\",\n ignoreErrors: body.ignoreErrors === \"on\",\n includePatterns: parsePatterns(body.includePatterns),\n excludePatterns: parsePatterns(body.excludePatterns),\n headers: parseHeaders(body[\"header[]\"]), // <-- propagate custom headers from web UI\n },\n };\n\n // Execute the scrape tool\n const result = await scrapeTool.execute(scrapeOptions);\n\n if (\"jobId\" in result) {\n // Success: Use Alert component and OOB swap\n return (\n <>\n {/* Main target response */}\n <Alert\n type=\"success\"\n message={\n <>\n Job queued successfully! ID:{\" \"}\n <span safe>{result.jobId}</span>\n </>\n }\n />\n {/* OOB target response - contains only the inner form content */}\n <div id=\"scrape-form-container\" hx-swap-oob=\"innerHTML\">\n <ScrapeFormContent />\n </div>\n </>\n );\n }\n\n // This case shouldn't happen with waitForCompletion: false, but handle defensively\n // Use Alert component for unexpected success\n return (\n <Alert type=\"warning\" message=\"Job finished unexpectedly quickly.\" />\n );\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : \"Unknown error\";\n logger.error(`Scrape job submission failed: ${error}`);\n reply.status(500); // Keep status code for errors\n // Use Alert component for server error\n return (\n <Alert\n type=\"error\"\n message={<>Failed to queue job: {errorMessage}</>}\n />\n );\n }\n }\n );\n}\n","import type { LibraryVersionDetails } from \"../../store/types\";\nimport VersionBadge from \"./VersionBadge\"; // Adjusted import path\nimport LoadingSpinner from \"./LoadingSpinner\"; // Import spinner\n\n/**\n * Props for the VersionDetailsRow component.\n */\ninterface VersionDetailsRowProps {\n version: LibraryVersionDetails;\n libraryName: string;\n showDelete?: boolean; // Optional prop to control delete button visibility\n}\n\n/**\n * Renders details for a single library version in a row format.\n * Includes version, stats, and an optional delete button.\n * @param props - Component props including version, libraryName, and showDelete flag.\n */\nconst VersionDetailsRow = ({\n version,\n libraryName,\n showDelete = true, // Default to true\n}: VersionDetailsRowProps) => {\n // Format the indexed date nicely, handle null case\n const indexedDate = version.indexedAt\n ? new Date(version.indexedAt).toLocaleDateString()\n : \"N/A\";\n // Display 'Unversioned' if version string is empty\n const versionLabel = version.version || \"Unversioned\";\n // Use empty string for unversioned in param and rowId\n const versionParam = version.version || \"\";\n\n // Sanitize both libraryName and versionParam for valid CSS selector\n const sanitizedLibraryName = libraryName.replace(/[^a-zA-Z0-9-_]/g, \"-\");\n const sanitizedVersionParam = versionParam.replace(/[^a-zA-Z0-9-_]/g, \"-\");\n const rowId = `row-${sanitizedLibraryName}-${sanitizedVersionParam}`;\n\n // Define state-specific button classes for Alpine toggling\n const defaultStateClasses =\n \"text-red-700 border border-red-700 hover:bg-red-700 hover:text-white focus:ring-4 focus:outline-none focus:ring-red-300 dark:border-red-500 dark:text-red-500 dark:hover:text-white dark:focus:ring-red-800 dark:hover:bg-red-500\";\n const confirmingStateClasses =\n \"bg-red-600 text-white border-red-600 focus:ring-4 focus:outline-none focus:ring-red-300 dark:bg-red-700 dark:border-red-700 dark:focus:ring-red-800\";\n\n return (\n // Use flexbox for layout, add border between rows\n <div\n id={rowId}\n class=\"flex justify-between items-center py-1 border-b border-gray-200 dark:border-gray-600 last:border-b-0\"\n >\n {/* Version Label */}\n <span\n class=\"text-sm text-gray-900 dark:text-white w-1/4 truncate\"\n title={versionLabel}\n >\n {version.version ? (\n <VersionBadge version={version.version} />\n ) : (\n <span>Unversioned</span>\n )}\n </span>\n\n {/* Stats Group */}\n <div class=\"flex space-x-2 text-sm text-gray-600 dark:text-gray-400 w-3/4 justify-end items-center\">\n <span title=\"Number of unique pages indexed\">\n Pages:{\" \"}\n <span class=\"font-semibold\" safe>\n {version.uniqueUrlCount.toLocaleString()}\n </span>\n </span>\n <span title=\"Number of indexed snippets\">\n Snippets:{\" \"}\n <span class=\"font-semibold\" safe>\n {version.documentCount.toLocaleString()}\n </span>\n </span>\n <span title=\"Date last indexed\">\n Last Update:{\" \"}\n <span class=\"font-semibold\" safe>\n {indexedDate}\n </span>\n </span>\n </div>\n\n {/**\n * Conditionally renders a delete button for the version row.\n * The button has three states:\n * 1. Default: Displays a trash icon.\n * 2. Confirming: Displays a confirmation text with an accessible label.\n * 3. Deleting: Displays a spinner icon indicating the deletion process.\n * The button uses AlpineJS for state management and htmx for server interaction.\n */}\n {showDelete && (\n <button\n type=\"button\"\n class=\"ml-2 font-medium rounded-lg text-sm p-1 text-center inline-flex items-center transition-colors duration-150 ease-in-out\"\n title=\"Remove this version\"\n x-data=\"{}\"\n x-bind:class={`$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' ? '${confirmingStateClasses}' : '${defaultStateClasses}'`}\n x-bind:disabled={`$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && $store.confirmingAction.isDeleting`}\n x-on:click={`\n if ($store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}') {\n $store.confirmingAction.isDeleting = true;\n $el.dispatchEvent(new CustomEvent('confirmed-delete', { bubbles: true }));\n } else {\n if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }\n $store.confirmingAction.type = 'version-delete';\n $store.confirmingAction.id = '${libraryName}:${versionParam}';\n $store.confirmingAction.isDeleting = false;\n $store.confirmingAction.timeoutId = setTimeout(() => {\n $store.confirmingAction.type = null;\n $store.confirmingAction.id = null;\n $store.confirmingAction.isDeleting = false;\n $store.confirmingAction.timeoutId = null;\n }, 3000);\n }\n `}\n hx-delete={`/api/libraries/${encodeURIComponent(libraryName)}/versions/${encodeURIComponent(versionParam)}`}\n hx-target={`#${rowId}`}\n hx-swap=\"outerHTML\"\n hx-trigger=\"confirmed-delete\"\n >\n {/* Default State: Trash Icon */}\n <span\n x-show={`!($store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && $store.confirmingAction.isDeleting)`}\n >\n <svg\n class=\"w-4 h-4\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 18 20\"\n >\n <path\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M1 5h16M7 8v8m4-8v8M7 1h4a1 1 0 0 1 1 1v3H6V2a1 1 0 0 1-1-1ZM3 5h12v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5Z\"\n />\n </svg>\n <span class=\"sr-only\">Remove version</span>\n </span>\n\n {/* Confirming State: Text */}\n <span\n x-show={`$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && !$store.confirmingAction.isDeleting`}\n >\n Confirm?<span class=\"sr-only\">Confirm delete</span>\n </span>\n\n {/* Deleting State: Spinner Icon */}\n <span\n x-show={`$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && $store.confirmingAction.isDeleting`}\n >\n <LoadingSpinner />\n <span class=\"sr-only\">Loading...</span>\n </span>\n </button>\n )}\n </div>\n );\n};\n\nexport default VersionDetailsRow;\n","import type { LibraryInfo } from \"../../tools/ListLibrariesTool\";\nimport VersionDetailsRow from \"./VersionDetailsRow\"; // Adjusted import path\n\n/**\n * Props for the LibraryDetailCard component.\n */\ninterface LibraryDetailCardProps {\n library: LibraryInfo;\n}\n\n/**\n * Renders a card displaying library details and its versions.\n * Uses VersionDetailsRow without the delete button.\n * @param props - Component props including the library information.\n */\nconst LibraryDetailCard = ({ library }: LibraryDetailCardProps) => (\n // Use Flowbite Card structure with updated padding and border, and white background\n <div class=\"block p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-300 dark:border-gray-600 mb-4\">\n <h3 class=\"text-lg font-medium text-gray-900 dark:text-white mb-1\">\n <span safe>{library.name}</span>\n </h3>\n {/* Container for version rows */}\n <div class=\"mt-1\">\n {library.versions.length > 0 ? (\n library.versions.map((version) => (\n <VersionDetailsRow\n libraryName={library.name}\n version={version}\n showDelete={false} // Explicitly hide delete button\n />\n ))\n ) : (\n // Display message if no versions are indexed\n <p class=\"text-sm text-gray-500 dark:text-gray-400 italic\">\n No versions indexed.\n </p>\n )}\n </div>\n </div>\n);\n\nexport default LibraryDetailCard;\n","import type { LibraryInfo } from \"../../tools/ListLibrariesTool\";\nimport LoadingSpinner from \"./LoadingSpinner\"; // Import spinner\n\n/**\n * Props for the LibrarySearchCard component.\n */\ninterface LibrarySearchCardProps {\n library: LibraryInfo;\n}\n\n/**\n * Renders the search form card for a specific library.\n * Includes a version dropdown and query input.\n * @param props - Component props including the library information.\n */\nconst LibrarySearchCard = ({ library }: LibrarySearchCardProps) => {\n return (\n <div class=\"block p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-300 dark:border-gray-600 mb-4\">\n <h2 class=\"text-xl font-semibold mb-2 text-gray-900 dark:text-white\" safe>\n Search {library.name} Documentation\n </h2>\n <form\n hx-get={`/api/libraries/${encodeURIComponent(library.name)}/search`}\n hx-target=\"#searchResultsContainer .search-results\"\n hx-swap=\"innerHTML\"\n hx-indicator=\"#searchResultsContainer\"\n class=\"flex space-x-2\"\n >\n <select\n name=\"version\"\n class=\"w-40 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\"\n >\n <option value=\"\">Latest</option> {/* Default to latest */}\n {library.versions.map((version) => (\n <option value={version.version || \"unversioned\"} safe>\n {version.version || \"Unversioned\"}\n </option>\n ))}\n </select>\n <input\n type=\"text\"\n name=\"query\"\n placeholder=\"Search query...\"\n required\n class=\"flex-grow bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\"\n />\n <button\n type=\"submit\"\n class=\"text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 relative\"\n >\n <span class=\"search-text\">Search</span>\n {/* Spinner for HTMX loading - shown via htmx-indicator class on parent */}\n <span class=\"spinner absolute inset-0 flex items-center justify-center\">\n <LoadingSpinner />\n </span>\n </button>\n </form>\n {/* Add style for htmx-indicator behavior on button */}\n {/* Styles moved to Layout.tsx */}\n </div>\n );\n};\n\nexport default LibrarySearchCard;\n","import { unified } from \"unified\"; // Import unified\nimport remarkParse from \"remark-parse\"; // Import unified plugins\nimport remarkGfm from \"remark-gfm\";\nimport remarkHtml from \"remark-html\";\nimport DOMPurify from \"dompurify\"; // Import DOMPurify\nimport { createJSDOM } from \"../../utils/dom\"; // Import JSDOM helper\nimport type { StoreSearchResult } from \"../../store/types\";\n\n/**\n * Props for the SearchResultItem component.\n */\ninterface SearchResultItemProps {\n result: StoreSearchResult;\n}\n\n/**\n * Renders a single search result item.\n * Converts markdown content to HTML using unified.\n * @param props - Component props including the search result data.\n */\nconst SearchResultItem = async ({ result }: SearchResultItemProps) => {\n // Use unified pipeline to convert markdown to HTML\n const processor = unified().use(remarkParse).use(remarkGfm).use(remarkHtml);\n const file = await processor.process(result.content);\n const rawHtml = String(file);\n\n // Create JSDOM instance and initialize DOMPurify\n const jsdom = createJSDOM(\"\");\n const purifier = DOMPurify(jsdom.window);\n\n // Sanitize the HTML content\n const safeHtml = purifier.sanitize(rawHtml);\n\n return (\n <div class=\"block px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-300 dark:border-gray-600 mb-2\">\n <div class=\"text-sm text-gray-600 dark:text-gray-400 mb-1\">\n <a\n href={result.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline underline-offset-4\"\n safe\n >\n {result.url}\n </a>\n </div>\n {/* Render the sanitized HTML content */}\n <div class=\"format dark:format-invert max-w-none\">{safeHtml}</div>\n </div>\n );\n};\n\nexport default SearchResultItem;\n","import type { StoreSearchResult } from \"../../store/types\";\nimport SearchResultItem from \"./SearchResultItem\"; // Adjusted import path\n\n/**\n * Props for the SearchResultList component.\n */\ninterface SearchResultListProps {\n results: StoreSearchResult[];\n}\n\n/**\n * Renders the list of search results using SearchResultItem.\n * Displays a message if no results are found.\n * @param props - Component props including the array of search results.\n */\nconst SearchResultList = ({ results }: SearchResultListProps) => {\n if (results.length === 0) {\n return (\n <p class=\"text-gray-500 dark:text-gray-400 italic\">No results found.</p>\n );\n }\n return (\n <div class=\"space-y-2\">\n {results.map((result) => (\n <SearchResultItem result={result} />\n ))}\n </div>\n );\n};\n\nexport default SearchResultList;\n","/**\n * Renders a skeleton placeholder for a search result item.\n * Used to indicate loading state while search results are being fetched.\n */\nconst SearchResultSkeletonItem = () => (\n <div class=\"block px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm mb-2 animate-pulse\">\n <div class=\"h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-3/4 mb-2\"></div>\n <div class=\"h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-full mb-2\"></div>\n <div class=\"h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-5/6\"></div>\n </div>\n);\n\nexport default SearchResultSkeletonItem;\n","import type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport type { ListLibrariesTool } from \"../../../tools/ListLibrariesTool\";\nimport { SearchTool } from \"../../../tools/SearchTool\";\nimport Layout from \"../../components/Layout\";\nimport LibraryDetailCard from \"../../components/LibraryDetailCard\";\nimport LibrarySearchCard from \"../../components/LibrarySearchCard\";\nimport SearchResultList from \"../../components/SearchResultList\";\nimport SearchResultSkeletonItem from \"../../components/SearchResultSkeletonItem\";\n\n/**\n * Registers the route for displaying library details.\n * @param server - The Fastify instance.\n * @param listLibrariesTool - The tool instance for listing libraries.\n * @param searchTool - The tool instance for searching documentation.\n */\nexport function registerLibraryDetailRoutes(\n server: FastifyInstance,\n listLibrariesTool: ListLibrariesTool,\n searchTool: SearchTool\n) {\n // Route for the library detail page\n server.get(\n \"/libraries/:libraryName\",\n async (\n request: FastifyRequest<{ Params: { libraryName: string } }>,\n reply: FastifyReply\n ) => {\n const { libraryName } = request.params;\n try {\n // Fetch all libraries and find the requested one\n const result = await listLibrariesTool.execute();\n const libraryInfo = result.libraries.find(\n (lib) => lib.name === libraryName\n );\n\n if (!libraryInfo) {\n reply.status(404).send(\"Library not found\");\n return;\n }\n\n reply.type(\"text/html; charset=utf-8\");\n // Use the Layout component\n return (\n \"<!DOCTYPE html>\" +\n (\n <Layout title={`MCP Docs - ${libraryInfo.name}`}>\n {/* Library Detail Card */}\n <LibraryDetailCard library={libraryInfo} />\n\n {/* Library Search Card */}\n <LibrarySearchCard library={libraryInfo} />\n\n {/* Search Results Container */}\n <div id=\"searchResultsContainer\">\n {/* Skeleton loader - Initially present */}\n <div class=\"search-skeleton space-y-2\">\n <SearchResultSkeletonItem />\n <SearchResultSkeletonItem />\n <SearchResultSkeletonItem />\n </div>\n {/* Search results will be loaded here via HTMX */}\n <div class=\"search-results\">\n {/* Initially empty, HTMX will swap content here */}\n </div>\n </div>\n </Layout>\n )\n );\n } catch (error) {\n server.log.error(\n error,\n `Failed to load library details for ${libraryName}`\n );\n reply.status(500).send(\"Internal Server Error\");\n }\n }\n );\n\n // API route for searching a specific library\n server.get(\n \"/api/libraries/:libraryName/search\",\n async (\n request: FastifyRequest<{\n Params: { libraryName: string };\n Querystring: { query: string; version?: string };\n }>,\n reply: FastifyReply\n ) => {\n const { libraryName } = request.params;\n const { query, version } = request.query;\n\n if (!query) {\n reply.status(400).send(\"Search query is required.\");\n return;\n }\n\n // Map \"unversioned\" string to undefined for the tool\n const versionParam = version === \"unversioned\" ? undefined : version;\n\n try {\n const searchResult = await searchTool.execute({\n library: libraryName,\n query,\n version: versionParam,\n limit: 10, // Limit search results\n });\n\n // Return only the results list or error message\n reply.type(\"text/html; charset=utf-8\");\n return <SearchResultList results={searchResult.results} />;\n } catch (error) {\n server.log.error(error, `Failed to search library ${libraryName}`);\n // Return error message on catch\n reply.type(\"text/html; charset=utf-8\");\n return (\n <p class=\"text-red-500 dark:text-red-400 italic\">\n An unexpected error occurred during the search.\n </p>\n );\n }\n }\n );\n}\n","import type { LibraryInfo } from \"../../tools/ListLibrariesTool\";\nimport VersionDetailsRow from \"./VersionDetailsRow\"; // Adjusted import path\n\n/**\n * Props for the LibraryItem component.\n */\ninterface LibraryItemProps {\n library: LibraryInfo;\n}\n\n/**\n * Renders a card for a single library, listing its versions with details.\n * Uses VersionDetailsRow to display each version.\n * @param props - Component props including the library information.\n */\nconst LibraryItem = ({ library }: LibraryItemProps) => (\n // Use Flowbite Card structure with updated padding and border, and white background\n <div class=\"block px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-300 dark:border-gray-600\">\n <h3 class=\"text-lg font-medium text-gray-900 dark:text-white mb-1\">\n <a\n href={`/libraries/${encodeURIComponent(library.name)}`}\n class=\"hover:underline\"\n >\n <span safe>{library.name}</span>\n </a>\n </h3>\n {/* Container for version rows */}\n <div class=\"mt-1\">\n {library.versions.length > 0 ? (\n library.versions.map((version) => (\n <VersionDetailsRow libraryName={library.name} version={version} />\n ))\n ) : (\n // Display message if no versions are indexed\n <p class=\"text-sm text-gray-500 dark:text-gray-400 italic\">\n No versions indexed.\n </p>\n )}\n </div>\n </div>\n);\n\nexport default LibraryItem;\n","import type { LibraryInfo } from \"../../tools/ListLibrariesTool\";\nimport LibraryItem from \"./LibraryItem\"; // Adjusted import path\n\n/**\n * Props for the LibraryList component.\n */\ninterface LibraryListProps {\n libraries: LibraryInfo[];\n}\n\n/**\n * Renders a list of LibraryItem components.\n * @param props - Component props including the array of libraries.\n */\nconst LibraryList = ({ libraries }: LibraryListProps) => {\n return (\n <>\n <div class=\"space-y-2\">\n {libraries.map((library) => (\n <LibraryItem library={library} />\n ))}\n </div>\n </>\n );\n};\n\nexport default LibraryList;\n","import type { FastifyInstance } from \"fastify\";\nimport type { ListLibrariesTool } from \"../../../tools/ListLibrariesTool\";\nimport { RemoveTool } from \"../../../tools\";\nimport LibraryList from \"../../components/LibraryList\";\n\n/**\n * Registers the API routes for library management.\n * @param server - The Fastify instance.\n * @param listLibrariesTool - The tool instance for listing libraries.\n * @param removeTool - The tool instance for removing library versions.\n */\nexport function registerLibrariesRoutes(\n server: FastifyInstance,\n listLibrariesTool: ListLibrariesTool,\n removeTool: RemoveTool // Accept RemoveTool\n) {\n server.get(\"/api/libraries\", async (_request, reply) => {\n // Add reply\n try {\n const result = await listLibrariesTool.execute();\n // Set content type to HTML for JSX rendering\n reply.type(\"text/html; charset=utf-8\");\n // Render the component directly\n return <LibraryList libraries={result.libraries} />;\n } catch (error) {\n server.log.error(error, \"Failed to list libraries\");\n reply.status(500).send(\"Internal Server Error\"); // Handle errors\n }\n });\n\n // Add DELETE route for removing versions\n server.delete<{ Params: { libraryName: string; versionParam: string } }>(\n \"/api/libraries/:libraryName/versions/:versionParam\",\n async (request, reply) => {\n const { libraryName, versionParam } = request.params;\n const version = versionParam === \"unversioned\" ? undefined : versionParam;\n try {\n await removeTool.execute({ library: libraryName, version });\n reply.status(204).send(); // No Content on success\n } catch (error: any) {\n server.log.error(\n error,\n `Failed to remove ${libraryName}@${versionParam}`\n );\n // Check for specific errors if needed, e.g., NotFoundError\n reply\n .status(500)\n .send({ message: error.message || \"Failed to remove version.\" });\n }\n }\n );\n}\n","import path from \"node:path\";\nimport formBody from \"@fastify/formbody\";\nimport fastifyStatic from \"@fastify/static\";\nimport Fastify, { type FastifyInstance } from \"fastify\";\nimport type { PipelineManager } from \"../pipeline/PipelineManager\";\nimport type { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport { SearchTool } from \"../tools\";\nimport { CancelJobTool } from \"../tools/CancelJobTool\";\nimport { ClearCompletedJobsTool } from \"../tools/ClearCompletedJobsTool\";\nimport { ListJobsTool } from \"../tools/ListJobsTool\";\nimport { ListLibrariesTool } from \"../tools/ListLibrariesTool\";\nimport { RemoveTool } from \"../tools/RemoveTool\";\nimport { ScrapeTool } from \"../tools/ScrapeTool\";\nimport { logger } from \"../utils/logger\";\nimport { getProjectRoot } from \"../utils/paths\";\nimport { registerIndexRoute } from \"./routes/index\";\nimport { registerCancelJobRoute } from \"./routes/jobs/cancel\";\nimport { registerClearCompletedJobsRoute } from \"./routes/jobs/clear-completed\";\nimport { registerJobListRoutes } from \"./routes/jobs/list\";\nimport { registerNewJobRoutes } from \"./routes/jobs/new\";\nimport { registerLibraryDetailRoutes } from \"./routes/libraries/detail\";\nimport { registerLibrariesRoutes } from \"./routes/libraries/list\";\n\n/**\n * Initializes the Fastify web server instance.\n *\n * @param port The port number for the web server.\n * @param docService The document management service instance.\n * @param pipelineManager The pipeline manager instance.\n * @returns The initialized Fastify server instance.\n */\nexport async function startWebServer(\n port: number,\n docService: DocumentManagementService,\n pipelineManager: PipelineManager,\n): Promise<FastifyInstance> {\n const server = Fastify({\n logger: false, // Use our own logger instead\n });\n\n // Register plugins\n await server.register(formBody); // Register formbody to parse form data\n\n // Instantiate tools using provided services\n const listLibrariesTool = new ListLibrariesTool(docService);\n const listJobsTool = new ListJobsTool(pipelineManager);\n const scrapeTool = new ScrapeTool(docService, pipelineManager);\n const removeTool = new RemoveTool(docService, pipelineManager);\n const searchTool = new SearchTool(docService);\n const cancelJobTool = new CancelJobTool(pipelineManager);\n const clearCompletedJobsTool = new ClearCompletedJobsTool(pipelineManager);\n\n // Register static file serving\n await server.register(fastifyStatic, {\n // Use project root to construct absolute path to public directory\n root: path.join(getProjectRoot(), \"public\"),\n prefix: \"/\",\n index: false, // Disable automatic index.html serving\n });\n\n // Register routes\n registerIndexRoute(server); // Register the root route first\n registerJobListRoutes(server, listJobsTool);\n registerNewJobRoutes(server, scrapeTool);\n registerCancelJobRoute(server, cancelJobTool);\n registerClearCompletedJobsRoute(server, clearCompletedJobsTool);\n registerLibrariesRoutes(server, listLibrariesTool, removeTool);\n registerLibraryDetailRoutes(server, listLibrariesTool, searchTool);\n\n // Graceful shutdown of services will be handled by the caller (src/index.ts)\n\n try {\n const address = await server.listen({ port, host: \"0.0.0.0\" });\n logger.info(`🚀 Web UI available at ${address}`);\n return server; // Return the server instance\n } catch (error) {\n logger.error(`❌ Failed to start web UI: ${error}`);\n // Ensure server is closed if listen fails but initialization succeeded partially\n await server.close();\n throw error;\n }\n}\n\n/**\n * Stops the provided Fastify web server instance.\n *\n * @param server - The Fastify server instance to stop.\n */\nexport async function stopWebServer(server: FastifyInstance): Promise<void> {\n try {\n await server.close();\n logger.info(\"🛑 Web UI stopped.\");\n } catch (error) {\n logger.error(`❌ Failed to stop web server gracefully: ${error}`);\n // Rethrow or handle as needed, but ensure the process doesn't hang\n throw error;\n }\n}\n","#!/usr/bin/env node\nimport { execSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport \"dotenv/config\";\nimport { Command } from \"commander\";\nimport type { FastifyInstance } from \"fastify\";\nimport { chromium, devices } from \"playwright\";\nimport packageJson from \"../package.json\";\nimport { startServer as startMcpServer, stopServer as stopMcpServer } from \"./mcp\";\nimport { PipelineManager } from \"./pipeline/PipelineManager\";\nimport { FileFetcher, HttpFetcher } from \"./scraper/fetcher\";\nimport { ScrapeMode } from \"./scraper/types\";\nimport { DocumentManagementService } from \"./store/DocumentManagementService\";\nimport {\n FetchUrlTool,\n FindVersionTool,\n ListLibrariesTool,\n ScrapeTool,\n SearchTool,\n} from \"./tools\";\nimport {\n DEFAULT_HTTP_PORT,\n DEFAULT_MAX_CONCURRENCY,\n DEFAULT_MAX_DEPTH,\n DEFAULT_MAX_PAGES,\n DEFAULT_PROTOCOL,\n DEFAULT_WEB_PORT,\n} from \"./utils/config\";\nimport { LogLevel, logger, setLogLevel } from \"./utils/logger\";\nimport { getProjectRoot } from \"./utils/paths\";\nimport { startWebServer, stopWebServer } from \"./web/web\";\n\n/**\n * Ensures that the Playwright browsers are installed, unless a system Chromium path is set.\n */\nfunction ensurePlaywrightBrowsersInstalled(): void {\n // If PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH is set, skip install\n const chromiumEnvPath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH;\n if (chromiumEnvPath && existsSync(chromiumEnvPath)) {\n logger.debug(\n `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH is set to '${chromiumEnvPath}', skipping Playwright browser install.`,\n );\n return;\n }\n try {\n // Dynamically require Playwright and check for Chromium browser\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const chromiumPath = chromium.executablePath();\n if (!chromiumPath || !existsSync(chromiumPath)) {\n throw new Error(\"Playwright Chromium browser not found\");\n }\n } catch (err) {\n // Not installed or not found, attempt to install\n logger.debug(\n \"Playwright browsers not found. Installing Chromium browser for dynamic scraping (this may take a minute)...\",\n );\n try {\n logger.debug(\"Installing Playwright Chromium browser...\");\n execSync(\"npm exec -y playwright install --no-shell --with-deps chromium\", {\n stdio: \"ignore\", // Suppress output\n cwd: getProjectRoot(),\n });\n } catch (installErr) {\n console.error(\n \"❌ Failed to install Playwright browsers automatically. Please run:\\n npx playwright install --no-shell --with-deps chromium\\nand try again.\",\n );\n process.exit(1);\n }\n }\n}\n\nensurePlaywrightBrowsersInstalled();\n\nconst formatOutput = (data: unknown) => JSON.stringify(data, null, 2);\n\n// Module-level variables for server instances and shutdown state\nlet mcpServerRunning = false;\nlet webServerInstance: FastifyInstance | null = null;\nlet activeDocService: DocumentManagementService | null = null;\nlet activePipelineManager: PipelineManager | null = null;\n\nlet isShuttingDown = false; // Use a module-level boolean\n\nconst sigintHandler = async () => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n\n logger.debug(\"Received SIGINT. Shutting down gracefully...\");\n try {\n if (webServerInstance) {\n logger.debug(\"SIGINT: Stopping web server...\");\n await stopWebServer(webServerInstance);\n webServerInstance = null;\n logger.debug(\"SIGINT: Web server stopped.\");\n }\n if (mcpServerRunning) {\n logger.debug(\"SIGINT: Stopping MCP server instance...\");\n await stopMcpServer(); // This now only stops the McpServer object\n mcpServerRunning = false;\n logger.debug(\"SIGINT: MCP server instance stopped.\");\n }\n\n // Shutdown active services\n logger.debug(\"SIGINT: Shutting down active services...\");\n if (activePipelineManager) {\n await activePipelineManager.stop();\n activePipelineManager = null;\n logger.debug(\"SIGINT: PipelineManager stopped.\");\n }\n if (activeDocService) {\n await activeDocService.shutdown();\n activeDocService = null;\n logger.debug(\"SIGINT: DocumentManagementService shut down.\");\n }\n } catch (e) {\n logger.error(`❌ Error during SIGINT shutdown: ${e}`);\n } finally {\n logger.debug(\"SIGINT: Shutdown process completed.\");\n process.exit(0);\n }\n};\n\nasync function main() {\n let commandExecuted = false;\n // The module-level 'isShuttingDown' is initialized to false.\n // HMR handler will reset it for new module instances if HMR is active.\n // For a standard run, it's reset here.\n isShuttingDown = false;\n\n // Ensure only one SIGINT handler is active for this process instance,\n // especially important across HMR cycles if dispose isn't perfect.\n process.removeListener(\"SIGINT\", sigintHandler);\n process.on(\"SIGINT\", sigintHandler);\n\n // Helper functions for service initialization (for long-running modes)\n async function ensureDocServiceInitialized(): Promise<DocumentManagementService> {\n if (!activeDocService) {\n logger.debug(\"Initializing DocumentManagementService for server mode...\");\n const service = new DocumentManagementService();\n await service.initialize();\n activeDocService = service;\n logger.debug(\"DocumentManagementService initialized for server mode.\");\n }\n return activeDocService;\n }\n\n async function ensurePipelineManagerInitialized(): Promise<PipelineManager> {\n const ds = await ensureDocServiceInitialized(); // Depends on DocService\n if (!activePipelineManager) {\n logger.debug(\"Initializing PipelineManager for server mode...\");\n const manager = new PipelineManager(ds);\n await manager.start();\n activePipelineManager = manager;\n logger.debug(\"PipelineManager initialized for server mode.\");\n }\n return activePipelineManager;\n }\n\n try {\n const program = new Command();\n\n program\n .name(\"docs-mcp-server\")\n .description(\"Unified CLI, MCP Server, and Web Interface for Docs MCP Server.\")\n .version(packageJson.version)\n .option(\"--verbose\", \"Enable verbose (debug) logging\", false)\n .option(\"--silent\", \"Disable all logging except errors\", false)\n .enablePositionalOptions()\n .showHelpAfterError(true)\n .option(\n \"--protocol <type>\",\n \"Protocol for MCP server (stdio or http)\",\n DEFAULT_PROTOCOL,\n )\n .option(\n \"--port <number>\",\n \"Port for MCP server (if http protocol)\",\n DEFAULT_HTTP_PORT.toString(),\n );\n\n program.hook(\"preAction\", (thisCommand, actionCommand) => {\n const globalOptions = thisCommand.opts();\n if (globalOptions.silent) setLogLevel(LogLevel.ERROR);\n else if (globalOptions.verbose) setLogLevel(LogLevel.DEBUG);\n if (actionCommand.name() !== program.name()) commandExecuted = true;\n });\n\n program\n .command(\"web\")\n .description(\"Start the web interface\")\n .option(\n \"--port <number>\",\n \"Port for the web interface\",\n DEFAULT_WEB_PORT.toString(),\n )\n .action(async (cmdOptions) => {\n commandExecuted = true;\n const docService = await ensureDocServiceInitialized();\n const pipelineManager = await ensurePipelineManagerInitialized();\n const port = Number.parseInt(cmdOptions.port, 10);\n if (Number.isNaN(port)) {\n console.error(\"Web port must be a number.\");\n process.exit(1);\n }\n webServerInstance = await startWebServer(port, docService, pipelineManager);\n await new Promise(() => {}); // Keep alive\n });\n\n // --- Scrape Command ---\n program\n .command(\"scrape <library> <url>\")\n .description(\n \"Scrape and index documentation from a URL or local folder.\\n\\n\" +\n \"To scrape local files or folders, use a file:// URL.\\n\" +\n \"Examples:\\n\" +\n \" scrape mylib https://react.dev/reference/react\\n\" +\n \" scrape mylib file:///Users/me/docs/index.html\\n\" +\n \" scrape mylib file:///Users/me/docs/my-library\\n\" +\n \"\\nNote: For local files/folders, you must use the file:// prefix. If running in Docker, mount the folder and use the container path. See README for details.\",\n )\n .option(\"-v, --version <string>\", \"Version of the library (optional)\")\n .option(\n \"-p, --max-pages <number>\",\n \"Maximum pages to scrape\",\n DEFAULT_MAX_PAGES.toString(),\n )\n .option(\n \"-d, --max-depth <number>\",\n \"Maximum navigation depth\",\n DEFAULT_MAX_DEPTH.toString(),\n )\n .option(\n \"-c, --max-concurrency <number>\",\n \"Maximum concurrent page requests\",\n DEFAULT_MAX_CONCURRENCY.toString(),\n )\n .option(\"--ignore-errors\", \"Ignore errors during scraping\", true)\n .option(\n \"--scope <scope>\",\n \"Crawling boundary: 'subpages' (default), 'hostname', or 'domain'\",\n (value) => {\n const validScopes = [\"subpages\", \"hostname\", \"domain\"];\n if (!validScopes.includes(value)) {\n console.warn(`Warning: Invalid scope '${value}'. Using default 'subpages'.`);\n return \"subpages\";\n }\n return value;\n },\n \"subpages\",\n )\n .option(\n \"--no-follow-redirects\",\n \"Disable following HTTP redirects (default: follow redirects)\",\n )\n .option(\n \"--scrape-mode <mode>\",\n `HTML processing strategy: '${ScrapeMode.Fetch}', '${ScrapeMode.Playwright}', '${ScrapeMode.Auto}' (default)`,\n (value: string): ScrapeMode => {\n const validModes = Object.values(ScrapeMode);\n if (!validModes.includes(value as ScrapeMode)) {\n console.warn(\n `Warning: Invalid scrape mode '${value}'. Using default '${ScrapeMode.Auto}'.`,\n );\n return ScrapeMode.Auto;\n }\n return value as ScrapeMode;\n },\n ScrapeMode.Auto,\n )\n .option(\n \"--include-pattern <pattern>\",\n \"Glob or regex pattern for URLs to include (can be specified multiple times). Regex patterns must be wrapped in slashes, e.g. /pattern/.\",\n (val: string, prev: string[] = []) => prev.concat([val]),\n [] as string[],\n )\n .option(\n \"--exclude-pattern <pattern>\",\n \"Glob or regex pattern for URLs to exclude (can be specified multiple times, takes precedence over include). Regex patterns must be wrapped in slashes, e.g. /pattern/.\",\n (val: string, prev: string[] = []) => prev.concat([val]),\n [] as string[],\n )\n .option(\n \"--header <name:value>\",\n \"Custom HTTP header to send with each request (can be specified multiple times)\",\n (val: string, prev: string[] = []) => prev.concat([val]),\n [] as string[],\n )\n .action(async (library, url, options) => {\n commandExecuted = true; // Ensure this is set for CLI commands\n const docService = new DocumentManagementService();\n let pipelineManager: PipelineManager | null = null;\n try {\n await docService.initialize();\n pipelineManager = new PipelineManager(docService);\n await pipelineManager.start();\n const scrapeTool = new ScrapeTool(docService, pipelineManager);\n\n // Parse headers from CLI options\n const headers: Record<string, string> = {};\n if (Array.isArray(options.header)) {\n for (const entry of options.header) {\n const idx = entry.indexOf(\":\");\n if (idx > 0) {\n const name = entry.slice(0, idx).trim();\n const value = entry.slice(idx + 1).trim();\n if (name) headers[name] = value;\n }\n }\n }\n\n const result = await scrapeTool.execute({\n url,\n library,\n version: options.version,\n options: {\n maxPages: Number.parseInt(options.maxPages),\n maxDepth: Number.parseInt(options.maxDepth),\n maxConcurrency: Number.parseInt(options.maxConcurrency),\n ignoreErrors: options.ignoreErrors,\n scope: options.scope,\n followRedirects: options.followRedirects,\n scrapeMode: options.scrapeMode,\n includePatterns:\n Array.isArray(options.includePattern) && options.includePattern.length > 0\n ? options.includePattern\n : undefined,\n excludePatterns:\n Array.isArray(options.excludePattern) && options.excludePattern.length > 0\n ? options.excludePattern\n : undefined,\n headers: Object.keys(headers).length > 0 ? headers : undefined,\n },\n });\n if (\"pagesScraped\" in result)\n console.log(`✅ Successfully scraped ${result.pagesScraped} pages`);\n else console.log(`🚀 Scraping job started with ID: ${result.jobId}`);\n } finally {\n if (pipelineManager) await pipelineManager.stop();\n await docService.shutdown();\n }\n });\n\n // --- Search Command ---\n program\n .command(\"search <library> <query>\")\n .description(\n \"Search documents in a library. Version matching examples:\\n\" +\n \" - search react --version 18.0.0 'hooks' -> matches docs for React 18.0.0 or earlier versions\\n\" +\n \" - search react --version 18.0.0 'hooks' --exact-match -> only matches React 18.0.0\\n\" +\n \" - search typescript --version 5.x 'types' -> matches any TypeScript 5.x.x version\\n\" +\n \" - search typescript --version 5.2.x 'types' -> matches any TypeScript 5.2.x version\",\n )\n .option(\n \"-v, --version <string>\",\n \"Version of the library (optional, supports ranges)\",\n )\n .option(\"-l, --limit <number>\", \"Maximum number of results\", \"5\")\n .option(\"-e, --exact-match\", \"Only use exact version match (default: false)\", false)\n .action(async (library, query, options) => {\n commandExecuted = true; // Ensure this is set\n const docService = new DocumentManagementService();\n try {\n await docService.initialize();\n const searchTool = new SearchTool(docService);\n const result = await searchTool.execute({\n library,\n version: options.version,\n query,\n limit: Number.parseInt(options.limit),\n exactMatch: options.exactMatch,\n });\n console.log(formatOutput(result.results));\n } finally {\n await docService.shutdown();\n }\n });\n\n // --- List Command ---\n program\n .command(\"list\")\n .description(\"List all available libraries and their versions\")\n .action(async () => {\n commandExecuted = true; // Ensure this is set\n const docService = new DocumentManagementService();\n try {\n await docService.initialize();\n const listLibrariesTool = new ListLibrariesTool(docService);\n const result = await listLibrariesTool.execute();\n console.log(formatOutput(result.libraries));\n } finally {\n await docService.shutdown();\n }\n });\n\n // --- Find Version Command ---\n program\n .command(\"find-version <library>\")\n .description(\"Find the best matching version for a library\")\n .option(\"-v, --version <string>\", \"Pattern to match (optional, supports ranges)\")\n .action(async (library, options) => {\n commandExecuted = true; // Ensure this is set\n const docService = new DocumentManagementService();\n try {\n await docService.initialize();\n const findVersionTool = new FindVersionTool(docService);\n const versionInfo = await findVersionTool.execute({\n library,\n targetVersion: options.version,\n });\n if (!versionInfo) throw new Error(\"Failed to get version information\");\n console.log(versionInfo);\n } finally {\n await docService.shutdown();\n }\n });\n\n // --- Remove Command ---\n program\n .command(\"remove <library>\")\n .description(\"Remove documents for a specific library and version\")\n .option(\n \"-v, --version <string>\",\n \"Version to remove (optional, removes unversioned if omitted)\",\n )\n .action(async (library, options) => {\n commandExecuted = true; // Ensure this is set\n const docService = new DocumentManagementService();\n const { version } = options;\n try {\n await docService.initialize();\n // No specific tool needed, direct service call\n await docService.removeAllDocuments(library, version);\n console.log(\n `✅ Successfully removed documents for ${library}${version ? `@${version}` : \" (unversioned)\"}.`,\n );\n } catch (error) {\n console.error(\n `❌ Failed to remove documents for ${library}${version ? `@${version}` : \" (unversioned)\"}:`,\n error instanceof Error ? error.message : String(error),\n );\n // Re-throw to allow main error handler to catch if necessary,\n // but ensure shutdown still happens in finally.\n throw error;\n } finally {\n await docService.shutdown();\n }\n });\n\n // --- Fetch URL Command ---\n program\n .command(\"fetch-url <url>\")\n .description(\"Fetch a URL and convert its content to Markdown\")\n .option(\n \"--no-follow-redirects\",\n \"Disable following HTTP redirects (default: follow redirects)\",\n )\n .option(\n \"--scrape-mode <mode>\",\n `HTML processing strategy: '${ScrapeMode.Fetch}', '${ScrapeMode.Playwright}', '${ScrapeMode.Auto}' (default)`,\n (value: string): ScrapeMode => {\n const validModes = Object.values(ScrapeMode);\n if (!validModes.includes(value as ScrapeMode)) {\n console.warn(\n `Warning: Invalid scrape mode '${value}'. Using default '${ScrapeMode.Auto}'.`,\n );\n return ScrapeMode.Auto;\n }\n return value as ScrapeMode;\n },\n ScrapeMode.Auto,\n )\n .option(\n \"--header <name:value>\",\n \"Custom HTTP header to send with the request (can be specified multiple times)\",\n (val: string, prev: string[] = []) => prev.concat([val]),\n [] as string[],\n )\n .action(async (url, options) => {\n commandExecuted = true; // Ensure this is set\n // Parse headers from CLI options\n const headers: Record<string, string> = {};\n if (Array.isArray(options.header)) {\n for (const entry of options.header) {\n const idx = entry.indexOf(\":\");\n if (idx > 0) {\n const name = entry.slice(0, idx).trim();\n const value = entry.slice(idx + 1).trim();\n if (name) headers[name] = value;\n }\n }\n }\n // FetchUrlTool does not require DocumentManagementService or PipelineManager\n const fetchUrlTool = new FetchUrlTool(new HttpFetcher(), new FileFetcher());\n const content = await fetchUrlTool.execute({\n url,\n followRedirects: options.followRedirects,\n scrapeMode: options.scrapeMode,\n headers: Object.keys(headers).length > 0 ? headers : undefined,\n });\n console.log(content);\n });\n\n program.action(async (options) => {\n if (!commandExecuted) {\n logger.debug(\"No subcommand specified, starting MCP server by default...\");\n const docService = await ensureDocServiceInitialized();\n const pipelineManager = await ensurePipelineManagerInitialized();\n const protocol = options.protocol as \"stdio\" | \"http\";\n const port = Number.parseInt(options.port, 10);\n if (protocol !== \"stdio\" && protocol !== \"http\") {\n console.error('Invalid protocol specified. Use \"stdio\" or \"http\".');\n process.exit(1);\n }\n if (protocol === \"http\" && Number.isNaN(port)) {\n console.error(\"Port must be a number when using http protocol.\");\n process.exit(1);\n }\n mcpServerRunning = true;\n // Pass docService and pipelineManager to the startServer from src/mcp/index.ts\n await startMcpServer(\n protocol,\n docService,\n pipelineManager,\n protocol === \"http\" ? port : undefined,\n );\n await new Promise(() => {});\n }\n });\n\n await program.parseAsync(process.argv);\n } catch (error) {\n logger.error(`❌ Error in main: ${error}`);\n if (!isShuttingDown) {\n isShuttingDown = true;\n if (webServerInstance) await stopWebServer(webServerInstance);\n if (mcpServerRunning) await stopMcpServer();\n // For other errors, attempt to shutdown active global services if they exist\n else {\n if (activePipelineManager) {\n await activePipelineManager.stop();\n activePipelineManager = null;\n }\n if (activeDocService) {\n await activeDocService.shutdown();\n activeDocService = null;\n }\n }\n }\n process.exit(1);\n }\n\n // This block handles cleanup for CLI commands that completed successfully\n // and were not long-running servers.\n // Since short-lived commands now manage their own service shutdown,\n // this block might not need to do anything with activeDocService/activePipelineManager,\n // as those are intended for server modes.\n if (commandExecuted && !webServerInstance && !mcpServerRunning) {\n if (!isShuttingDown) {\n // No active server mode services to shut down here,\n // as CLI commands handle their own.\n logger.debug(\n \"CLI command executed. No global services to shut down from this path.\",\n );\n }\n }\n}\n\nmain().catch((error) => {\n if (!isShuttingDown) {\n isShuttingDown = true; // Mark as shutting down\n logger.error(`🔥 Fatal error in main execution: ${error}`);\n // Attempt to shut down active global services\n const shutdownPromises = [];\n if (activePipelineManager) {\n shutdownPromises.push(\n activePipelineManager.stop().then(() => {\n activePipelineManager = null;\n }),\n );\n }\n if (activeDocService) {\n shutdownPromises.push(\n activeDocService.shutdown().then(() => {\n activeDocService = null;\n }),\n );\n }\n Promise.allSettled(shutdownPromises).catch((err) =>\n logger.error(`❌ Error during fatal shutdown cleanup: ${err}`),\n );\n }\n process.exit(1); // Ensure exit on fatal error\n});\n\n// Handle HMR for vite-node --watch\nif (import.meta.hot) {\n import.meta.hot.on(\"vite:beforeFullReload\", async () => {\n logger.info(\"🔥 Hot reload detected\");\n process.removeListener(\"SIGINT\", sigintHandler); // Remove for this outgoing instance\n\n // Set shutting down flag for HMR context\n const wasAlreadyShuttingDown = isShuttingDown; // Capture current state\n isShuttingDown = true; // Mark as shutting down for HMR cleanup\n\n try {\n if (webServerInstance) {\n logger.debug(\"Shutting down web server...\");\n await stopWebServer(webServerInstance);\n logger.debug(\"Web server shut down.\");\n }\n if (mcpServerRunning) {\n logger.debug(\"Shutting down MCP server...\");\n await stopMcpServer();\n logger.debug(\"MCP server shut down.\");\n }\n // Shut down active global services for HMR\n logger.debug(\"Shutting down active services...\");\n if (activePipelineManager) {\n await activePipelineManager.stop();\n activePipelineManager = null; // Reset for next instantiation\n logger.debug(\"PipelineManager stopped.\");\n }\n if (activeDocService) {\n await activeDocService.shutdown();\n activeDocService = null; // Reset for next instantiation\n logger.debug(\"DocumentManagementService shut down.\");\n }\n logger.debug(\"Active services shut down.\");\n } catch (hmrError) {\n logger.error(`❌ Error during HMR cleanup: ${hmrError}`);\n } finally {\n // Reset state for the next module instantiation\n webServerInstance = null;\n mcpServerRunning = false;\n // Only reset isShuttingDown if HMR itself initiated the shutdown state\n // and it wasn't already shutting down due to SIGINT or other error.\n if (!wasAlreadyShuttingDown) {\n isShuttingDown = false;\n }\n }\n });\n}\n"],"names":["LogLevel","PipelineJobStatus","semver","DEFAULT_MAX_PAGES","DEFAULT_MAX_DEPTH","ScrapeMode","version","path","DEFAULT_CONCURRENCY","URL","item","name","job","uuidv4","chunks","fs","projectRoot","packageJson","type","stopMcpServer","startMcpServer"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/utils/logger.ts","../src/pipeline/types.ts","../src/tools/CancelJobTool.ts","../src/tools/ClearCompletedJobsTool.ts","../src/tools/errors.ts","../src/utils/mimeTypeUtils.ts","../src/scraper/middleware/HtmlCheerioParserMiddleware.ts","../src/utils/dom.ts","../src/scraper/middleware/HtmlLinkExtractorMiddleware.ts","../src/scraper/middleware/HtmlMetadataExtractorMiddleware.ts","../src/utils/config.ts","../src/scraper/types.ts","../src/scraper/middleware/HtmlPlaywrightMiddleware.ts","../src/scraper/middleware/HtmlSanitizerMiddleware.ts","../src/scraper/middleware/HtmlToMarkdownMiddleware.ts","../src/scraper/middleware/MarkdownLinkExtractorMiddleware.ts","../src/scraper/middleware/MarkdownMetadataExtractorMiddleware.ts","../src/scraper/utils/charset.ts","../src/scraper/utils/buffer.ts","../src/scraper/pipelines/BasePipeline.ts","../src/scraper/pipelines/HtmlPipeline.ts","../src/scraper/pipelines/MarkdownPipeline.ts","../src/utils/errors.ts","../src/tools/FetchUrlTool.ts","../src/tools/FindVersionTool.ts","../src/tools/GetJobInfoTool.ts","../src/tools/ListJobsTool.ts","../src/tools/ListLibrariesTool.ts","../src/tools/RemoveTool.ts","../src/tools/ScrapeTool.ts","../src/tools/SearchTool.ts","../src/mcp/utils.ts","../src/mcp/mcpServer.ts","../src/scraper/fetcher/FileFetcher.ts","../src/pipeline/errors.ts","../src/scraper/fetcher/FingerprintGenerator.ts","../src/scraper/fetcher/HttpFetcher.ts","../src/mcp/tools.ts","../src/services/mcpService.ts","../src/pipeline/PipelineApiService.ts","../src/services/pipelineApiService.ts","../src/web/components/Layout.tsx","../src/web/routes/index.tsx","../src/web/routes/jobs/cancel.tsx","../src/web/routes/jobs/clear-completed.tsx","../src/store/types.ts","../src/web/components/VersionBadge.tsx","../src/web/components/StatusBadge.tsx","../src/web/components/ProgressBar.tsx","../src/web/components/LoadingSpinner.tsx","../src/web/components/JobItem.tsx","../src/web/components/JobList.tsx","../src/web/routes/jobs/list.tsx","../src/web/components/Alert.tsx","../src/web/components/Tooltip.tsx","../src/web/components/ScrapeFormContent.tsx","../src/web/components/ScrapeForm.tsx","../src/web/routes/jobs/new.tsx","../src/web/components/VersionDetailsRow.tsx","../src/web/components/LibraryDetailCard.tsx","../src/web/components/LibrarySearchCard.tsx","../src/web/components/SearchResultItem.tsx","../src/web/components/SearchResultList.tsx","../src/web/components/SearchResultSkeletonItem.tsx","../src/web/routes/libraries/detail.tsx","../src/web/components/LibraryItem.tsx","../src/web/components/LibraryList.tsx","../src/web/routes/libraries/list.tsx","../src/services/webService.ts","../src/services/workerService.ts","../src/utils/paths.ts","../src/app/AppServer.ts","../src/app/index.ts","../src/mcp/startStdioServer.ts","../src/pipeline/PipelineClient.ts","../src/utils/url.ts","../src/scraper/utils/patternMatcher.ts","../src/scraper/utils/scope.ts","../src/scraper/strategies/BaseScraperStrategy.ts","../src/scraper/strategies/WebScraperStrategy.ts","../src/scraper/strategies/GitHubScraperStrategy.ts","../src/scraper/strategies/LocalFileStrategy.ts","../src/scraper/strategies/NpmScraperStrategy.ts","../src/scraper/strategies/PyPiScraperStrategy.ts","../src/scraper/ScraperRegistry.ts","../src/scraper/ScraperService.ts","../src/pipeline/PipelineWorker.ts","../src/pipeline/PipelineManager.ts","../src/pipeline/PipelineFactory.ts","../src/splitter/errors.ts","../src/splitter/GreedySplitter.ts","../src/utils/string.ts","../src/splitter/splitters/CodeContentSplitter.ts","../src/splitter/splitters/TableContentSplitter.ts","../src/splitter/splitters/TextContentSplitter.ts","../src/splitter/SemanticMarkdownSplitter.ts","../src/store/DocumentRetrieverService.ts","../src/store/errors.ts","../src/store/applyMigrations.ts","../src/store/DocumentStore.ts","../src/store/DocumentManagementService.ts","../src/cli/utils.ts","../src/cli/commands/default.ts","../src/cli/commands/fetchUrl.ts","../src/cli/commands/findVersion.ts","../src/cli/commands/list.ts","../src/cli/commands/mcp.ts","../src/cli/commands/remove.ts","../src/cli/commands/scrape.ts","../src/cli/commands/search.ts","../src/cli/commands/web.ts","../src/cli/commands/worker.ts","../src/cli/index.ts","../src/cli/main.ts","../src/index.ts"],"sourcesContent":["/**\n * Defines the available log levels.\n */\nexport const LogLevel = {\n ERROR: 0,\n WARN: 1,\n INFO: 2,\n DEBUG: 3,\n} as const;\n\nexport type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];\n\n/**\n * Maps string log level names to their numeric values.\n */\nconst LOG_LEVEL_MAP: Record<string, LogLevel> = {\n ERROR: LogLevel.ERROR,\n WARN: LogLevel.WARN,\n INFO: LogLevel.INFO,\n DEBUG: LogLevel.DEBUG,\n};\n\n/**\n * Gets the log level from environment variable or returns default.\n */\nfunction getLogLevelFromEnv(): LogLevel {\n const envLevel = process.env.LOG_LEVEL?.toUpperCase();\n return envLevel && envLevel in LOG_LEVEL_MAP ? LOG_LEVEL_MAP[envLevel] : LogLevel.INFO;\n}\n\nlet currentLogLevel: LogLevel = getLogLevelFromEnv();\n\n/**\n * Sets the current logging level for the application.\n */\nexport function setLogLevel(level: LogLevel): void {\n currentLogLevel = level;\n}\n\n/**\n * Provides logging functionalities with level control.\n */\nexport const logger = {\n /**\n * Logs a debug message if the current log level is DEBUG or higher.\n * @param message - The message to log.\n */\n debug: (message: string) => {\n if (currentLogLevel >= LogLevel.DEBUG && !process.env.VITEST_WORKER_ID) {\n console.debug(message);\n }\n },\n /**\n * Logs an info message if the current log level is INFO or higher.\n * @param message - The message to log.\n */\n info: (message: string) => {\n if (currentLogLevel >= LogLevel.INFO && !process.env.VITEST_WORKER_ID) {\n console.log(message); // Using console.log for INFO\n }\n },\n /**\n * Logs a warning message if the current log level is WARN or higher.\n * @param message - The message to log.\n */\n warn: (message: string) => {\n if (currentLogLevel >= LogLevel.WARN && !process.env.VITEST_WORKER_ID) {\n console.warn(message);\n }\n },\n /**\n * Logs an error message if the current log level is ERROR or higher (always logs).\n * @param message - The message to log.\n */\n error: (message: string) => {\n if (currentLogLevel >= LogLevel.ERROR && !process.env.VITEST_WORKER_ID) {\n console.error(message);\n }\n },\n};\n","import type { ScraperProgress } from \"../scraper/types\";\nimport type { VersionScraperOptions, VersionStatus } from \"../store/types\";\nimport type { Document } from \"../types\"; // Use local Document type\n\n/**\n * Represents the possible states of a pipeline job.\n */\nexport enum PipelineJobStatus {\n QUEUED = \"queued\",\n RUNNING = \"running\",\n COMPLETED = \"completed\",\n FAILED = \"failed\",\n CANCELLING = \"cancelling\",\n CANCELLED = \"cancelled\",\n}\n\n/**\n * Public interface for pipeline jobs exposed through API boundaries.\n * Contains only serializable fields suitable for JSON transport.\n */\nexport interface PipelineJob {\n /** Unique identifier for the job. */\n id: string;\n /** The library name associated with the job. */\n library: string;\n /** The library version associated with the job. */\n version: string | null;\n /** Current pipeline status of the job. */\n status: PipelineJobStatus;\n /** Detailed progress information. */\n progress: ScraperProgress | null;\n /** Error information if the job failed. */\n error: { message: string } | null;\n /** Timestamp when the job was created. */\n createdAt: Date;\n /** Timestamp when the job started running. */\n startedAt: Date | null;\n /** Timestamp when the job finished (completed, failed, or cancelled). */\n finishedAt: Date | null;\n /** Database version ID for direct updates. */\n versionId?: number;\n /** Database version status (authoritative). */\n versionStatus?: VersionStatus;\n /** Current number of pages processed. */\n progressPages?: number;\n /** Maximum number of pages to process. */\n progressMaxPages?: number;\n /** Database error message (more detailed than Error object). */\n errorMessage?: string | null;\n /** Last update timestamp from database. */\n updatedAt?: Date;\n /** Original scraping URL. */\n sourceUrl: string | null;\n /** Stored scraper options for reproducibility. */\n scraperOptions: VersionScraperOptions | null;\n}\n\n/**\n * Internal pipeline job representation used within PipelineManager.\n * Contains non-serializable fields for job management and control.\n */\nexport interface InternalPipelineJob extends Omit<PipelineJob, \"version\" | \"error\"> {\n /** The library version associated with the job (internal uses string). */\n version: string;\n /** Error object if the job failed. */\n error: Error | null;\n /** AbortController to signal cancellation. */\n abortController: AbortController;\n /** Promise that resolves/rejects when the job finishes. */\n completionPromise: Promise<void>;\n /** Resolver function for the completion promise. */\n resolveCompletion: () => void;\n /** Rejector function for the completion promise. */\n rejectCompletion: (reason?: unknown) => void;\n}\n\n/**\n * Defines the structure for callback functions used with the PipelineManager.\n * Allows external components to hook into job lifecycle events.\n */\nexport interface PipelineManagerCallbacks {\n /** Callback triggered when a job's status changes. */\n onJobStatusChange?: (job: InternalPipelineJob) => Promise<void>;\n /** Callback triggered when a job makes progress. */\n onJobProgress?: (job: InternalPipelineJob, progress: ScraperProgress) => Promise<void>;\n /** Callback triggered when a job encounters an error during processing (e.g., storing a doc). */\n onJobError?: (\n job: InternalPipelineJob,\n error: Error,\n document?: Document,\n ) => Promise<void>;\n}\n","import type { IPipeline } from \"../pipeline/interfaces\";\nimport { PipelineJobStatus } from \"../pipeline/types\";\nimport { logger } from \"../utils/logger\";\n\n/**\n * Input parameters for the CancelJobTool.\n */\nexport interface CancelJobInput {\n /** The ID of the job to cancel. */\n jobId: string;\n}\n\n/**\n * Output result for the CancelJobTool.\n */\nexport interface CancelJobResult {\n /** A message indicating the outcome of the cancellation attempt. */\n message: string;\n /** Indicates if the cancellation request was successfully initiated or if the job was already finished/cancelled. */\n success: boolean;\n}\n\n/**\n * Tool for attempting to cancel a pipeline job.\n */\nexport class CancelJobTool {\n private pipeline: IPipeline;\n\n /**\n * Creates an instance of CancelJobTool.\n * @param pipeline The pipeline instance.\n */\n constructor(pipeline: IPipeline) {\n this.pipeline = pipeline;\n }\n\n /**\n * Executes the tool to attempt cancellation of a specific job.\n * @param input - The input parameters, containing the jobId.\n * @returns A promise that resolves with the outcome message.\n */\n async execute(input: CancelJobInput): Promise<CancelJobResult> {\n try {\n // Retrieve the job first to check its status before attempting cancellation\n const job = await this.pipeline.getJob(input.jobId);\n\n if (!job) {\n logger.warn(`❓ [CancelJobTool] Job not found: ${input.jobId}`);\n return {\n message: `Job with ID ${input.jobId} not found.`,\n success: false,\n };\n }\n\n // Check if the job is already in a final state\n if (\n job.status === PipelineJobStatus.COMPLETED || // Use enum member\n job.status === PipelineJobStatus.FAILED || // Use enum member\n job.status === PipelineJobStatus.CANCELLED // Use enum member\n ) {\n logger.debug(`Job ${input.jobId} is already in a final state: ${job.status}.`);\n return {\n message: `Job ${input.jobId} is already ${job.status}. No action taken.`,\n success: true, // Considered success as no cancellation needed\n };\n }\n\n // Attempt cancellation\n await this.pipeline.cancelJob(input.jobId);\n\n // Re-fetch the job to confirm status change (or check status directly if cancelJob returned it)\n // PipelineManager.cancelJob doesn't return status, so re-fetch is needed for confirmation.\n const updatedJob = await this.pipeline.getJob(input.jobId);\n const finalStatus = updatedJob?.status ?? \"UNKNOWN (job disappeared?)\";\n\n logger.debug(\n `Cancellation requested for job ${input.jobId}. Current status: ${finalStatus}`,\n );\n return {\n message: `Cancellation requested for job ${input.jobId}. Current status: ${finalStatus}.`,\n success: true,\n };\n } catch (error) {\n logger.error(`❌ Error cancelling job ${input.jobId}: ${error}`);\n return {\n message: `Failed to cancel job ${input.jobId}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n success: false,\n };\n }\n }\n}\n","import type { IPipeline } from \"../pipeline/interfaces\";\nimport { logger } from \"../utils/logger\";\n\n/**\n * Input parameters for the ClearCompletedJobsTool.\n */\n// biome-ignore lint/suspicious/noEmptyInterface: No input parameters needed for this tool\nexport interface ClearCompletedJobsInput {\n // No input parameters needed for this tool\n}\n\n/**\n * Output result for the ClearCompletedJobsTool.\n */\nexport interface ClearCompletedJobsResult {\n /** A message indicating the outcome of the clear operation. */\n message: string;\n /** Indicates if the clear operation was successful. */\n success: boolean;\n /** The number of jobs that were cleared. */\n clearedCount: number;\n}\n\n/**\n * Tool for clearing all completed, cancelled, and failed jobs from the pipeline.\n * This helps keep the job queue clean by removing jobs that are no longer active.\n */\nexport class ClearCompletedJobsTool {\n private pipeline: IPipeline;\n\n /**\n * Creates an instance of ClearCompletedJobsTool.\n * @param pipeline The pipeline instance.\n */\n constructor(pipeline: IPipeline) {\n this.pipeline = pipeline;\n }\n\n /**\n * Executes the tool to clear all completed jobs from the pipeline.\n * @param input - The input parameters (currently unused).\n * @returns A promise that resolves with the outcome of the clear operation.\n */\n async execute(_input: ClearCompletedJobsInput): Promise<ClearCompletedJobsResult> {\n try {\n const clearedCount = await this.pipeline.clearCompletedJobs();\n\n const message =\n clearedCount > 0\n ? `Successfully cleared ${clearedCount} completed job${clearedCount === 1 ? \"\" : \"s\"} from the queue.`\n : \"No completed jobs to clear.\";\n\n logger.debug(`[ClearCompletedJobsTool] ${message}`);\n\n return {\n message,\n success: true,\n clearedCount,\n };\n } catch (error) {\n const errorMessage = `Failed to clear completed jobs: ${\n error instanceof Error ? error.message : String(error)\n }`;\n\n logger.error(`❌ [ClearCompletedJobsTool] ${errorMessage}`);\n\n return {\n message: errorMessage,\n success: false,\n clearedCount: 0,\n };\n }\n }\n}\n","import semver from \"semver\";\nimport type { LibraryVersionDetails } from \"../store/types\"; // Import LibraryVersionDetails\n\nclass ToolError extends Error {\n constructor(\n message: string,\n public readonly toolName: string,\n ) {\n super(message);\n this.name = this.constructor.name;\n }\n}\n\nclass VersionNotFoundError extends ToolError {\n constructor(\n public readonly library: string,\n public readonly requestedVersion: string,\n public readonly availableVersions: LibraryVersionDetails[], // Use LibraryVersionDetails\n ) {\n super(\n `Version ${requestedVersion} not found for ${library}. Available versions: ${availableVersions.map((v) => v.version).join(\", \")}`,\n \"SearchTool\",\n );\n }\n\n getLatestVersion() {\n return this.availableVersions.sort((a, b) => semver.compare(b.version, a.version))[0];\n }\n}\n\n/**\n * Error thrown when a requested library cannot be found in the store.\n * Includes suggestions for similar library names if available.\n */\nclass LibraryNotFoundError extends ToolError {\n constructor(\n public readonly requestedLibrary: string,\n public readonly suggestions: string[] = [],\n ) {\n let message = `Library '${requestedLibrary}' not found.`;\n if (suggestions.length > 0) {\n message += ` Did you mean one of these: ${suggestions.join(\", \")}?`;\n }\n // Assuming this error might originate from various tools, but SearchTool is a primary candidate.\n // We might need to adjust the toolName if it's thrown elsewhere.\n super(message, \"SearchTool\");\n }\n}\n\nexport { LibraryNotFoundError, ToolError, VersionNotFoundError };\n","/**\n * Represents a parsed Content-Type header.\n */\nexport interface ParsedContentType {\n mimeType: string;\n charset?: string;\n}\n\n/**\n * Utility functions for handling MIME types and charsets.\n */\n// biome-ignore lint/complexity/noStaticOnlyClass: helpers are static\nexport class MimeTypeUtils {\n /**\n * Parses a Content-Type header string into its MIME type and charset.\n * @param contentTypeHeader The Content-Type header string (e.g., \"text/html; charset=utf-8\").\n * @returns A ParsedContentType object, or a default if parsing fails.\n */\n public static parseContentType(contentTypeHeader?: string | null): ParsedContentType {\n if (!contentTypeHeader) {\n return { mimeType: \"application/octet-stream\" };\n }\n const parts = contentTypeHeader.split(\";\").map((part) => part.trim());\n const mimeType = parts[0].toLowerCase();\n let charset: string | undefined;\n\n for (let i = 1; i < parts.length; i++) {\n const param = parts[i];\n if (param.toLowerCase().startsWith(\"charset=\")) {\n charset = param.substring(\"charset=\".length).toLowerCase();\n break;\n }\n }\n return { mimeType, charset };\n }\n\n /**\n * Checks if a MIME type represents HTML content.\n */\n public static isHtml(mimeType: string): boolean {\n return mimeType === \"text/html\" || mimeType === \"application/xhtml+xml\";\n }\n\n /**\n * Checks if a MIME type represents Markdown content.\n */\n public static isMarkdown(mimeType: string): boolean {\n return mimeType === \"text/markdown\" || mimeType === \"text/x-markdown\";\n }\n\n /**\n * Checks if a MIME type represents plain text content.\n */\n public static isText(mimeType: string): boolean {\n return mimeType.startsWith(\"text/\");\n }\n\n // Extend with more helpers as needed (isJson, isXml, isPdf, etc.)\n}\n","import * as cheerio from \"cheerio\";\nimport { logger } from \"../../utils/logger\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Middleware to parse HTML string/buffer content into a Cheerio object.\n * It populates the `context.dom` property.\n * Assumes the input HTML in `context.content` is the final version to be parsed\n * (e.g., after potential rendering by Playwright or modification by JS execution).\n */\nexport class HtmlCheerioParserMiddleware implements ContentProcessorMiddleware {\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n try {\n logger.debug(`Parsing HTML content with Cheerio from ${context.source}`);\n // Load the HTML string using Cheerio\n const $ = cheerio.load(context.content);\n\n // Add the Cheerio API object to the context\n context.dom = $;\n\n // Proceed to the next middleware\n await next();\n } catch (error) {\n logger.error(\n `❌ Failed to parse HTML with Cheerio for ${context.source}: ${error}`,\n );\n context.errors.push(\n error instanceof Error\n ? error\n : new Error(`Cheerio HTML parsing failed: ${String(error)}`),\n );\n // Do not proceed further down the pipeline if parsing fails\n return;\n }\n }\n}\n","import type { ConstructorOptions } from \"jsdom\";\nimport { JSDOM, VirtualConsole } from \"jsdom\";\n\n/**\n * Creates a JSDOM instance with a pre-configured virtual console to suppress console noise.\n * This utility simplifies the setup of JSDOM by providing a standard configuration.\n *\n * @param html - The HTML content to parse.\n * @param options - Optional JSDOM configuration options. These will be merged with the default virtual console setup.\n * @returns A JSDOM instance.\n */\nexport function createJSDOM(html: string, options?: ConstructorOptions): JSDOM {\n const virtualConsole = new VirtualConsole();\n // Suppress console output from JSDOM by default\n virtualConsole.on(\"error\", () => {});\n virtualConsole.on(\"warn\", () => {});\n virtualConsole.on(\"info\", () => {});\n virtualConsole.on(\"debug\", () => {});\n virtualConsole.on(\"log\", () => {}); // Also suppress regular logs\n\n const defaultOptions: ConstructorOptions = {\n virtualConsole,\n };\n\n // Merge provided options with defaults, letting provided options override\n const finalOptions: ConstructorOptions = { ...defaultOptions, ...options };\n\n return new JSDOM(html, finalOptions);\n}\n","import { logger } from \"../../utils/logger\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Middleware to extract links (href attributes from <a> tags) from HTML content using Cheerio.\n * It expects the Cheerio API object to be available in `context.dom`.\n * This should run *after* parsing but *before* conversion to Markdown.\n */\nexport class HtmlLinkExtractorMiddleware implements ContentProcessorMiddleware {\n /**\n * Processes the context to extract links from the sanitized HTML body.\n * @param context The current middleware context.\n * @param next Function to call the next middleware.\n */\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n // Check if we have a Cheerio object from a previous step\n const $ = context.dom;\n if (!$) {\n logger.warn(\n `⏭️ Skipping ${this.constructor.name}: context.dom is missing. Ensure HtmlCheerioParserMiddleware runs before this.`,\n );\n await next();\n return;\n }\n\n try {\n const linkElements = $(\"a[href]\");\n logger.debug(`Found ${linkElements.length} potential links in ${context.source}`);\n\n const extractedLinks: string[] = [];\n linkElements.each((_index, element) => {\n const href = $(element).attr(\"href\");\n if (href && href.trim() !== \"\") {\n try {\n const urlObj = new URL(href, context.source);\n if (![\"http:\", \"https:\", \"file:\"].includes(urlObj.protocol)) {\n logger.debug(`Ignoring link with invalid protocol: ${href}`);\n return;\n }\n extractedLinks.push(urlObj.href);\n } catch (_e) {\n logger.debug(`Ignoring invalid URL syntax: ${href}`);\n }\n }\n });\n\n context.links = [...new Set(extractedLinks)];\n logger.debug(\n `Extracted ${context.links.length} unique, valid links from ${context.source}`,\n );\n } catch (error) {\n logger.error(`❌ Error extracting links from ${context.source}: ${error}`);\n context.errors.push(\n new Error(\n `Failed to extract links from HTML: ${error instanceof Error ? error.message : String(error)}`,\n ),\n );\n }\n\n await next();\n }\n}\n","import { logger } from \"../../utils/logger\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Middleware to extract the title from HTML content using Cheerio.\n * Assumes context.dom (Cheerio API object) is populated by a preceding middleware\n * (e.g., HtmlCheerioParserMiddleware).\n */\nexport class HtmlMetadataExtractorMiddleware implements ContentProcessorMiddleware {\n /**\n * Processes the context to extract the HTML title.\n * @param context The current processing context.\n * @param next Function to call the next middleware.\n */\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n // Check if Cheerio DOM exists from previous middleware\n const $ = context.dom;\n if (!$) {\n logger.warn(\n `⏭️ Skipping ${this.constructor.name}: context.dom is missing. Ensure HtmlCheerioParserMiddleware runs before this.`,\n );\n await next();\n return;\n }\n\n // Only process if we have a Cheerio object (implicitly means it's HTML)\n try {\n // Extract title (using title tag, fallback to h1 if title is empty/missing)\n let title = $(\"title\").first().text().trim();\n\n if (!title) {\n // Fallback to the first H1 if title is empty\n title = $(\"h1\").first().text().trim();\n }\n\n // Default to \"Untitled\" if both are empty\n title = title || \"Untitled\";\n\n // Basic cleanup (replace multiple spaces with single space)\n title = title.replace(/\\s+/g, \" \").trim();\n\n context.metadata.title = title;\n logger.debug(`Extracted title: \"${title}\" from ${context.source}`);\n } catch (error) {\n logger.error(`❌ Error extracting metadata from ${context.source}: ${error}`);\n context.errors.push(\n new Error(\n `Failed to extract metadata from HTML: ${error instanceof Error ? error.message : String(error)}`,\n ),\n );\n // Optionally decide whether to stop the pipeline here\n }\n\n // Call the next middleware in the chain\n await next();\n\n // No cleanup needed for Cheerio\n }\n}\n","/**\n * Default configuration values for the scraping pipeline and server\n */\n\n/** Maximum number of pages to scrape in a single job */\nexport const DEFAULT_MAX_PAGES = 1000;\n\n/** Maximum navigation depth when crawling links */\nexport const DEFAULT_MAX_DEPTH = 3;\n\n/** Maximum number of concurrent page requests */\nexport const DEFAULT_MAX_CONCURRENCY = 3;\n\n/** Default protocol for the MCP server */\nexport const DEFAULT_PROTOCOL = \"auto\";\n\n/** Default port for the HTTP protocol */\nexport const DEFAULT_HTTP_PORT = 6280;\n\n/** Default port for the Web UI */\nexport const DEFAULT_WEB_PORT = 6281;\n\n/**\n * Default timeout in milliseconds for page operations (e.g., Playwright waitForSelector).\n */\nexport const DEFAULT_PAGE_TIMEOUT = 5000;\n\n/**\n * Maximum number of retries for HTTP fetcher requests.\n */\nexport const FETCHER_MAX_RETRIES = 6;\n\n/**\n * Base delay in milliseconds for HTTP fetcher retry backoff.\n */\nexport const FETCHER_BASE_DELAY = 1000;\n\n/**\n * Default chunk size settings for splitters\n */\nexport const SPLITTER_MIN_CHUNK_SIZE = 500;\nexport const SPLITTER_PREFERRED_CHUNK_SIZE = 1500;\nexport const SPLITTER_MAX_CHUNK_SIZE = 5000;\n\n/**\n * Maximum number of documents to process in a single batch for embeddings.\n */\nexport const EMBEDDING_BATCH_SIZE = 100;\n\n/**\n * Maximum number of retries for database migrations if busy.\n */\nexport const MIGRATION_MAX_RETRIES = 5;\n\n/**\n * Delay in milliseconds between migration retry attempts.\n */\nexport const MIGRATION_RETRY_DELAY_MS = 300;\n","import type { Document, ProgressCallback } from \"../types\";\n\n/**\n * Enum defining the available HTML processing strategies.\n */\nexport enum ScrapeMode {\n Fetch = \"fetch\",\n Playwright = \"playwright\",\n Auto = \"auto\",\n}\n\n/**\n * Strategy interface for implementing different scraping behaviors\n */\nexport interface ScraperStrategy {\n canHandle(url: string): boolean;\n scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal, // Add optional signal\n ): Promise<void>;\n}\n\n/**\n * Options for configuring the scraping process\n */\nexport interface ScraperOptions {\n url: string;\n library: string;\n version: string;\n maxPages?: number;\n maxDepth?: number;\n /**\n * Defines the allowed crawling boundary relative to the starting URL\n * - 'subpages': Only crawl URLs on the same hostname and within the same starting path (default)\n * - 'hostname': Crawl any URL on the same exact hostname, regardless of path\n * - 'domain': Crawl any URL on the same top-level domain, including subdomains\n */\n scope?: \"subpages\" | \"hostname\" | \"domain\";\n /**\n * Controls whether HTTP redirects (3xx responses) should be followed\n * - When true: Redirects are followed automatically (default)\n * - When false: A RedirectError is thrown when a 3xx response is received\n */\n followRedirects?: boolean;\n maxConcurrency?: number;\n ignoreErrors?: boolean;\n /** CSS selectors for elements to exclude during HTML processing */\n excludeSelectors?: string[];\n /**\n * Determines the HTML processing strategy.\n * - 'fetch': Use a simple DOM parser (faster, less JS support).\n * - 'playwright': Use a headless browser (slower, full JS support).\n * - 'auto': Automatically select the best strategy (currently defaults to 'playwright').\n * @default ScrapeMode.Auto\n */\n scrapeMode?: ScrapeMode;\n /** Optional AbortSignal for cancellation */\n signal?: AbortSignal;\n /**\n * Patterns for including URLs during scraping. If not set, all are included by default.\n */\n includePatterns?: string[];\n /**\n * Patterns for excluding URLs during scraping. Exclude takes precedence over include.\n */\n excludePatterns?: string[];\n /**\n * Custom HTTP headers to send with each HTTP request (e.g., for authentication).\n * Keys are header names, values are header values.\n */\n headers?: Record<string, string>;\n}\n\n/**\n * Result of scraping a single page. Used internally by HtmlScraper.\n */\nexport interface ScrapedPage {\n content: string;\n title: string;\n url: string;\n /** URLs extracted from page links, used for recursive scraping */\n links: string[];\n}\n\n/**\n * Progress information during scraping\n */\nexport interface ScraperProgress {\n pagesScraped: number;\n totalPages: number; // Effective total pages (limited by maxPages configuration)\n totalDiscovered: number; // Actual number of pages discovered (may exceed totalPages)\n currentUrl: string;\n depth: number;\n maxDepth: number;\n document?: Document;\n}\n","import { type Browser, type BrowserContext, chromium, type Page } from \"playwright\";\nimport { DEFAULT_PAGE_TIMEOUT } from \"../../utils/config\";\nimport { logger } from \"../../utils/logger\";\nimport { ScrapeMode } from \"../types\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Middleware to process HTML content using Playwright for rendering dynamic content,\n * *if* the scrapeMode option requires it ('playwright' or 'auto').\n * It updates `context.content` with the rendered HTML if Playwright runs.\n * Subsequent middleware (e.g., HtmlCheerioParserMiddleware) should handle parsing this content.\n *\n * This middleware also supports URLs with embedded credentials (user:password@host) and ensures\n * credentials are used for all same-origin resource requests (not just the main page) via HTTP Basic Auth.\n *\n * Additionally, all custom headers from context.options?.headers are forwarded to Playwright requests.\n */\nexport class HtmlPlaywrightMiddleware implements ContentProcessorMiddleware {\n private browser: Browser | null = null;\n\n /**\n * Initializes the Playwright browser instance.\n * Consider making this more robust (e.g., lazy initialization, singleton).\n */\n private async ensureBrowser(): Promise<Browser> {\n if (!this.browser || !this.browser.isConnected()) {\n const launchArgs = process.env.PLAYWRIGHT_LAUNCH_ARGS?.split(\" \") ?? [];\n const executablePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || undefined;\n logger.debug(\n `Launching new Playwright browser instance (Chromium) with args: ${launchArgs.join(\" \") || \"none\"}...`,\n );\n this.browser = await chromium.launch({\n channel: \"chromium\",\n args: launchArgs,\n executablePath: executablePath,\n });\n this.browser.on(\"disconnected\", () => {\n logger.debug(\"Playwright browser instance disconnected.\");\n this.browser = null;\n });\n }\n return this.browser;\n }\n\n /**\n * Closes the Playwright browser instance if it exists.\n * Should be called during application shutdown.\n */\n async closeBrowser(): Promise<void> {\n if (this.browser?.isConnected()) {\n logger.debug(\"Closing Playwright browser instance...\");\n await this.browser.close();\n this.browser = null;\n }\n }\n\n /**\n * Waits for common loading indicators (spinners, loaders) that are currently visible to disappear from the page.\n * Only waits for selectors that are present and visible at the time of check.\n *\n * @param page The Playwright page instance to operate on.\n */\n private async waitForLoadingToComplete(page: Page): Promise<void> {\n const commonLoadingSelectors = [\n '[class*=\"loading\"]',\n '[class*=\"spinner\"]',\n '[class*=\"loader\"]',\n '[id*=\"loading\"]',\n '[class*=\"preload\"]',\n \"#loading\",\n '[aria-label*=\"loading\" i]',\n '[aria-label*=\"spinner\" i]',\n ];\n\n // Wait for all visible loading indicators in parallel\n const waitPromises: Promise<unknown>[] = [];\n for (const selector of commonLoadingSelectors) {\n try {\n // Use page.isVisible to check if any matching element is visible (legacy API, but works for any visible match)\n const isVisible = await page.isVisible(selector).catch(() => false);\n if (isVisible) {\n waitPromises.push(\n page\n .waitForSelector(selector, {\n state: \"hidden\",\n timeout: DEFAULT_PAGE_TIMEOUT,\n })\n .catch(() => {}),\n );\n }\n } catch {\n // Ignore errors (e.g., selector not found or timeout)\n }\n }\n if (waitPromises.length > 0) {\n await Promise.all(waitPromises);\n }\n }\n\n /**\n * Processes the context using Playwright, rendering dynamic content and propagating credentials for all same-origin requests.\n *\n * - Parses credentials from the URL (if present).\n * - Uses browser.newContext({ httpCredentials }) for HTTP Basic Auth on the main page and subresources.\n * - Injects Authorization header for all same-origin requests if credentials are present and not already set.\n * - Forwards all custom headers from context.options?.headers to Playwright requests.\n * - Waits for common loading indicators to disappear before extracting HTML.\n *\n * @param context The middleware context containing the HTML and source URL.\n * @param next The next middleware function in the pipeline.\n */\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n // Always process, content type is handled by pipeline selection\n\n // Determine if Playwright should run based on scrapeMode\n const scrapeMode = context.options?.scrapeMode ?? ScrapeMode.Auto;\n const shouldRunPlaywright =\n scrapeMode === ScrapeMode.Playwright || scrapeMode === ScrapeMode.Auto;\n\n if (!shouldRunPlaywright) {\n logger.debug(\n `Skipping Playwright rendering for ${context.source} as scrapeMode is '${scrapeMode}'.`,\n );\n await next();\n return;\n }\n\n logger.debug(\n `Running Playwright rendering for ${context.source} (scrapeMode: '${scrapeMode}')`,\n );\n\n let page: Page | null = null;\n let browserContext: BrowserContext | null = null;\n let renderedHtml: string | null = null;\n\n // Extract credentials and origin using helper\n const { credentials, origin } = extractCredentialsAndOrigin(context.source);\n\n // Extract custom headers (Record<string, string>)\n const customHeaders: Record<string, string> = context.options?.headers ?? {};\n\n try {\n const browser = await this.ensureBrowser();\n if (credentials) {\n browserContext = await browser.newContext({ httpCredentials: credentials });\n page = await browserContext.newPage();\n } else {\n page = await browser.newPage();\n }\n logger.debug(`Playwright: Processing ${context.source}`);\n\n // Block unnecessary resources and inject credentials and custom headers for same-origin requests\n await page.route(\"**/*\", async (route) => {\n const reqUrl = route.request().url();\n const reqOrigin = (() => {\n try {\n return new URL(reqUrl).origin;\n } catch {\n return null;\n }\n })();\n // Serve the initial HTML for the main page\n if (reqUrl === context.source) {\n return route.fulfill({\n status: 200,\n contentType: \"text/html; charset=utf-8\",\n body: context.content, // context.content is always a string in middleware\n });\n }\n // Abort non-essential resources\n const resourceType = route.request().resourceType();\n if ([\"image\", \"stylesheet\", \"font\", \"media\"].includes(resourceType)) {\n return route.abort();\n }\n // Use helper to merge headers\n const headers = mergePlaywrightHeaders(\n route.request().headers(),\n customHeaders,\n credentials ?? undefined,\n origin ?? undefined,\n reqOrigin ?? undefined,\n );\n return route.continue({ headers });\n });\n\n // Load initial HTML content\n await page.goto(context.source, { waitUntil: \"load\" });\n await page.waitForSelector(\"body\");\n await this.waitForLoadingToComplete(page);\n // await page.waitForLoadState(\"networkidle\");\n\n renderedHtml = await page.content();\n logger.debug(`Playwright: Successfully rendered content for ${context.source}`);\n } catch (error) {\n logger.error(`❌ Playwright failed to render ${context.source}: ${error}`);\n context.errors.push(\n error instanceof Error\n ? error\n : new Error(`Playwright rendering failed: ${String(error)}`),\n );\n } finally {\n // Ensure page/context are closed even if subsequent steps fail\n if (page) {\n await page.unroute(\"**/*\");\n await page.close();\n }\n if (browserContext) {\n await browserContext.close();\n }\n }\n\n if (renderedHtml !== null) {\n context.content = renderedHtml;\n logger.debug(\n `Playwright middleware updated content for ${context.source}. Proceeding.`,\n );\n } else {\n logger.warn(\n `⚠️ Playwright rendering resulted in null content for ${context.source}. Proceeding without content update.`,\n );\n }\n\n await next();\n }\n}\n\n/**\n * Extracts credentials and origin from a URL string.\n * Returns { credentials, origin } where credentials is null if not present.\n */\nexport function extractCredentialsAndOrigin(urlString: string): {\n credentials: { username: string; password: string } | null;\n origin: string | null;\n} {\n try {\n const url = new URL(urlString);\n const origin = url.origin;\n if (url.username && url.password) {\n return {\n credentials: { username: url.username, password: url.password },\n origin,\n };\n }\n return { credentials: null, origin };\n } catch {\n return { credentials: null, origin: null };\n }\n}\n\n/**\n * Merges Playwright request headers, custom headers, and credentials.\n * - Custom headers are merged in unless already present (except Authorization, see below).\n * - If credentials are present and the request is same-origin, injects Authorization if not already set.\n */\nexport function mergePlaywrightHeaders(\n requestHeaders: Record<string, string>,\n customHeaders: Record<string, string>,\n credentials?: { username: string; password: string },\n origin?: string,\n reqOrigin?: string,\n): Record<string, string> {\n let headers = { ...requestHeaders };\n for (const [key, value] of Object.entries(customHeaders)) {\n if (key.toLowerCase() === \"authorization\" && headers.authorization) continue;\n headers[key] = value;\n }\n if (credentials && origin && reqOrigin === origin && !headers.authorization) {\n const basic = Buffer.from(`${credentials.username}:${credentials.password}`).toString(\n \"base64\",\n );\n headers = {\n ...headers,\n Authorization: `Basic ${basic}`,\n };\n }\n return headers;\n}\n","import { logger } from \"../../utils/logger\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Options for HtmlSanitizerMiddleware.\n */\nexport interface HtmlSanitizerOptions {\n /** CSS selectors for elements to remove *in addition* to the defaults. */\n excludeSelectors?: string[];\n}\n\n/**\n * Middleware to remove unwanted elements from parsed HTML content using Cheerio.\n * It expects the Cheerio API object (`context.dom`) to be populated by a preceding middleware\n * (e.g., HtmlCheerioParserMiddleware).\n * It modifies the `context.dom` object in place.\n */\nexport class HtmlSanitizerMiddleware implements ContentProcessorMiddleware {\n // Default selectors to remove\n private readonly defaultSelectorsToRemove = [\n \"nav\",\n \"footer\",\n \"script\",\n \"style\",\n \"noscript\",\n \"svg\",\n \"link\",\n \"meta\",\n \"iframe\",\n \"header\",\n \"button\",\n \"input\",\n \"textarea\",\n \"select\",\n // \"form\", // Keep commented\n \".ads\",\n \".advertisement\",\n \".banner\",\n \".cookie-banner\",\n \".cookie-consent\",\n \".hidden\",\n \".hide\",\n \".modal\",\n \".nav-bar\",\n \".overlay\",\n \".popup\",\n \".promo\",\n \".mw-editsection\",\n \".side-bar\",\n \".social-share\",\n \".sticky\",\n \"#ads\",\n \"#banner\",\n \"#cookieBanner\",\n \"#modal\",\n \"#nav\",\n \"#overlay\",\n \"#popup\",\n \"#sidebar\",\n \"#socialMediaBox\",\n \"#stickyHeader\",\n \"#ad-container\",\n \".ad-container\",\n \".login-form\",\n \".signup-form\",\n \".tooltip\",\n \".dropdown-menu\",\n // \".alert\", // Keep commented\n \".breadcrumb\",\n \".pagination\",\n // '[role=\"alert\"]', // Keep commented\n '[role=\"banner\"]',\n '[role=\"dialog\"]',\n '[role=\"alertdialog\"]',\n '[role=\"region\"][aria-label*=\"skip\" i]',\n '[aria-modal=\"true\"]',\n \".noprint\",\n ];\n\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n // Check if Cheerio DOM exists\n const $ = context.dom;\n if (!$) {\n logger.warn(\n `⏭️ Skipping ${this.constructor.name}: context.dom is missing. Ensure HtmlCheerioParserMiddleware runs before this.`,\n );\n await next();\n return;\n }\n\n try {\n // Remove unwanted elements using Cheerio\n const selectorsToRemove = [\n ...(context.options.excludeSelectors || []), // Use options from the context\n ...this.defaultSelectorsToRemove,\n ];\n logger.debug(\n `Removing elements matching ${selectorsToRemove.length} selectors for ${context.source}`,\n );\n let removedCount = 0;\n for (const selector of selectorsToRemove) {\n try {\n const elements = $(selector); // Use Cheerio selector\n const count = elements.length;\n if (count > 0) {\n elements.remove(); // Use Cheerio remove\n removedCount += count;\n }\n } catch (selectorError) {\n // Log invalid selectors but continue with others\n // Cheerio is generally more tolerant of invalid selectors than querySelectorAll\n logger.warn(\n `⚠️ Potentially invalid selector \"${selector}\" during element removal: ${selectorError}`,\n );\n context.errors.push(\n new Error(`Invalid selector \"${selector}\": ${selectorError}`),\n );\n }\n }\n logger.debug(`Removed ${removedCount} elements for ${context.source}`);\n\n // The context.dom object ($) has been modified in place.\n } catch (error) {\n logger.error(\n `❌ Error during HTML element removal for ${context.source}: ${error}`,\n );\n context.errors.push(\n error instanceof Error\n ? error\n : new Error(`HTML element removal failed: ${String(error)}`),\n );\n // Decide if pipeline should stop? For now, continue.\n }\n\n // Proceed to the next middleware\n await next();\n }\n}\n","// @ts-ignore\nimport { gfm } from \"@joplin/turndown-plugin-gfm\";\nimport TurndownService from \"turndown\";\nimport { logger } from \"../../utils/logger\"; // Added logger\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Middleware to convert the final processed HTML content (from Cheerio object in context.dom)\n * into Markdown using Turndown, applying custom rules.\n */\nexport class HtmlToMarkdownMiddleware implements ContentProcessorMiddleware {\n private turndownService: TurndownService;\n\n constructor() {\n this.turndownService = new TurndownService({\n headingStyle: \"atx\",\n hr: \"---\",\n bulletListMarker: \"-\",\n codeBlockStyle: \"fenced\",\n emDelimiter: \"_\",\n strongDelimiter: \"**\",\n linkStyle: \"inlined\",\n });\n\n this.turndownService.use(gfm);\n\n this.addCustomRules();\n }\n\n private addCustomRules(): void {\n // Preserve code blocks and syntax (replicated from HtmlProcessor)\n this.turndownService.addRule(\"pre\", {\n filter: [\"pre\"],\n replacement: (_content, node) => {\n const element = node as unknown as HTMLElement;\n let language = element.getAttribute(\"data-language\") || \"\";\n if (!language) {\n // Try to infer the language from the class name\n // This is a common pattern in syntax highlighters\n const highlightElement =\n element.closest(\n '[class*=\"highlight-source-\"], [class*=\"highlight-\"], [class*=\"language-\"]',\n ) ||\n element.querySelector(\n '[class*=\"highlight-source-\"], [class*=\"highlight-\"], [class*=\"language-\"]',\n );\n if (highlightElement) {\n const className = highlightElement.className;\n const match = className.match(\n /(?:highlight-source-|highlight-|language-)(\\w+)/,\n );\n if (match) language = match[1];\n }\n }\n\n const brElements = Array.from(element.querySelectorAll(\"br\"));\n for (const br of brElements) {\n br.replaceWith(\"\\n\");\n }\n const text = element.textContent || \"\";\n\n return `\\n\\`\\`\\`${language}\\n${text.replace(/^\\n+|\\n+$/g, \"\")}\\n\\`\\`\\`\\n`;\n },\n });\n this.turndownService.addRule(\"anchor\", {\n filter: [\"a\"],\n replacement: (content, node) => {\n const href = (node as HTMLElement).getAttribute(\"href\");\n if (!content || content === \"#\") {\n return \"\"; // Remove if content is # or empty\n }\n if (!href) {\n return content; // Preserve content if href is missing or empty\n }\n return `[${content}](${href})`; // Standard link conversion\n },\n });\n }\n\n /**\n * Processes the context to convert the sanitized HTML body node to Markdown.\n * @param context The current processing context.\n * @param next Function to call the next middleware.\n */\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n // Check if we have a Cheerio object from a previous step\n const $ = context.dom;\n if (!$) {\n logger.warn(\n `⏭️ Skipping ${this.constructor.name}: context.dom is missing. Ensure HtmlCheerioParserMiddleware ran correctly.`,\n );\n await next();\n return;\n }\n\n // Only process if we have a Cheerio object (implicitly means it's HTML)\n try {\n logger.debug(`Converting HTML content to Markdown for ${context.source}`);\n // Provide Turndown with the HTML string content from the Cheerio object's body,\n // or the whole document if body is empty/unavailable.\n const htmlToConvert = $(\"body\").html() || $.html();\n const markdown = this.turndownService.turndown(htmlToConvert).trim();\n\n if (!markdown) {\n // If conversion results in empty markdown, log a warning but treat as valid empty markdown\n const warnMsg = `HTML to Markdown conversion resulted in empty content for ${context.source}.`;\n logger.warn(`⚠️ ${warnMsg}`);\n context.content = \"\";\n } else {\n // Conversion successful and produced non-empty markdown\n context.content = markdown;\n logger.debug(`Successfully converted HTML to Markdown for ${context.source}`);\n }\n } catch (error) {\n logger.error(\n `❌ Error converting HTML to Markdown for ${context.source}: ${error}`,\n );\n context.errors.push(\n new Error(\n `Failed to convert HTML to Markdown: ${error instanceof Error ? error.message : String(error)}`,\n ),\n );\n // Decide if pipeline should stop? For now, continue.\n }\n\n // Call the next middleware in the chain regardless of whether conversion happened\n await next();\n\n // No need to close/free Cheerio object explicitly\n // context.dom = undefined; // Optionally clear the dom property if no longer needed downstream\n }\n}\n","import type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Placeholder middleware for extracting links from Markdown content.\n * Currently, it does not implement link extraction, matching the\n * original MarkdownProcessor's TODO status.\n */\nexport class MarkdownLinkExtractorMiddleware implements ContentProcessorMiddleware {\n /**\n * Processes the context. Currently a no-op regarding link extraction.\n * @param context The current processing context.\n * @param next Function to call the next middleware.\n */\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n // TODO: Implement Markdown link extraction (e.g., using regex or a Markdown parser)\n // For now, ensure context.links exists, defaulting to empty array if not set.\n if (!Array.isArray(context.links)) {\n context.links = [];\n }\n // No links are added here yet.\n\n await next();\n }\n}\n","import type { ContentProcessorMiddleware, MiddlewareContext } from \"./types\";\n\n/**\n * Middleware to extract the title (first H1 heading) from Markdown content.\n */\nexport class MarkdownMetadataExtractorMiddleware implements ContentProcessorMiddleware {\n /**\n * Processes the context to extract the title from Markdown.\n * @param context The current processing context.\n * @param next Function to call the next middleware.\n */\n async process(context: MiddlewareContext, next: () => Promise<void>): Promise<void> {\n try {\n let title = \"Untitled\";\n const match = context.content.match(/^#\\s+(.*)$/m);\n if (match?.[1]) {\n title = match[1].trim();\n }\n context.metadata.title = title;\n } catch (error) {\n context.errors.push(\n new Error(\n `Failed to extract metadata from Markdown: ${error instanceof Error ? error.message : String(error)}`,\n ),\n );\n }\n\n await next();\n }\n}\n","/**\n * Utility functions for enhanced charset detection and handling.\n */\n\nimport { logger } from \"../../utils/logger\";\n\n/**\n * Detects charset from HTML meta tags.\n * Looks for both <meta charset=\"...\"> and <meta http-equiv=\"Content-Type\" content=\"text/html; charset=...\">\n */\nexport function detectCharsetFromHtml(htmlContent: string): string | undefined {\n // Match <meta charset=\"...\"> (HTML5 style)\n const charsetMatch = htmlContent.match(\n /<meta\\s+charset\\s*=\\s*[\"']?([^\"'>\\s]+)[\"']?[^>]*>/i,\n );\n if (charsetMatch) {\n return charsetMatch[1].toLowerCase();\n }\n\n // Match <meta http-equiv=\"Content-Type\" content=\"...charset=...\"> (HTML4 style)\n const httpEquivMatch = htmlContent.match(\n /<meta\\s+http-equiv\\s*=\\s*[\"']?content-type[\"']?\\s+content\\s*=\\s*[\"']?[^\"'>]*charset=([^\"'>\\s;]+)/i,\n );\n if (httpEquivMatch) {\n return httpEquivMatch[1].toLowerCase();\n }\n\n return undefined;\n}\n\n/**\n * Determines the best charset to use for decoding content.\n * Prioritizes HTML meta tags over HTTP headers for HTML content.\n */\nexport function resolveCharset(\n httpCharset: string | undefined,\n htmlContent: string | Buffer,\n mimeType: string,\n): string {\n // For non-HTML content, use HTTP charset or default to UTF-8\n if (!mimeType.includes(\"html\")) {\n return httpCharset || \"utf-8\";\n }\n\n // For HTML content, try to detect charset from meta tags\n let htmlString: string;\n try {\n // Try to decode as UTF-8 first to look for meta tags\n htmlString =\n typeof htmlContent === \"string\" ? htmlContent : htmlContent.toString(\"utf-8\");\n } catch {\n // If UTF-8 fails, use the HTTP charset or fall back to latin1 for meta tag detection\n htmlString =\n typeof htmlContent === \"string\"\n ? htmlContent\n : htmlContent.toString((httpCharset || \"latin1\") as BufferEncoding);\n }\n\n // Look for charset in HTML meta tags (usually in first 1024 bytes)\n const headContent = htmlString.substring(0, 1024);\n const metaCharset = detectCharsetFromHtml(headContent);\n\n if (metaCharset) {\n logger.debug(`Detected charset from HTML meta tag: ${metaCharset}`);\n return metaCharset;\n }\n\n if (httpCharset) {\n logger.debug(`Using charset from HTTP header: ${httpCharset}`);\n return httpCharset;\n }\n\n logger.debug(\"No charset detected, defaulting to UTF-8\");\n return \"utf-8\";\n}\n\n/**\n * Common charset aliases and their canonical names.\n * Helps handle cases where servers use non-standard charset names.\n */\nexport const CHARSET_ALIASES: Record<string, string> = {\n \"iso-8859-1\": \"latin1\",\n \"iso_8859-1\": \"latin1\",\n \"latin-1\": \"latin1\",\n \"windows-1252\": \"cp1252\",\n \"cp-1252\": \"cp1252\",\n \"ms-ansi\": \"cp1252\",\n utf8: \"utf-8\",\n unicode: \"utf-8\",\n \"us-ascii\": \"ascii\",\n ascii: \"ascii\",\n};\n\n/**\n * Normalizes charset name to handle common aliases.\n */\nexport function normalizeCharset(charset: string): string {\n const normalized = charset.toLowerCase().trim();\n return CHARSET_ALIASES[normalized] || normalized;\n}\n","import iconv from \"iconv-lite\";\nimport { normalizeCharset } from \"./charset\";\n\n/**\n * Decodes a Buffer or string to a JavaScript string using the specified charset.\n * The charset should be the encoding as reported by the source (e.g., HTTP header).\n * The result is always a valid JS string (Unicode/UTF-16).\n *\n * If the charset is missing or unsupported, falls back to UTF-8.\n *\n * @param content The content to decode (Buffer or string)\n * @param charset The source encoding (e.g., 'utf-8', 'iso-8859-1', 'utf-16le', etc.)\n * @returns The decoded string\n */\nexport function convertToString(content: string | Buffer, charset?: string): string {\n if (typeof content === \"string\") return content;\n\n const normalizedCharset = charset ? normalizeCharset(charset) : \"utf-8\";\n\n try {\n return iconv.decode(content, normalizedCharset);\n } catch {\n // Fallback to utf-8 if decoding fails\n try {\n return iconv.decode(content, \"utf-8\");\n } catch {\n // Last resort: decode as latin1 which can handle any byte sequence\n return iconv.decode(content, \"latin1\");\n }\n }\n}\n","import type { ContentFetcher, RawContent } from \"../fetcher/types\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"../middleware/types\";\nimport type { ScraperOptions } from \"../types\";\nimport type { ContentPipeline, ProcessedContent } from \"./types\";\n\n/**\n * Base class for content processing pipelines.\n * Provides common functionality for executing middleware stacks.\n */\nexport class BasePipeline implements ContentPipeline {\n /**\n * Determines if this pipeline can process the given content.\n * Must be implemented by derived classes.\n */\n public canProcess(_rawContent: RawContent): boolean {\n throw new Error(\"Method not implemented.\");\n }\n\n /**\n * Processes the raw content through the pipeline.\n * Must be implemented by derived classes.\n */\n public async process(\n _rawContent: RawContent,\n _options: ScraperOptions,\n _fetcher?: ContentFetcher,\n ): Promise<ProcessedContent> {\n throw new Error(\"Method not implemented.\");\n }\n\n /**\n * Executes a middleware stack on the given context.\n * This is a utility method used by derived pipeline classes.\n *\n * @param middleware - The middleware stack to execute\n * @param context - The context to process\n */\n protected async executeMiddlewareStack(\n middleware: ContentProcessorMiddleware[],\n context: MiddlewareContext,\n ): Promise<void> {\n let index = -1;\n const dispatch = async (i: number): Promise<void> => {\n if (i <= index) throw new Error(\"next() called multiple times\");\n index = i;\n const mw = middleware[i];\n if (!mw) return;\n await mw.process(context, dispatch.bind(null, i + 1));\n };\n\n try {\n await dispatch(0);\n } catch (error) {\n context.errors.push(error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n /**\n * Cleans up resources when the pipeline is no longer needed.\n * Default implementation does nothing.\n */\n public async close(): Promise<void> {\n // Default implementation does nothing\n }\n}\n","import { MimeTypeUtils } from \"../../utils/mimeTypeUtils\";\nimport type { ContentFetcher, RawContent } from \"../fetcher/types\";\nimport { HtmlSanitizerMiddleware } from \"../middleware\";\nimport { HtmlCheerioParserMiddleware } from \"../middleware/HtmlCheerioParserMiddleware\";\nimport { HtmlLinkExtractorMiddleware } from \"../middleware/HtmlLinkExtractorMiddleware\";\nimport { HtmlMetadataExtractorMiddleware } from \"../middleware/HtmlMetadataExtractorMiddleware\";\nimport { HtmlPlaywrightMiddleware } from \"../middleware/HtmlPlaywrightMiddleware\";\nimport { HtmlToMarkdownMiddleware } from \"../middleware/HtmlToMarkdownMiddleware\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"../middleware/types\";\nimport type { ScraperOptions } from \"../types\";\nimport { convertToString } from \"../utils/buffer\";\nimport { resolveCharset } from \"../utils/charset\";\nimport { BasePipeline } from \"./BasePipeline\";\nimport type { ProcessedContent } from \"./types\";\n\n/**\n * Pipeline for processing HTML content using middleware.\n */\nexport class HtmlPipeline extends BasePipeline {\n private readonly playwrightMiddleware: HtmlPlaywrightMiddleware;\n private readonly standardMiddleware: ContentProcessorMiddleware[];\n\n constructor() {\n super();\n this.playwrightMiddleware = new HtmlPlaywrightMiddleware();\n this.standardMiddleware = [\n new HtmlCheerioParserMiddleware(),\n new HtmlMetadataExtractorMiddleware(),\n new HtmlLinkExtractorMiddleware(),\n new HtmlSanitizerMiddleware(),\n new HtmlToMarkdownMiddleware(),\n ];\n }\n\n canProcess(rawContent: RawContent): boolean {\n return MimeTypeUtils.isHtml(rawContent.mimeType);\n }\n\n async process(\n rawContent: RawContent,\n options: ScraperOptions,\n fetcher?: ContentFetcher,\n ): Promise<ProcessedContent> {\n // Use enhanced charset detection that considers HTML meta tags\n const resolvedCharset = resolveCharset(\n rawContent.charset,\n rawContent.content,\n rawContent.mimeType,\n );\n const contentString = convertToString(rawContent.content, resolvedCharset);\n\n const context: MiddlewareContext = {\n content: contentString,\n source: rawContent.source,\n metadata: {},\n links: [],\n errors: [],\n options,\n fetcher,\n };\n\n // Build middleware stack dynamically based on scrapeMode\n let middleware: ContentProcessorMiddleware[] = [...this.standardMiddleware];\n if (options.scrapeMode === \"playwright\" || options.scrapeMode === \"auto\") {\n middleware = [this.playwrightMiddleware, ...middleware];\n }\n\n // Execute the middleware stack using the base class method\n await this.executeMiddlewareStack(middleware, context);\n\n return {\n textContent: typeof context.content === \"string\" ? context.content : \"\",\n metadata: context.metadata,\n links: context.links,\n errors: context.errors,\n };\n }\n\n async close(): Promise<void> {\n await this.playwrightMiddleware.closeBrowser();\n }\n}\n","import { MimeTypeUtils } from \"../../utils/mimeTypeUtils\";\nimport type { ContentFetcher, RawContent } from \"../fetcher/types\";\nimport { MarkdownLinkExtractorMiddleware } from \"../middleware/MarkdownLinkExtractorMiddleware\";\nimport { MarkdownMetadataExtractorMiddleware } from \"../middleware/MarkdownMetadataExtractorMiddleware\";\nimport type { ContentProcessorMiddleware, MiddlewareContext } from \"../middleware/types\";\nimport type { ScraperOptions } from \"../types\";\nimport { convertToString } from \"../utils/buffer\";\nimport { BasePipeline } from \"./BasePipeline\";\nimport type { ProcessedContent } from \"./types\";\n\n/**\n * Pipeline for processing Markdown content using middleware.\n */\nexport class MarkdownPipeline extends BasePipeline {\n private readonly middleware: ContentProcessorMiddleware[];\n\n constructor() {\n super();\n this.middleware = [\n new MarkdownMetadataExtractorMiddleware(),\n new MarkdownLinkExtractorMiddleware(),\n ];\n }\n\n canProcess(rawContent: RawContent): boolean {\n if (!rawContent.mimeType) return false;\n return (\n MimeTypeUtils.isMarkdown(rawContent.mimeType) ||\n MimeTypeUtils.isText(rawContent.mimeType)\n );\n }\n\n async process(\n rawContent: RawContent,\n options: ScraperOptions,\n fetcher?: ContentFetcher,\n ): Promise<ProcessedContent> {\n const contentString = convertToString(rawContent.content, rawContent.charset);\n\n const context: MiddlewareContext = {\n content: contentString,\n source: rawContent.source,\n metadata: {},\n links: [],\n errors: [],\n options,\n fetcher,\n };\n\n // Execute the middleware stack using the base class method\n await this.executeMiddlewareStack(this.middleware, context);\n\n return {\n textContent: typeof context.content === \"string\" ? context.content : \"\",\n metadata: context.metadata,\n links: context.links,\n errors: context.errors,\n };\n }\n\n async close(): Promise<void> {}\n}\n","class ScraperError extends Error {\n constructor(\n message: string,\n public readonly isRetryable: boolean = false,\n public readonly cause?: Error,\n ) {\n super(message);\n this.name = this.constructor.name;\n if (cause?.stack) {\n this.stack = `${this.stack}\\nCaused by: ${cause.stack}`;\n }\n }\n}\n\nclass NetworkError extends ScraperError {\n constructor(\n message: string,\n public readonly statusCode?: number,\n cause?: Error,\n ) {\n super(message, true, cause);\n }\n}\n\nclass RateLimitError extends ScraperError {\n constructor(\n message: string,\n public readonly retryAfter?: number,\n ) {\n super(message, true);\n }\n}\n\nclass InvalidUrlError extends ScraperError {\n constructor(url: string, cause?: Error) {\n super(`Invalid URL: ${url}`, false, cause);\n }\n}\n\nclass ParsingError extends ScraperError {\n constructor(message: string, cause?: Error) {\n super(`Failed to parse content: ${message}`, false, cause);\n }\n}\n\nclass RedirectError extends ScraperError {\n constructor(\n public readonly originalUrl: string,\n public readonly redirectUrl: string,\n public readonly statusCode: number,\n ) {\n super(\n `Redirect detected from ${originalUrl} to ${redirectUrl} (status: ${statusCode})`,\n false,\n );\n }\n}\n\nexport {\n ScraperError,\n NetworkError,\n RateLimitError,\n InvalidUrlError,\n ParsingError,\n RedirectError,\n};\n","import type {\n ContentFetcher,\n FileFetcher,\n HttpFetcher,\n RawContent,\n} from \"../scraper/fetcher\";\nimport { HtmlPipeline } from \"../scraper/pipelines/HtmlPipeline\";\nimport { MarkdownPipeline } from \"../scraper/pipelines/MarkdownPipeline\";\nimport { ScrapeMode } from \"../scraper/types\";\nimport { convertToString } from \"../scraper/utils/buffer\";\nimport { resolveCharset } from \"../scraper/utils/charset\";\nimport { ScraperError } from \"../utils/errors\";\nimport { logger } from \"../utils/logger\";\nimport { ToolError } from \"./errors\";\n\nexport interface FetchUrlToolOptions {\n /**\n * The URL to fetch and convert to markdown.\n * Must be a valid HTTP/HTTPS URL or file:// URL.\n */\n url: string;\n\n /**\n * Whether to follow HTTP redirects.\n * @default true\n */\n followRedirects?: boolean;\n\n /**\n * Determines the HTML processing strategy.\n * - 'fetch': Use a simple DOM parser (faster, less JS support).\n * - 'playwright': Use a headless browser (slower, full JS support).\n * - 'auto': Automatically select the best strategy (currently defaults to 'playwright').\n * @default ScrapeMode.Auto\n */\n scrapeMode?: ScrapeMode;\n\n /**\n * Custom HTTP headers to send with the request (e.g., for authentication).\n * Keys are header names, values are header values.\n */\n headers?: Record<string, string>;\n}\n\n/**\n * Tool for fetching a single URL and converting its content to Markdown.\n * Unlike scrape_docs, this tool only processes one page without crawling\n * or storing the content.\n *\n * Supports both HTTP/HTTPS URLs and local file URLs (file://).\n */\nexport class FetchUrlTool {\n /**\n * Collection of fetchers that will be tried in order for a given URL.\n */\n private readonly fetchers: ContentFetcher[];\n\n constructor(httpFetcher: HttpFetcher, fileFetcher: FileFetcher) {\n this.fetchers = [httpFetcher, fileFetcher];\n }\n\n /**\n * Fetches content from a URL and converts it to Markdown.\n * Supports both HTTP/HTTPS URLs and local file URLs (file://).\n * @returns The processed Markdown content\n * @throws {ToolError} If fetching or processing fails\n */\n async execute(options: FetchUrlToolOptions): Promise<string> {\n const { url, scrapeMode = ScrapeMode.Auto, headers } = options;\n\n const canFetchResults = this.fetchers.map((f) => f.canFetch(url));\n const fetcherIndex = canFetchResults.findIndex((result) => result === true);\n if (fetcherIndex === -1) {\n throw new ToolError(\n `Invalid URL: ${url}. Must be an HTTP/HTTPS URL or a file:// URL.`,\n this.constructor.name,\n );\n }\n\n const fetcher = this.fetchers[fetcherIndex];\n const htmlPipeline = new HtmlPipeline();\n const markdownPipeline = new MarkdownPipeline();\n const pipelines = [htmlPipeline, markdownPipeline];\n\n try {\n logger.info(`📡 Fetching ${url}...`);\n const rawContent: RawContent = await fetcher.fetch(url, {\n followRedirects: options.followRedirects ?? true,\n maxRetries: 3,\n headers, // propagate custom headers\n });\n\n logger.info(\"🔄 Processing content...\");\n\n let processed: Awaited<ReturnType<(typeof htmlPipeline)[\"process\"]>> | undefined;\n for (const pipeline of pipelines) {\n if (pipeline.canProcess(rawContent)) {\n processed = await pipeline.process(\n rawContent,\n {\n url,\n library: \"\",\n version: \"\",\n maxDepth: 0,\n maxPages: 1,\n maxConcurrency: 1,\n scope: \"subpages\",\n followRedirects: options.followRedirects ?? true,\n excludeSelectors: undefined,\n ignoreErrors: false,\n scrapeMode,\n headers, // propagate custom headers\n },\n fetcher,\n );\n break;\n }\n }\n\n if (!processed) {\n logger.warn(\n `⚠️ Unsupported content type \"${rawContent.mimeType}\" for ${url}. Returning raw content.`,\n );\n // Use proper charset detection for unsupported content types\n const resolvedCharset = resolveCharset(\n rawContent.charset,\n rawContent.content,\n rawContent.mimeType,\n );\n const contentString = convertToString(rawContent.content, resolvedCharset);\n return contentString;\n }\n\n for (const err of processed.errors) {\n logger.warn(`⚠️ Processing error for ${url}: ${err.message}`);\n }\n\n if (typeof processed.textContent !== \"string\" || !processed.textContent.trim()) {\n throw new ToolError(\n `Processing resulted in empty content for ${url}`,\n this.constructor.name,\n );\n }\n\n logger.info(`✅ Successfully processed ${url}`);\n return processed.textContent;\n } catch (error) {\n if (error instanceof ScraperError || error instanceof ToolError) {\n throw new ToolError(\n `Failed to fetch or process URL: ${error.message}`,\n this.constructor.name,\n );\n }\n throw new ToolError(\n `Failed to fetch or process URL: ${error instanceof Error ? error.message : String(error)}`,\n this.constructor.name,\n );\n } finally {\n await htmlPipeline.close();\n await markdownPipeline.close();\n }\n }\n}\n","import type { DocumentManagementService } from \"../store\";\nimport { logger } from \"../utils/logger\";\nimport { VersionNotFoundError } from \"./errors\";\n\nexport interface FindVersionToolOptions {\n library: string;\n targetVersion?: string;\n}\n\n/**\n * Tool for finding the best matching version of a library in the store.\n * Supports exact version matches and X-Range patterns (e.g., '5.x', '5.2.x').\n */\nexport class FindVersionTool {\n private docService: DocumentManagementService;\n\n constructor(docService: DocumentManagementService) {\n this.docService = docService;\n }\n\n /**\n * Executes the tool to find the best matching version and checks for unversioned docs.\n * @returns A descriptive string indicating the best match and unversioned status, or an error message.\n */\n async execute(options: FindVersionToolOptions): Promise<string> {\n const { library, targetVersion } = options;\n const libraryAndVersion = `${library}${targetVersion ? `@${targetVersion}` : \"\"}`;\n\n try {\n const { bestMatch, hasUnversioned } = await this.docService.findBestVersion(\n library,\n targetVersion,\n );\n\n let message = \"\";\n if (bestMatch) {\n message = `Best match: ${bestMatch}.`;\n if (hasUnversioned) {\n message += \" Unversioned docs also available.\";\n }\n } else if (hasUnversioned) {\n message = `No matching version found for ${libraryAndVersion}, but unversioned docs exist.`;\n } else {\n // This case should ideally be caught by VersionNotFoundError below,\n // but added for completeness.\n message = `No matching version or unversioned documents found for ${libraryAndVersion}.`;\n }\n return message;\n } catch (error) {\n if (error instanceof VersionNotFoundError) {\n // This error is thrown when no semver versions AND no unversioned docs exist.\n logger.info(`ℹ️ Version not found: ${error.message}`);\n return `No matching version or unversioned documents found for ${libraryAndVersion}. Available: ${\n error.availableVersions.length > 0\n ? error.availableVersions.map((v) => v.version).join(\", \")\n : \"None\"\n }.`;\n }\n // Re-throw unexpected errors\n logger.error(\n `❌ Error finding version for ${libraryAndVersion}: ${error instanceof Error ? error.message : error}`,\n );\n throw error;\n }\n }\n}\n","import type { IPipeline } from \"../pipeline/interfaces\";\nimport type { PipelineJobStatus } from \"../pipeline/types\";\nimport type { VersionStatus } from \"../store/types\";\n\n/**\n * Input parameters for the GetJobInfoTool.\n */\nexport interface GetJobInfoInput {\n /** The ID of the job to retrieve info for. */\n jobId: string;\n}\n\n/**\n * Simplified information about a pipeline job for external use.\n */\nexport interface JobInfo {\n id: string;\n library: string;\n version: string | null;\n status: PipelineJobStatus; // Pipeline status (for compatibility)\n dbStatus?: VersionStatus; // Database status (enhanced)\n createdAt: string;\n startedAt: string | null;\n finishedAt: string | null;\n error: string | null;\n // Progress information from database\n progress?: {\n pages: number;\n totalPages: number;\n totalDiscovered: number;\n };\n // Additional database fields\n updatedAt?: string;\n errorMessage?: string; // Database error message\n}\n\n/**\n * Response structure for the GetJobInfoTool.\n */\nexport interface GetJobInfoToolResponse {\n job: JobInfo | null;\n}\n\n/**\n * Tool for retrieving simplified information about a specific pipeline job.\n */\nexport class GetJobInfoTool {\n private pipeline: IPipeline;\n\n /**\n * Creates an instance of GetJobInfoTool.\n * @param pipeline The pipeline instance.\n */\n constructor(pipeline: IPipeline) {\n this.pipeline = pipeline;\n }\n\n /**\n * Executes the tool to retrieve simplified info for a specific job using enhanced PipelineJob interface.\n * @param input - The input parameters, containing the jobId.\n * @returns A promise that resolves with the simplified job info or null if not found.\n */\n async execute(input: GetJobInfoInput): Promise<GetJobInfoToolResponse> {\n const job = await this.pipeline.getJob(input.jobId);\n\n if (!job) {\n // Return null in the result if job not found\n return { job: null };\n }\n\n // Transform the job into a simplified object using enhanced PipelineJob interface\n const jobInfo: JobInfo = {\n id: job.id,\n library: job.library,\n version: job.version,\n status: job.status,\n dbStatus: job.versionStatus,\n createdAt: job.createdAt.toISOString(),\n startedAt: job.startedAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n error: job.error?.message ?? null,\n progress:\n job.progressMaxPages && job.progressMaxPages > 0\n ? {\n pages: job.progressPages || 0,\n totalPages: job.progressMaxPages,\n totalDiscovered: job.progress?.totalDiscovered || job.progressMaxPages,\n }\n : undefined,\n updatedAt: job.updatedAt?.toISOString(),\n errorMessage: job.errorMessage ?? undefined,\n };\n\n return { job: jobInfo };\n }\n}\n","import type { IPipeline } from \"../pipeline/interfaces\";\nimport type { PipelineJob, PipelineJobStatus } from \"../pipeline/types\";\nimport type { JobInfo } from \"./GetJobInfoTool\"; // Import JobInfo\n\n/**\n * Input parameters for the ListJobsTool.\n */\nexport interface ListJobsInput {\n /** Optional status to filter jobs by. */\n status?: PipelineJobStatus;\n}\n\n/**\n * Response structure for the ListJobsTool.\n */\nexport interface ListJobsToolResponse {\n jobs: JobInfo[];\n}\n\n/**\n * Tool for listing pipeline jobs managed by the pipeline.\n * Allows filtering jobs by their status.\n */\nexport class ListJobsTool {\n private pipeline: IPipeline;\n\n /**\n * Creates an instance of ListJobsTool.\n * @param pipeline The pipeline instance.\n */\n constructor(pipeline: IPipeline) {\n this.pipeline = pipeline;\n }\n\n /**\n * Executes the tool to retrieve a list of pipeline jobs using single source of truth.\n * @param input - The input parameters, optionally including a status filter.\n * @returns A promise that resolves with the list of simplified job objects.\n */\n async execute(input: ListJobsInput): Promise<ListJobsToolResponse> {\n const jobs = await this.pipeline.getJobs(input.status);\n\n // Transform jobs into simplified objects using enhanced PipelineJob interface\n const simplifiedJobs: JobInfo[] = jobs.map((job: PipelineJob): JobInfo => {\n return {\n id: job.id,\n library: job.library,\n version: job.version,\n status: job.status,\n dbStatus: job.versionStatus,\n createdAt: job.createdAt.toISOString(),\n startedAt: job.startedAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n error: job.error?.message ?? null,\n progress:\n job.progressMaxPages && job.progressMaxPages > 0\n ? {\n pages: job.progressPages || 0,\n totalPages: job.progressMaxPages,\n totalDiscovered: job.progress?.totalDiscovered || job.progressMaxPages,\n }\n : undefined,\n updatedAt: job.updatedAt?.toISOString(),\n errorMessage: job.errorMessage ?? undefined,\n };\n });\n\n return { jobs: simplifiedJobs };\n }\n}\n","import type { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport type { LibraryVersionDetails } from \"../store/types\";\n\n// Define the structure for the tool's output, using the detailed version info\nexport interface LibraryInfo {\n name: string;\n versions: LibraryVersionDetails[]; // Use the detailed interface\n}\n\nexport interface ListLibrariesResult {\n libraries: LibraryInfo[];\n}\n\n/**\n * Tool for listing all available libraries and their indexed versions in the store.\n */\nexport class ListLibrariesTool {\n private docService: DocumentManagementService;\n\n constructor(docService: DocumentManagementService) {\n this.docService = docService;\n }\n\n async execute(_options?: Record<string, never>): Promise<ListLibrariesResult> {\n // docService.listLibraries() now returns the detailed structure directly\n const rawLibraries = await this.docService.listLibraries();\n\n // The structure returned by listLibraries already matches LibraryInfo[]\n // No complex mapping is needed here anymore, just ensure the names match\n const libraries: LibraryInfo[] = rawLibraries.map(({ library, versions }) => ({\n name: library,\n versions: versions, // Directly assign the detailed versions array\n }));\n\n return { libraries };\n }\n}\n","import type { IPipeline } from \"../pipeline/interfaces\";\nimport { PipelineJobStatus } from \"../pipeline/types\";\nimport type { DocumentManagementService } from \"../store\";\nimport { logger } from \"../utils/logger\";\nimport { ToolError } from \"./errors\";\n\n/**\n * Represents the arguments for the remove_docs tool.\n * The MCP server should validate the input against RemoveToolInputSchema before calling execute.\n */\nexport interface RemoveToolArgs {\n library: string;\n version?: string;\n}\n\n/**\n * Tool to remove indexed documentation for a specific library version.\n * This class provides the core logic, intended to be called by the McpServer.\n */\nexport class RemoveTool {\n constructor(\n private readonly documentManagementService: DocumentManagementService,\n private readonly pipeline: IPipeline,\n ) {}\n\n /**\n * Executes the tool to remove the specified library version documents.\n * Aborts any QUEUED/RUNNING job for the same library+version before deleting.\n */\n async execute(args: RemoveToolArgs): Promise<{ message: string }> {\n const { library, version } = args;\n\n logger.info(\n `🗑️ Removing library: ${library}${version ? `, version: ${version}` : \" (unversioned)\"}`,\n );\n\n try {\n // Abort any QUEUED or RUNNING job for this library+version\n const allJobs = await this.pipeline.getJobs();\n const jobs = allJobs.filter(\n (job) =>\n job.library === library &&\n job.version === (version ?? \"\") &&\n (job.status === PipelineJobStatus.QUEUED ||\n job.status === PipelineJobStatus.RUNNING),\n );\n\n for (const job of jobs) {\n logger.info(\n `🚫 Aborting job for ${library}@${version ?? \"\"} before deletion: ${job.id}`,\n );\n await this.pipeline.cancelJob(job.id);\n // Wait for job to finish cancelling if running\n await this.pipeline.waitForJobCompletion(job.id);\n }\n // Core logic: Call the document management service\n await this.documentManagementService.removeAllDocuments(library, version);\n\n const message = `Successfully removed documents for ${library}${version ? `@${version}` : \" (unversioned)\"}.`;\n logger.info(`✅ ${message}`);\n // Return a simple success object, the McpServer will format the final response\n return { message };\n } catch (error) {\n const errorMessage = `Failed to remove documents for ${library}${version ? `@${version}` : \" (unversioned)\"}: ${error instanceof Error ? error.message : String(error)}`;\n logger.error(`❌ Error removing library: ${errorMessage}`);\n // Re-throw the error for the McpServer to handle and format\n throw new ToolError(errorMessage, this.constructor.name);\n }\n }\n}\n","import * as semver from \"semver\";\nimport type { IPipeline } from \"../pipeline/interfaces\";\nimport { ScrapeMode } from \"../scraper/types\";\nimport {\n DEFAULT_MAX_CONCURRENCY,\n DEFAULT_MAX_DEPTH,\n DEFAULT_MAX_PAGES,\n} from \"../utils/config\";\nimport { logger } from \"../utils/logger\";\n\nexport interface ScrapeToolOptions {\n library: string;\n version?: string | null; // Make version optional\n url: string;\n options?: {\n maxPages?: number;\n maxDepth?: number;\n /**\n * Defines the allowed crawling boundary relative to the starting URL\n * - 'subpages': Only crawl URLs on the same hostname and within the same starting path (default)\n * - 'hostname': Crawl any URL on the same hostname, regardless of path\n * - 'domain': Crawl any URL on the same top-level domain, including subdomains\n */\n scope?: \"subpages\" | \"hostname\" | \"domain\";\n /**\n * Controls whether HTTP redirects (3xx responses) should be followed\n * - When true: Redirects are followed automatically (default)\n * - When false: A RedirectError is thrown when a 3xx response is received\n */\n followRedirects?: boolean;\n maxConcurrency?: number; // Note: Concurrency is now set when PipelineManager is created\n ignoreErrors?: boolean;\n /**\n * Determines the HTML processing strategy.\n * - 'fetch': Use a simple DOM parser (faster, less JS support).\n * - 'playwright': Use a headless browser (slower, full JS support).\n * - 'auto': Automatically select the best strategy (currently defaults to 'playwright').\n * @default ScrapeMode.Auto\n */\n scrapeMode?: ScrapeMode;\n /**\n * Patterns for including URLs during scraping. If not set, all are included by default.\n * Regex patterns must be wrapped in slashes, e.g. /pattern/.\n */\n includePatterns?: string[];\n /**\n * Patterns for excluding URLs during scraping. Exclude takes precedence over include.\n * Regex patterns must be wrapped in slashes, e.g. /pattern/.\n */\n excludePatterns?: string[];\n /**\n * Custom HTTP headers to send with each request (e.g., for authentication).\n * Keys are header names, values are header values.\n */\n headers?: Record<string, string>;\n };\n /** If false, returns jobId immediately without waiting. Defaults to true. */\n waitForCompletion?: boolean;\n}\n\nexport interface ScrapeResult {\n /** Indicates the number of pages scraped if waitForCompletion was true and the job succeeded. May be 0 or inaccurate if job failed or waitForCompletion was false. */\n pagesScraped: number;\n}\n\n/** Return type for ScrapeTool.execute */\nexport type ScrapeExecuteResult = ScrapeResult | { jobId: string };\n\n/**\n * Tool for enqueuing documentation scraping jobs via the pipeline.\n */\nexport class ScrapeTool {\n private pipeline: IPipeline;\n\n constructor(pipeline: IPipeline) {\n this.pipeline = pipeline;\n }\n\n async execute(options: ScrapeToolOptions): Promise<ScrapeExecuteResult> {\n const {\n library,\n version,\n url,\n options: scraperOptions,\n waitForCompletion = true,\n } = options;\n\n // Store initialization and manager start should happen externally\n\n let internalVersion: string;\n const partialVersionRegex = /^\\d+(\\.\\d+)?$/; // Matches '1' or '1.2'\n\n if (version === null || version === undefined) {\n internalVersion = \"\";\n } else {\n const validFullVersion = semver.valid(version);\n if (validFullVersion) {\n internalVersion = validFullVersion;\n } else if (partialVersionRegex.test(version)) {\n const coercedVersion = semver.coerce(version);\n if (coercedVersion) {\n internalVersion = coercedVersion.version;\n } else {\n throw new Error(\n `Invalid version format for scraping: '${version}'. Use 'X.Y.Z', 'X.Y.Z-prerelease', 'X.Y', 'X', or omit.`,\n );\n }\n } else {\n throw new Error(\n `Invalid version format for scraping: '${version}'. Use 'X.Y.Z', 'X.Y.Z-prerelease', 'X.Y', 'X', or omit.`,\n );\n }\n }\n\n internalVersion = internalVersion.toLowerCase();\n\n // Use the injected pipeline instance\n const pipeline = this.pipeline;\n\n // Remove internal progress tracking and callbacks\n // let pagesScraped = 0;\n // let lastReportedPages = 0;\n // const reportProgress = ...\n // pipeline.setCallbacks(...)\n\n // Enqueue the job using the injected pipeline\n const jobId = await pipeline.enqueueJob(library, internalVersion, {\n url: url,\n library: library,\n version: internalVersion,\n scope: scraperOptions?.scope ?? \"subpages\",\n followRedirects: scraperOptions?.followRedirects ?? true,\n maxPages: scraperOptions?.maxPages ?? DEFAULT_MAX_PAGES,\n maxDepth: scraperOptions?.maxDepth ?? DEFAULT_MAX_DEPTH,\n maxConcurrency: scraperOptions?.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY,\n ignoreErrors: scraperOptions?.ignoreErrors ?? true,\n scrapeMode: scraperOptions?.scrapeMode ?? ScrapeMode.Auto, // Pass scrapeMode enum\n includePatterns: scraperOptions?.includePatterns,\n excludePatterns: scraperOptions?.excludePatterns,\n headers: scraperOptions?.headers, // <-- propagate headers\n });\n\n // Conditionally wait for completion\n if (waitForCompletion) {\n try {\n await pipeline.waitForJobCompletion(jobId);\n // Fetch final job state to get status and potentially final page count\n const finalJob = await pipeline.getJob(jobId);\n const finalPagesScraped = finalJob?.progress?.pagesScraped ?? 0; // Get count from final job state\n logger.debug(\n `Job ${jobId} finished with status ${finalJob?.status}. Pages scraped: ${finalPagesScraped}`,\n );\n return {\n pagesScraped: finalPagesScraped,\n };\n } catch (error) {\n logger.error(`❌ Job ${jobId} failed or was cancelled: ${error}`);\n throw error; // Re-throw so the caller knows it failed\n }\n // No finally block needed to stop pipeline, as it's managed externally\n }\n\n // If not waiting, return the job ID immediately\n return { jobId };\n }\n}\n","import type { DocumentManagementService } from \"../store\";\nimport type { LibraryVersionDetails, StoreSearchResult } from \"../store/types\"; // Import LibraryVersionDetails\nimport { logger } from \"../utils/logger\";\nimport { VersionNotFoundError } from \"./errors\";\n\nexport interface SearchToolOptions {\n library: string;\n version?: string;\n query: string;\n limit?: number;\n exactMatch?: boolean;\n}\n\nexport interface SearchToolResultError {\n message: string;\n availableVersions?: LibraryVersionDetails[]; // Use LibraryVersionDetails\n suggestions?: string[]; // Specific to LibraryNotFoundError\n}\n\nexport interface SearchToolResult {\n results: StoreSearchResult[];\n}\n\n/**\n * Tool for searching indexed documentation.\n * Supports exact version matches and version range patterns.\n * Returns available versions when requested version is not found.\n */\nexport class SearchTool {\n private docService: DocumentManagementService;\n\n constructor(docService: DocumentManagementService) {\n this.docService = docService;\n }\n\n async execute(options: SearchToolOptions): Promise<SearchToolResult> {\n const { library, version, query, limit = 5, exactMatch = false } = options;\n\n // When exactMatch is true, version must be specified and not 'latest'\n if (exactMatch && (!version || version === \"latest\")) {\n // Get available *detailed* versions for error message\n await this.docService.validateLibraryExists(library);\n // Fetch detailed versions using listLibraries and find the specific library\n const allLibraries = await this.docService.listLibraries();\n const libraryInfo = allLibraries.find((lib) => lib.library === library);\n const detailedVersions = libraryInfo ? libraryInfo.versions : [];\n throw new VersionNotFoundError(\n library,\n \"latest\", // Or perhaps the original 'version' if it wasn't 'latest'? Check logic.\n detailedVersions,\n );\n }\n\n // Default to 'latest' only when exactMatch is false\n const resolvedVersion = version || \"latest\";\n\n logger.info(\n `🔍 Searching ${library}@${resolvedVersion} for: ${query}${exactMatch ? \" (exact match)\" : \"\"}`,\n );\n\n try {\n // 1. Validate library exists first\n await this.docService.validateLibraryExists(library);\n\n // 2. Proceed with version finding and searching\n let versionToSearch: string | null | undefined = resolvedVersion;\n\n if (!exactMatch) {\n // If not exact match, find the best version (which might be null)\n const versionResult = await this.docService.findBestVersion(library, version);\n // Use the bestMatch from the result, which could be null\n versionToSearch = versionResult.bestMatch;\n\n // If findBestVersion returned null (no matching semver) AND unversioned docs exist,\n // should we search unversioned? The current logic passes null to searchStore,\n // which gets normalized to \"\" (unversioned). This seems reasonable.\n // If findBestVersion threw VersionNotFoundError, it's caught below.\n }\n // If exactMatch is true, versionToSearch remains the originally provided version.\n\n // Note: versionToSearch can be string | null | undefined here.\n // searchStore handles null/undefined by normalizing to \"\".\n const results = await this.docService.searchStore(\n library,\n versionToSearch,\n query,\n limit,\n );\n logger.info(`✅ Found ${results.length} matching results`);\n\n return { results };\n } catch (error) {\n logger.error(\n `❌ Search failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n throw error;\n }\n }\n}\n","import type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\n\n/**\n * Creates a success response object in the format expected by the MCP server.\n * @param text The text content of the response.\n * @returns The response object.\n */\nexport function createResponse(text: string): CallToolResult {\n return {\n content: [\n {\n type: \"text\",\n text,\n },\n ],\n isError: false,\n };\n}\n\n/**\n * Creates an error response object in the format expected by the MCP server.\n * @param text The error message.\n * @returns The response object.\n */\nexport function createError(text: string): CallToolResult {\n return {\n content: [\n {\n type: \"text\",\n text,\n },\n ],\n isError: true,\n };\n}\n","import { McpServer, ResourceTemplate } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod/v3\";\nimport { PipelineJobStatus } from \"../pipeline/types\";\nimport { type JobInfo, LibraryNotFoundError, VersionNotFoundError } from \"../tools\";\nimport { DEFAULT_MAX_DEPTH, DEFAULT_MAX_PAGES } from \"../utils/config\";\nimport { logger } from \"../utils/logger\";\nimport type { McpServerTools } from \"./tools\";\nimport { createError, createResponse } from \"./utils\";\n\n/**\n * Creates and configures an instance of the MCP server with registered tools, prompts, and resources.\n * @param tools The shared tool instances to use for server operations.\n * @returns A configured McpServer instance.\n */\nexport function createMcpServerInstance(tools: McpServerTools): McpServer {\n const server = new McpServer(\n {\n name: \"docs-mcp-server\",\n version: \"0.1.0\",\n },\n {\n capabilities: {\n tools: {},\n prompts: {},\n resources: {},\n },\n },\n );\n\n // --- Tool Definitions ---\n\n // Scrape docs tool - suppress deep inference issues\n // @ts-ignore TypeScript has issues with deep Zod inference in MCP SDK\n server.tool(\n \"scrape_docs\",\n \"Scrape and index documentation from a URL for a library. Use this tool to index a new library or a new version.\",\n {\n url: z.string().url().describe(\"Documentation root URL to scrape.\"),\n library: z.string().describe(\"Library name.\"),\n version: z.string().optional().describe(\"Library version (optional).\"),\n maxPages: z\n .number()\n .optional()\n .default(DEFAULT_MAX_PAGES)\n .describe(`Maximum number of pages to scrape (default: ${DEFAULT_MAX_PAGES}).`),\n maxDepth: z\n .number()\n .optional()\n .default(DEFAULT_MAX_DEPTH)\n .describe(`Maximum navigation depth (default: ${DEFAULT_MAX_DEPTH}).`),\n scope: z\n .enum([\"subpages\", \"hostname\", \"domain\"])\n .optional()\n .default(\"subpages\")\n .describe(\"Crawling boundary: 'subpages', 'hostname', or 'domain'.\"),\n followRedirects: z\n .boolean()\n .optional()\n .default(true)\n .describe(\"Follow HTTP redirects (3xx responses).\"),\n },\n {\n title: \"Scrape New Library Documentation\",\n destructiveHint: true, // replaces existing docs\n openWorldHint: true, // requires internet access\n },\n async ({ url, library, version, maxPages, maxDepth, scope, followRedirects }) => {\n try {\n // Execute scrape tool without waiting and without progress callback\n const result = await tools.scrape.execute({\n url,\n library,\n version,\n waitForCompletion: false, // Don't wait for completion\n // onProgress: undefined, // Explicitly undefined or omitted\n options: {\n maxPages,\n maxDepth,\n scope,\n followRedirects,\n },\n });\n\n // Check the type of result\n if (\"jobId\" in result) {\n // If we got a jobId back, report that\n return createResponse(`🚀 Scraping job started with ID: ${result.jobId}.`);\n }\n // This case shouldn't happen if waitForCompletion is false, but handle defensively\n return createResponse(\n `Scraping finished immediately (unexpectedly) with ${result.pagesScraped} pages.`,\n );\n } catch (error) {\n // Handle errors during job *enqueueing* or initial setup\n return createError(\n `Failed to scrape documentation: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // Search docs tool\n server.tool(\n \"search_docs\",\n \"Search up-to-date documentation for a library or package. Examples:\\n\\n\" +\n '- {library: \"react\", query: \"hooks lifecycle\"} -> matches latest version of React\\n' +\n '- {library: \"react\", version: \"18.0.0\", query: \"hooks lifecycle\"} -> matches React 18.0.0 or earlier\\n' +\n '- {library: \"typescript\", version: \"5.x\", query: \"ReturnType example\"} -> any TypeScript 5.x.x version\\n' +\n '- {library: \"typescript\", version: \"5.2.x\", query: \"ReturnType example\"} -> any TypeScript 5.2.x version',\n {\n library: z.string().describe(\"Library name.\"),\n version: z\n .string()\n .optional()\n .describe(\"Library version (exact or X-Range, optional).\"),\n query: z.string().describe(\"Documentation search query.\"),\n limit: z.number().optional().default(5).describe(\"Maximum number of results.\"),\n },\n {\n title: \"Search Library Documentation\",\n readOnlyHint: true,\n destructiveHint: false,\n },\n async ({ library, version, query, limit }) => {\n try {\n const result = await tools.search.execute({\n library,\n version,\n query,\n limit,\n exactMatch: false, // Always false for MCP interface\n });\n\n const formattedResults = result.results.map(\n (r: { url: string; content: string }, i: number) => `\n------------------------------------------------------------\nResult ${i + 1}: ${r.url}\n\n${r.content}\\n`,\n );\n\n if (formattedResults.length === 0) {\n return createResponse(\n `No results found for '${query}' in ${library}. Try to use a different or more general query.`,\n );\n }\n return createResponse(formattedResults.join(\"\"));\n } catch (error) {\n if (error instanceof LibraryNotFoundError) {\n return createResponse(\n [\n `Library \"${library}\" not found.`,\n error.suggestions?.length\n ? `Did you mean: ${error.suggestions?.join(\", \")}?`\n : undefined,\n ].join(\" \"),\n );\n }\n if (error instanceof VersionNotFoundError) {\n const indexedVersions = error.availableVersions.map((v) => v.version);\n return createResponse(\n [\n `Version \"${version}\" not found.`,\n indexedVersions.length > 0\n ? `Available indexed versions for ${library}: ${indexedVersions.join(\", \")}`\n : undefined,\n ].join(\" \"),\n );\n }\n return createError(\n `Failed to search documentation: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // List libraries tool\n server.tool(\n \"list_libraries\",\n \"List all indexed libraries.\",\n {\n // no params\n },\n {\n title: \"List Libraries\",\n readOnlyHint: true,\n destructiveHint: false,\n },\n async () => {\n try {\n const result = await tools.listLibraries.execute();\n if (result.libraries.length === 0) {\n return createResponse(\"No libraries indexed yet.\");\n }\n\n return createResponse(\n `Indexed libraries:\\n\\n${result.libraries.map((lib: { name: string }) => `- ${lib.name}`).join(\"\\n\")}`,\n );\n } catch (error) {\n return createError(\n `Failed to list libraries: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // Find version tool\n // @ts-ignore TypeScript has issues with deep Zod inference in MCP SDK\n server.tool(\n \"find_version\",\n \"Find the best matching version for a library. Use to identify available or closest versions.\",\n {\n library: z.string().describe(\"Library name.\"),\n targetVersion: z\n .string()\n .optional()\n .describe(\"Version pattern to match (exact or X-Range, optional).\"),\n },\n {\n title: \"Find Library Version\",\n readOnlyHint: true,\n destructiveHint: false,\n },\n async ({ library, targetVersion }) => {\n try {\n const message = await tools.findVersion.execute({\n library,\n targetVersion,\n });\n\n if (!message) {\n return createError(\"No matching version found\");\n }\n\n return createResponse(message);\n } catch (error) {\n return createError(\n `Failed to find version: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // List jobs tool - suppress deep inference issues\n // @ts-expect-error TypeScript has issues with deep Zod inference in MCP SDK\n server.tool(\n \"list_jobs\",\n \"List all indexing jobs. Optionally filter by status.\",\n {\n status: z\n .enum([\"queued\", \"running\", \"completed\", \"failed\", \"cancelling\", \"cancelled\"])\n .optional()\n .describe(\"Filter jobs by status (optional).\"),\n },\n {\n title: \"List Indexing Jobs\",\n readOnlyHint: true,\n destructiveHint: false,\n },\n async ({ status }) => {\n try {\n const result = await tools.listJobs.execute({\n status: status as PipelineJobStatus | undefined,\n });\n // Format the simplified job list for display\n const formattedJobs = result.jobs\n .map(\n (job: JobInfo) =>\n `- ID: ${job.id}\\n Status: ${job.status}\\n Library: ${job.library}\\n Version: ${job.version}\\n Created: ${job.createdAt}${job.startedAt ? `\\n Started: ${job.startedAt}` : \"\"}${job.finishedAt ? `\\n Finished: ${job.finishedAt}` : \"\"}${job.error ? `\\n Error: ${job.error}` : \"\"}`,\n )\n .join(\"\\n\\n\");\n return createResponse(\n result.jobs.length > 0 ? `Current Jobs:\\n\\n${formattedJobs}` : \"No jobs found.\",\n );\n } catch (error) {\n return createError(\n `Failed to list jobs: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // Get job info tool\n server.tool(\n \"get_job_info\",\n \"Get details for a specific indexing job. Use the 'list_jobs' tool to find the job ID.\",\n {\n jobId: z.string().uuid().describe(\"Job ID to query.\"),\n },\n {\n title: \"Get Indexing Job Info\",\n readOnlyHint: true,\n destructiveHint: false,\n },\n async ({ jobId }) => {\n try {\n const result = await tools.getJobInfo.execute({ jobId });\n if (!result.job) {\n return createError(`Job with ID ${jobId} not found.`);\n }\n const job = result.job;\n const formattedJob = `- ID: ${job.id}\\n Status: ${job.status}\\n Library: ${job.library}@${job.version}\\n Created: ${job.createdAt}${job.startedAt ? `\\n Started: ${job.startedAt}` : \"\"}${job.finishedAt ? `\\n Finished: ${job.finishedAt}` : \"\"}${job.error ? `\\n Error: ${job.error}` : \"\"}`;\n return createResponse(`Job Info:\\n\\n${formattedJob}`);\n } catch (error) {\n return createError(\n `Failed to get job info for ${jobId}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // Cancel job tool\n server.tool(\n \"cancel_job\",\n \"Cancel a queued or running indexing job. Use the 'list_jobs' tool to find the job ID.\",\n {\n jobId: z.string().uuid().describe(\"Job ID to cancel.\"),\n },\n {\n title: \"Cancel Indexing Job\",\n destructiveHint: true,\n },\n async ({ jobId }) => {\n try {\n const result = await tools.cancelJob.execute({ jobId });\n // Use the message and success status from the tool's result\n if (result.success) {\n return createResponse(result.message);\n }\n // If not successful according to the tool, treat it as an error in MCP\n return createError(result.message);\n } catch (error) {\n // Catch any unexpected errors during the tool execution itself\n return createError(\n `Failed to cancel job ${jobId}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // Remove docs tool\n server.tool(\n \"remove_docs\",\n \"Remove indexed documentation for a library version. Use only if explicitly instructed.\",\n {\n library: z.string().describe(\"Library name.\"),\n version: z\n .string()\n .optional()\n .describe(\"Library version (optional, removes unversioned if omitted).\"),\n },\n {\n title: \"Remove Library Documentation\",\n destructiveHint: true,\n },\n async ({ library, version }) => {\n try {\n // Execute the remove tool logic\n const result = await tools.remove.execute({ library, version });\n // Use the message from the tool's successful execution\n return createResponse(result.message);\n } catch (error) {\n // Catch errors thrown by the RemoveTool's execute method\n return createError(\n `Failed to remove documents: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n },\n );\n\n // Fetch URL tool\n server.tool(\n \"fetch_url\",\n \"Fetch a single URL and convert its content to Markdown. Use this tool to read the content of any web page.\",\n {\n url: z.string().url().describe(\"URL to fetch and convert to Markdown.\"),\n followRedirects: z\n .boolean()\n .optional()\n .default(true)\n .describe(\"Follow HTTP redirects (3xx responses).\"),\n },\n {\n title: \"Fetch URL\",\n readOnlyHint: true,\n destructiveHint: false,\n openWorldHint: true, // requires internet access\n },\n async ({ url, followRedirects }) => {\n try {\n const result = await tools.fetchUrl.execute({ url, followRedirects });\n return createResponse(result);\n } catch (error) {\n return createError(\n `Failed to fetch URL: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n },\n );\n\n server.resource(\n \"libraries\",\n \"docs://libraries\",\n {\n description: \"List all indexed libraries\",\n },\n async (uri: URL) => {\n const result = await tools.listLibraries.execute();\n\n return {\n contents: result.libraries.map((lib: { name: string }) => ({\n uri: new URL(lib.name, uri).href,\n text: lib.name,\n })),\n };\n },\n );\n\n server.resource(\n \"versions\",\n new ResourceTemplate(\"docs://libraries/{library}/versions\", {\n list: undefined,\n }),\n {\n description: \"List all indexed versions for a library\",\n },\n async (uri: URL, { library }) => {\n const result = await tools.listLibraries.execute();\n\n const lib = result.libraries.find((l: { name: string }) => l.name === library);\n if (!lib) {\n return { contents: [] };\n }\n\n return {\n contents: lib.versions.map((v: { version: string }) => ({\n uri: new URL(v.version, uri).href,\n text: v.version,\n })),\n };\n },\n );\n\n /**\n * Resource handler for listing pipeline jobs.\n * Supports filtering by status via a query parameter (e.g., ?status=running).\n * URI: docs://jobs[?status=<status>]\n */\n server.resource(\n \"jobs\",\n \"docs://jobs\",\n {\n description: \"List indexing jobs, optionally filtering by status.\",\n mimeType: \"application/json\",\n },\n async (uri: URL) => {\n const statusParam = uri.searchParams.get(\"status\");\n let statusFilter: PipelineJobStatus | undefined;\n\n // Validate status parameter if provided\n if (statusParam) {\n const validation = z.nativeEnum(PipelineJobStatus).safeParse(statusParam);\n if (validation.success) {\n statusFilter = validation.data;\n } else {\n // Handle invalid status - perhaps return an error or ignore?\n // For simplicity, let's ignore invalid status for now and return all jobs.\n // Alternatively, could throw an McpError or return specific error content.\n logger.warn(`⚠️ Invalid status parameter received: ${statusParam}`);\n }\n }\n\n // Fetch simplified jobs using the ListJobsTool\n const result = await tools.listJobs.execute({ status: statusFilter });\n\n return {\n contents: result.jobs.map((job) => ({\n uri: new URL(job.id, uri).href,\n mimeType: \"application/json\",\n text: JSON.stringify({\n id: job.id,\n library: job.library,\n version: job.version,\n status: job.status,\n error: job.error || undefined,\n }),\n })),\n };\n },\n );\n\n /**\n * Resource handler for retrieving a specific pipeline job by its ID.\n * URI Template: docs://jobs/{jobId}\n */\n server.resource(\n \"job\", // A distinct name for this specific resource type\n new ResourceTemplate(\"docs://jobs/{jobId}\", { list: undefined }),\n {\n description: \"Get details for a specific indexing job by ID.\",\n mimeType: \"application/json\",\n },\n async (uri: URL, { jobId }) => {\n // Validate jobId format if necessary (basic check)\n if (typeof jobId !== \"string\" || jobId.length === 0) {\n // Handle invalid jobId format - return empty or error\n logger.warn(`⚠️ Invalid jobId received in URI: ${jobId}`);\n return { contents: [] }; // Return empty content for invalid ID format\n }\n\n // Fetch the simplified job info using GetJobInfoTool\n const result = await tools.getJobInfo.execute({ jobId });\n\n // result.job is either the simplified job object or null\n if (!result.job) {\n // Job not found, return empty content\n return { contents: [] };\n }\n\n // Job found, return its simplified details as JSON\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({\n id: result.job.id,\n library: result.job.library,\n version: result.job.version,\n status: result.job.status,\n error: result.job.error || undefined,\n }),\n },\n ],\n };\n },\n );\n\n return server;\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport * as mime from \"mime-types\";\nimport { ScraperError } from \"../../utils/errors\";\nimport type { ContentFetcher, FetchOptions, RawContent } from \"./types\";\n\n/**\n * Fetches content from local file system.\n */\nexport class FileFetcher implements ContentFetcher {\n canFetch(source: string): boolean {\n return source.startsWith(\"file://\");\n }\n\n /**\n * Fetches the content of a file given a file:// URL, decoding percent-encoded paths as needed.\n * Only HTML and Markdown files are processed.\n */\n async fetch(source: string, _options?: FetchOptions): Promise<RawContent> {\n // Always decode the file path from file:// URL\n const rawPath = source.replace(\"file://\", \"\");\n const filePath = decodeURIComponent(rawPath);\n\n try {\n const content = await fs.readFile(filePath);\n const ext = path.extname(filePath).toLowerCase();\n const mimeType = mime.lookup(ext) || \"application/octet-stream\";\n return {\n content,\n mimeType,\n source,\n // Don't assume charset for text files - let the pipeline detect it\n };\n } catch (error: unknown) {\n throw new ScraperError(\n `Failed to read file ${filePath}: ${\n (error as { message?: string }).message ?? \"Unknown error\"\n }`,\n false,\n error instanceof Error ? error : undefined,\n );\n }\n }\n}\n","export class PipelineError extends Error {\n constructor(\n message: string,\n public readonly cause?: Error,\n ) {\n super(message);\n this.name = this.constructor.name;\n if (cause?.stack) {\n this.stack = `${this.stack}\\nCaused by: ${cause.stack}`;\n }\n }\n}\n\nexport class PipelineStateError extends PipelineError {}\n\n/**\n * Error indicating that an operation was cancelled.\n */\nexport class CancellationError extends PipelineError {\n constructor(message = \"Operation cancelled\") {\n super(message);\n }\n}\n","import { HeaderGenerator, type HeaderGeneratorOptions } from \"header-generator\";\n\n/**\n * Generates realistic browser-like HTTP headers to help avoid bot detection.\n * Uses the `header-generator` library for header generation.\n */\nexport class FingerprintGenerator {\n private headerGenerator: HeaderGenerator;\n\n /**\n * Creates an instance of FingerprintGenerator.\n * @param options Optional configuration for the header generator.\n */\n constructor(options?: Partial<HeaderGeneratorOptions>) {\n // Default options for a broad range of realistic headers\n const defaultOptions: Partial<HeaderGeneratorOptions> = {\n browsers: [{ name: \"chrome\", minVersion: 100 }, \"firefox\", \"safari\"],\n devices: [\"desktop\", \"mobile\"],\n operatingSystems: [\"windows\", \"linux\", \"macos\", \"android\", \"ios\"],\n locales: [\"en-US\", \"en\"],\n httpVersion: \"2\",\n };\n\n this.headerGenerator = new HeaderGenerator({\n ...defaultOptions,\n ...options,\n });\n }\n\n /**\n * Generates a set of realistic HTTP headers.\n * @returns A set of realistic HTTP headers.\n */\n generateHeaders(): Record<string, string> {\n return this.headerGenerator.getHeaders();\n }\n}\n","import axios, { type AxiosError, type AxiosRequestConfig } from \"axios\";\nimport { CancellationError } from \"../../pipeline/errors\";\nimport { FETCHER_BASE_DELAY, FETCHER_MAX_RETRIES } from \"../../utils/config\";\nimport { RedirectError, ScraperError } from \"../../utils/errors\";\nimport { logger } from \"../../utils/logger\";\nimport { MimeTypeUtils } from \"../../utils/mimeTypeUtils\";\nimport { FingerprintGenerator } from \"./FingerprintGenerator\";\nimport type { ContentFetcher, FetchOptions, RawContent } from \"./types\";\n\n/**\n * Fetches content from remote sources using HTTP/HTTPS.\n */\nexport class HttpFetcher implements ContentFetcher {\n private readonly retryableStatusCodes = [\n 408, // Request Timeout\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n 525, // SSL Handshake Failed (Cloudflare specific)\n ];\n\n private fingerprintGenerator: FingerprintGenerator;\n\n constructor() {\n this.fingerprintGenerator = new FingerprintGenerator();\n }\n\n canFetch(source: string): boolean {\n return source.startsWith(\"http://\") || source.startsWith(\"https://\");\n }\n\n private async delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n async fetch(source: string, options?: FetchOptions): Promise<RawContent> {\n const maxRetries = options?.maxRetries ?? FETCHER_MAX_RETRIES;\n const baseDelay = options?.retryDelay ?? FETCHER_BASE_DELAY;\n // Default to following redirects if not specified\n const followRedirects = options?.followRedirects ?? true;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const fingerprint = this.fingerprintGenerator.generateHeaders();\n const headers = {\n ...fingerprint,\n ...options?.headers, // User-provided headers override generated ones\n };\n\n const config: AxiosRequestConfig = {\n responseType: \"arraybuffer\",\n headers: {\n ...headers,\n // Override Accept-Encoding to exclude zstd which Axios doesn't handle automatically\n // This prevents servers from sending zstd-compressed content that would appear as binary garbage\n \"Accept-Encoding\": \"gzip, deflate, br\",\n },\n timeout: options?.timeout,\n signal: options?.signal, // Pass signal to axios\n // Axios follows redirects by default, we need to explicitly disable it if needed\n maxRedirects: followRedirects ? 5 : 0,\n decompress: true,\n };\n\n const response = await axios.get(source, config);\n\n const contentTypeHeader = response.headers[\"content-type\"];\n const { mimeType, charset } = MimeTypeUtils.parseContentType(contentTypeHeader);\n const contentEncoding = response.headers[\"content-encoding\"];\n\n // Convert ArrayBuffer to Buffer properly\n let content: Buffer;\n if (response.data instanceof ArrayBuffer) {\n content = Buffer.from(response.data);\n } else if (Buffer.isBuffer(response.data)) {\n content = response.data;\n } else if (typeof response.data === \"string\") {\n content = Buffer.from(response.data, \"utf-8\");\n } else {\n // Fallback for other data types\n content = Buffer.from(response.data);\n }\n\n return {\n content,\n mimeType,\n charset,\n encoding: contentEncoding,\n source: source,\n } satisfies RawContent;\n } catch (error: unknown) {\n const axiosError = error as AxiosError;\n const status = axiosError.response?.status;\n const code = axiosError.code;\n\n // Handle abort/cancel: do not retry, throw CancellationError\n if (options?.signal?.aborted || code === \"ERR_CANCELED\") {\n // Throw with isError = false to indicate cancellation is not an error\n throw new CancellationError(\"HTTP fetch cancelled\");\n }\n\n // Handle redirect errors (status codes 301, 302, 303, 307, 308)\n if (!followRedirects && status && status >= 300 && status < 400) {\n const location = axiosError.response?.headers?.location;\n if (location) {\n throw new RedirectError(source, location, status);\n }\n }\n\n if (\n attempt < maxRetries &&\n (status === undefined || this.retryableStatusCodes.includes(status))\n ) {\n const delay = baseDelay * 2 ** attempt;\n logger.warn(\n `⚠️ Attempt ${attempt + 1}/${\n maxRetries + 1\n } failed for ${source} (Status: ${status}, Code: ${code}). Retrying in ${delay}ms...`,\n );\n await this.delay(delay);\n continue;\n }\n\n // Not a 5xx error or max retries reached\n throw new ScraperError(\n `Failed to fetch ${source} after ${\n attempt + 1\n } attempts: ${axiosError.message ?? \"Unknown error\"}`,\n true,\n error instanceof Error ? error : undefined,\n );\n }\n }\n throw new ScraperError(\n `Failed to fetch ${source} after ${maxRetries + 1} attempts`,\n true,\n );\n }\n}\n","import type { IPipeline } from \"../pipeline/interfaces\";\nimport { FileFetcher, HttpFetcher } from \"../scraper/fetcher\";\nimport type { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport {\n CancelJobTool,\n FetchUrlTool,\n FindVersionTool,\n GetJobInfoTool,\n ListJobsTool,\n ListLibrariesTool,\n RemoveTool,\n ScrapeTool,\n SearchTool,\n} from \"../tools\";\n\n/**\n * Interface for the shared tool instances.\n */\nexport interface McpServerTools {\n listLibraries: ListLibrariesTool;\n findVersion: FindVersionTool;\n scrape: ScrapeTool;\n search: SearchTool;\n listJobs: ListJobsTool;\n getJobInfo: GetJobInfoTool;\n cancelJob: CancelJobTool;\n remove: RemoveTool;\n fetchUrl: FetchUrlTool;\n}\n\n/**\n * Initializes and returns the shared tool instances.\n * This should be called after initializeServices has completed.\n * @param docService The initialized DocumentManagementService instance.\n * @param pipeline The initialized pipeline instance.\n * @returns An object containing all instantiated tool instances.\n */\nexport async function initializeTools(\n docService: DocumentManagementService,\n pipeline: IPipeline,\n): Promise<McpServerTools> {\n const tools: McpServerTools = {\n listLibraries: new ListLibrariesTool(docService),\n findVersion: new FindVersionTool(docService),\n scrape: new ScrapeTool(pipeline),\n search: new SearchTool(docService),\n listJobs: new ListJobsTool(pipeline),\n getJobInfo: new GetJobInfoTool(pipeline),\n cancelJob: new CancelJobTool(pipeline),\n // clearCompletedJobs: new ClearCompletedJobsTool(pipeline),\n remove: new RemoveTool(docService, pipeline),\n fetchUrl: new FetchUrlTool(new HttpFetcher(), new FileFetcher()),\n };\n\n return tools;\n}\n","/**\n * MCP service that registers MCP protocol routes for AI tool integration.\n * Provides modular server composition for MCP endpoints.\n */\n\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { SSEServerTransport } from \"@modelcontextprotocol/sdk/server/sse.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport { createMcpServerInstance } from \"../mcp/mcpServer\";\nimport { initializeTools } from \"../mcp/tools\";\nimport type { IPipeline } from \"../pipeline/interfaces\";\nimport type { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport { logger } from \"../utils/logger\";\n\n/**\n * Register MCP protocol routes on a Fastify server instance.\n * This includes SSE endpoints for persistent connections and HTTP endpoints for stateless requests.\n *\n * @returns The McpServer instance for cleanup\n */\nexport async function registerMcpService(\n server: FastifyInstance,\n docService: DocumentManagementService,\n pipeline: IPipeline,\n): Promise<McpServer> {\n // Initialize MCP server and tools\n const mcpTools = await initializeTools(docService, pipeline);\n const mcpServer = createMcpServerInstance(mcpTools);\n\n // Track SSE transports for cleanup\n const sseTransports: Record<string, SSEServerTransport> = {};\n\n // SSE endpoint for MCP connections\n server.route({\n method: \"GET\",\n url: \"/sse\",\n handler: async (_request: FastifyRequest, reply: FastifyReply) => {\n try {\n // Handle SSE connection using raw response\n const transport = new SSEServerTransport(\"/messages\", reply.raw);\n sseTransports[transport.sessionId] = transport;\n\n reply.raw.on(\"close\", () => {\n delete sseTransports[transport.sessionId];\n transport.close();\n });\n\n await mcpServer.connect(transport);\n } catch (error) {\n logger.error(`❌ Error in SSE endpoint: ${error}`);\n reply.code(500).send({\n error: error instanceof Error ? error.message : String(error),\n });\n }\n },\n });\n\n // SSE message handling endpoint\n server.route({\n method: \"POST\",\n url: \"/messages\",\n handler: async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const url = new URL(request.url, `http://${request.headers.host}`);\n const sessionId = url.searchParams.get(\"sessionId\");\n const transport = sessionId ? sseTransports[sessionId] : undefined;\n\n if (transport) {\n await transport.handlePostMessage(request.raw, reply.raw, request.body);\n } else {\n reply.code(400).send({ error: \"No transport found for sessionId\" });\n }\n } catch (error) {\n logger.error(`❌ Error in messages endpoint: ${error}`);\n reply.code(500).send({\n error: error instanceof Error ? error.message : String(error),\n });\n }\n },\n });\n\n // Streamable HTTP endpoint for stateless MCP requests\n server.route({\n method: \"POST\",\n url: \"/mcp\",\n handler: async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n // In stateless mode, create a new instance of server and transport for each request\n const requestServer = createMcpServerInstance(mcpTools);\n const requestTransport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n });\n\n reply.raw.on(\"close\", () => {\n logger.debug(\"Streamable HTTP request closed\");\n requestTransport.close();\n requestServer.close(); // Close the per-request server instance\n });\n\n await requestServer.connect(requestTransport);\n await requestTransport.handleRequest(request.raw, reply.raw, request.body);\n } catch (error) {\n logger.error(`❌ Error in MCP endpoint: ${error}`);\n reply.code(500).send({\n error: error instanceof Error ? error.message : String(error),\n });\n }\n },\n });\n\n // Store reference to SSE transports on the server instance for cleanup\n (\n mcpServer as unknown as { _sseTransports: Record<string, SSEServerTransport> }\n )._sseTransports = sseTransports;\n\n return mcpServer;\n}\n\n/**\n * Clean up MCP service resources including SSE transports.\n */\nexport async function cleanupMcpService(mcpServer: McpServer): Promise<void> {\n try {\n // Close all SSE transports\n const sseTransports = (\n mcpServer as unknown as { _sseTransports: Record<string, SSEServerTransport> }\n )._sseTransports;\n if (sseTransports) {\n for (const transport of Object.values(sseTransports)) {\n await transport.close();\n }\n }\n\n // Close MCP server\n await mcpServer.close();\n logger.debug(\"MCP service cleaned up\");\n } catch (error) {\n logger.error(`❌ Failed to cleanup MCP service: ${error}`);\n throw error;\n }\n}\n","/**\n * HTTP API service that exposes PipelineManager functionality via REST endpoints.\n * Used by external workers to provide pipeline services to remote clients.\n */\n\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport type { ScraperOptions } from \"../scraper/types\";\nimport { logger } from \"../utils/logger\";\nimport type { IPipeline } from \"./interfaces\";\nimport { type PipelineJob, PipelineJobStatus } from \"./types\";\n\n/**\n * Request/Response types for the pipeline API\n */\ninterface EnqueueJobRequest {\n library: string;\n version?: string | null;\n options: ScraperOptions;\n}\n\ninterface EnqueueJobResponse {\n jobId: string;\n}\n\ninterface GetJobsResponse {\n jobs: PipelineJob[];\n}\n\ninterface ClearJobsResponse {\n count: number;\n}\n\n/**\n * API service that provides HTTP endpoints for pipeline operations.\n */\nexport class PipelineApiService {\n private pipeline: IPipeline;\n\n constructor(pipeline: IPipeline) {\n this.pipeline = pipeline;\n }\n\n /**\n * Registers all pipeline API routes with the given Fastify instance.\n */\n async registerRoutes(server: FastifyInstance): Promise<void> {\n // Health check endpoint\n server.get(\"/api/health\", async (_request: FastifyRequest, reply: FastifyReply) => {\n return reply.send({ status: \"ok\", timestamp: new Date().toISOString() });\n });\n\n // Detailed health check\n server.get(\n \"/api/health/detailed\",\n async (_request: FastifyRequest, reply: FastifyReply) => {\n try {\n const jobs = await this.pipeline.getJobs();\n return reply.send({\n status: \"ok\",\n timestamp: new Date().toISOString(),\n jobCounts: {\n total: jobs.length,\n queued: jobs.filter((j) => j.status === PipelineJobStatus.QUEUED).length,\n running: jobs.filter((j) => j.status === PipelineJobStatus.RUNNING).length,\n completed: jobs.filter((j) => j.status === PipelineJobStatus.COMPLETED)\n .length,\n failed: jobs.filter((j) => j.status === PipelineJobStatus.FAILED).length,\n cancelled: jobs.filter((j) => j.status === PipelineJobStatus.CANCELLED)\n .length,\n },\n });\n } catch (error) {\n return reply.status(500).send({\n status: \"error\",\n error: error instanceof Error ? error.message : String(error),\n });\n }\n },\n );\n\n // Enqueue a new job\n server.post<{ Body: EnqueueJobRequest; Reply: EnqueueJobResponse }>(\n \"/api/jobs\",\n async (\n request: FastifyRequest<{ Body: EnqueueJobRequest }>,\n reply: FastifyReply,\n ) => {\n try {\n const { library, version, options } = request.body;\n\n if (!library || !options) {\n return reply\n .status(400)\n .send({ error: \"Missing required fields: library, options\" });\n }\n\n const jobId = await this.pipeline.enqueueJob(library, version, options);\n\n logger.debug(\n `API: Enqueued job ${jobId} for ${library}@${version || \"unversioned\"}`,\n );\n\n return reply.send({ jobId });\n } catch (error) {\n logger.error(`API: Failed to enqueue job: ${error}`);\n return reply.status(500).send({\n error: error instanceof Error ? error.message : String(error),\n });\n }\n },\n );\n\n // Get all jobs (with optional status filter)\n server.get<{ Querystring: { status?: PipelineJobStatus }; Reply: GetJobsResponse }>(\n \"/api/jobs\",\n async (\n request: FastifyRequest<{ Querystring: { status?: PipelineJobStatus } }>,\n reply: FastifyReply,\n ) => {\n try {\n const { status } = request.query;\n const jobs = await this.pipeline.getJobs(status);\n\n // Jobs are already in public format, no serialization needed\n return reply.send({ jobs });\n } catch (error) {\n logger.error(`API: Failed to get jobs: ${error}`);\n return reply.status(500).send({\n error: error instanceof Error ? error.message : String(error),\n });\n }\n },\n );\n\n // Get specific job by ID\n server.get<{ Params: { id: string } }>(\n \"/api/jobs/:id\",\n async (\n request: FastifyRequest<{ Params: { id: string } }>,\n reply: FastifyReply,\n ) => {\n try {\n const { id } = request.params;\n const job = await this.pipeline.getJob(id);\n\n if (!job) {\n return reply.status(404).send({ error: \"Job not found\" });\n }\n\n // Job is already in public format, no serialization needed\n return reply.send(job);\n } catch (error) {\n logger.error(`API: Failed to get job ${request.params.id}: ${error}`);\n return reply.status(500).send({\n error: error instanceof Error ? error.message : String(error),\n });\n }\n },\n );\n\n // Cancel specific job\n server.delete<{ Params: { id: string } }>(\n \"/api/jobs/:id\",\n async (\n request: FastifyRequest<{ Params: { id: string } }>,\n reply: FastifyReply,\n ) => {\n try {\n const { id } = request.params;\n await this.pipeline.cancelJob(id);\n\n logger.debug(`API: Cancelled job ${id}`);\n\n return reply.send({ success: true });\n } catch (error) {\n logger.error(`API: Failed to cancel job ${request.params.id}: ${error}`);\n return reply.status(500).send({\n error: error instanceof Error ? error.message : String(error),\n });\n }\n },\n );\n\n // Clear completed jobs\n server.delete<{ Reply: ClearJobsResponse }>(\n \"/api/jobs\",\n async (_request: FastifyRequest, reply: FastifyReply) => {\n try {\n const count = await this.pipeline.clearCompletedJobs();\n\n logger.debug(`API: Cleared ${count} completed jobs`);\n\n return reply.send({ count });\n } catch (error) {\n logger.error(`API: Failed to clear completed jobs: ${error}`);\n return reply.status(500).send({\n error: error instanceof Error ? error.message : String(error),\n });\n }\n },\n );\n\n logger.debug(\"Pipeline API routes registered\");\n }\n}\n","/**\n * Pipeline API service registration for modular server composition.\n * Wraps the existing PipelineApiService to provide a consistent function-based interface.\n */\n\nimport type { FastifyInstance } from \"fastify\";\nimport type { IPipeline } from \"../pipeline/interfaces\";\nimport { PipelineApiService } from \"../pipeline/PipelineApiService\";\n\n/**\n * Register Pipeline API routes on a Fastify server instance.\n * This provides HTTP endpoints for job management and pipeline operations.\n */\nexport async function registerPipelineApiService(\n server: FastifyInstance,\n pipeline: IPipeline,\n): Promise<void> {\n const pipelineApiService = new PipelineApiService(pipeline);\n await pipelineApiService.registerRoutes(server);\n}\n","import type { PropsWithChildren } from \"@kitajs/html\";\nimport { readFileSync } from \"node:fs\";\nimport { logger } from \"../../utils/logger\";\n\n/**\n * Props for the Layout component.\n */\ninterface LayoutProps extends PropsWithChildren {\n title: string;\n /** Optional version string to display next to the title. */\n version?: string;\n}\n\n/**\n * Base HTML layout component for all pages.\n * Includes common head elements, header, and scripts.\n * @param props - Component props including title, version, and children.\n */\nconst Layout = ({ title, version, children }: LayoutProps) => {\n let versionString = version;\n if (!versionString) {\n // If no version is provided, use the version from package.json\n // We cannot bake the version into the bundle, as the package.json will\n // be updated by the build process, after the bundle is created.\n try {\n const packageJson = JSON.parse(readFileSync(\"package.json\", \"utf-8\")) as {\n version: string;\n };\n versionString = packageJson.version;\n } catch (error) {\n logger.error(`Error reading package.json: ${error}`);\n }\n }\n return (\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title safe>{title}</title>\n {/* Bundled CSS (includes Tailwind and Flowbite) */}\n <link rel=\"stylesheet\" href=\"/assets/main.css\" />\n {/* Add style for htmx-indicator behavior (needed globally) */}\n <style>\n {`\n .htmx-indicator {\n display: none;\n }\n .htmx-request .htmx-indicator {\n display: block;\n }\n .htmx-request.htmx-indicator {\n display: block;\n }\n /* Default: Hide skeleton, show results container */\n #searchResultsContainer .search-skeleton { display: none; }\n #searchResultsContainer .search-results { display: block; } /* Or as needed */\n\n /* Request in progress: Show skeleton, hide results */\n #searchResultsContainer.htmx-request .search-skeleton { display: block; } /* Or flex etc. */\n #searchResultsContainer.htmx-request .search-results { display: none; }\n\n /* Keep button spinner logic */\n form .htmx-indicator .spinner { display: flex; }\n form .htmx-indicator .search-text { display: none; }\n form .spinner { display: none; }\n `}\n </style>\n </head>\n <body class=\"bg-gray-50 dark:bg-gray-900\">\n <div class=\"container max-w-2xl mx-auto px-4 py-4\">\n <header class=\"mb-4\">\n <h1 class=\"text-3xl font-bold text-gray-900 dark:text-white\">\n <a href=\"/\">MCP Docs</a>\n {versionString ? (\n <span\n safe\n class=\"ml-2 text-base font-normal text-gray-500 dark:text-gray-400 align-baseline\"\n title={`Version ${versionString}`}\n >\n v{versionString}\n </span>\n ) : null}\n </h1>\n </header>\n\n <main>{children}</main>\n </div>\n\n {/* Bundled JS (includes Flowbite, HTMX, AlpineJS, and initialization) */}\n <script type=\"module\" src=\"/assets/main.js\"></script>\n </body>\n </html>\n );\n};\n\nexport default Layout;\n","import type { FastifyInstance } from \"fastify\";\nimport Layout from \"../components/Layout\"; // Import the Layout component\n\n/**\n * Registers the root route that serves the main HTML page.\n * @param server - The Fastify instance.\n */\nexport function registerIndexRoute(server: FastifyInstance) {\n server.get(\"/\", async (_, reply) => {\n reply.type(\"text/html\");\n // Use the Layout component and define the main content within it\n return (\n \"<!DOCTYPE html>\" +\n (\n <Layout title=\"MCP Docs\">\n {/* Job Queue Section */}\n <section class=\"mb-4 p-4 bg-white rounded-lg shadow dark:bg-gray-800 border border-gray-300 dark:border-gray-600\">\n <div class=\"flex items-center justify-between mb-2\">\n <h2 class=\"text-xl font-semibold text-gray-900 dark:text-white\">\n Job Queue\n </h2>\n <button\n type=\"button\"\n class=\"text-xs px-3 py-1.5 text-gray-700 bg-gray-100 border border-gray-300 rounded-lg hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-gray-100 dark:bg-gray-600 dark:text-gray-300 dark:border-gray-500 dark:hover:bg-gray-700 dark:focus:ring-gray-700 transition-colors duration-150\"\n title=\"Clear all completed, cancelled, and failed jobs\"\n hx-post=\"/web/jobs/clear-completed\"\n hx-trigger=\"click\"\n hx-on=\"htmx:afterRequest: document.dispatchEvent(new Event('job-list-refresh'))\"\n hx-swap=\"none\"\n >\n Clear Completed Jobs\n </button>\n </div>\n {/* Container for the job list, loaded via HTMX */}\n <div id=\"job-queue\" hx-get=\"/web/jobs\" hx-trigger=\"load, every 1s\">\n {/* Initial loading state */}\n <div class=\"animate-pulse\">\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4\" />\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5\" />\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5\" />\n </div>\n </div>\n </section>\n {/* Add New Job Section */}\n <section class=\"mb-8\">\n {/* Container for the add job form, loaded via HTMX */}\n <div id=\"addJobForm\" hx-get=\"/web/jobs/new\" hx-trigger=\"load\">\n {/* Initial loading state (optional, could just be empty) */}\n <div class=\"p-6 bg-white rounded-lg shadow dark:bg-gray-800 animate-pulse\">\n <div class=\"h-6 bg-gray-200 rounded-full dark:bg-gray-700 w-1/3 mb-4\" />\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5\" />\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5\" />\n </div>\n </div>\n </section>\n {/* Indexed Documentation Section */}\n <div>\n <h2 class=\"text-xl font-semibold mb-2 text-gray-900 dark:text-white\">\n Indexed Documentation\n </h2>\n <div\n id=\"indexed-docs\"\n hx-get=\"/web/libraries\"\n hx-trigger=\"load, every 10s\"\n >\n <div class=\"animate-pulse\">\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4\" />\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5\" />\n <div class=\"h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5\" />\n </div>\n </div>\n </div>\n </Layout>\n )\n );\n });\n}\n","import type { FastifyInstance } from \"fastify\";\nimport type { CancelJobTool } from \"../../../tools/CancelJobTool\";\n\n/**\n * Registers the API route for cancelling jobs.\n * @param server - The Fastify instance.\n * @param cancelJobTool - The tool instance for cancelling jobs.\n */\nexport function registerCancelJobRoute(\n server: FastifyInstance,\n cancelJobTool: CancelJobTool\n) {\n // POST /web/jobs/:jobId/cancel - Cancel a job by ID\n server.post<{ Params: { jobId: string } }>(\n \"/web/jobs/:jobId/cancel\",\n async (request, reply) => {\n const { jobId } = request.params;\n const result = await cancelJobTool.execute({ jobId });\n if (result.success) {\n return { success: true, message: result.message };\n } else {\n reply.status(400);\n return { success: false, message: result.message };\n }\n }\n );\n}\n","import type { FastifyInstance } from \"fastify\";\nimport type { ClearCompletedJobsTool } from \"../../../tools/ClearCompletedJobsTool\";\n\n/**\n * Registers the API route for clearing completed jobs.\n * @param server - The Fastify instance.\n * @param clearCompletedJobsTool - The tool instance for clearing completed jobs.\n */\nexport function registerClearCompletedJobsRoute(\n server: FastifyInstance,\n clearCompletedJobsTool: ClearCompletedJobsTool\n) {\n // POST /web/jobs/clear-completed - Clear all completed jobs\n server.post(\"/web/jobs/clear-completed\", async (_, reply) => {\n try {\n const result = await clearCompletedJobsTool.execute({});\n\n reply.type(\"application/json\");\n return {\n success: result.success,\n message: result.message,\n };\n } catch (error) {\n reply.code(500);\n return {\n success: false,\n message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n });\n}\n","import type { ScrapeMode } from \"../scraper/types\";\nimport type { DocumentMetadata } from \"../types\";\n\n/** Default vector dimension used across the application */\nexport const VECTOR_DIMENSION = 1536;\n\n/**\n * Database document record type matching the documents table schema\n */\nexport interface DbDocument {\n id: string;\n library_id: number;\n version_id: number; // Changed from version: string to use foreign key\n url: string;\n content: string;\n metadata: string; // JSON string of DocumentMetadata\n embedding: string | null; // JSON string of number[]\n sort_order: number;\n score: number | null;\n}\n\n/**\n * Utility type for handling SQLite query results that may be undefined\n */\nexport type DbQueryResult<T> = T | undefined;\n\n/**\n * Maps raw database document to the Document type used by the application\n */\nexport function mapDbDocumentToDocument(doc: DbDocument) {\n return {\n id: doc.id,\n pageContent: doc.content,\n metadata: JSON.parse(doc.metadata) as DocumentMetadata,\n };\n}\n\n/**\n * Search result type returned by the DocumentRetrieverService\n */\nexport interface StoreSearchResult {\n url: string;\n content: string;\n score: number | null;\n}\n\n/**\n * Represents the possible states of a version's indexing status.\n * These statuses are stored in the database and persist across server restarts.\n */\nexport enum VersionStatus {\n NOT_INDEXED = \"not_indexed\", // Version created but never indexed\n QUEUED = \"queued\", // Waiting in pipeline queue\n RUNNING = \"running\", // Currently being indexed\n COMPLETED = \"completed\", // Successfully indexed\n FAILED = \"failed\", // Indexing failed\n CANCELLED = \"cancelled\", // Indexing was cancelled\n UPDATING = \"updating\", // Re-indexing existing version\n}\n\n/**\n * Scraper options stored with each version for reproducible indexing.\n * Excludes runtime-only fields like signal, library, version, and url.\n */\nexport interface VersionScraperOptions {\n // Core scraping parameters\n maxPages?: number;\n maxDepth?: number;\n scope?: \"subpages\" | \"hostname\" | \"domain\";\n followRedirects?: boolean;\n maxConcurrency?: number;\n ignoreErrors?: boolean;\n\n // Content filtering\n excludeSelectors?: string[];\n includePatterns?: string[];\n excludePatterns?: string[];\n\n // Processing options\n scrapeMode?: ScrapeMode;\n headers?: Record<string, string>;\n}\n\n/**\n * Database version record type matching the versions table schema.\n * Uses snake_case naming to match database column names.\n */\nexport interface DbVersion {\n id: number;\n library_id: number;\n name: string | null; // NULL for unversioned content\n created_at: string;\n\n // Status tracking fields (added in migration 005)\n status: VersionStatus;\n progress_pages: number;\n progress_max_pages: number;\n error_message: string | null;\n started_at: string | null; // When the indexing job started\n updated_at: string;\n\n // Scraper options fields (added in migration 006)\n source_url: string | null; // Original scraping URL\n scraper_options: string | null; // JSON string of VersionScraperOptions\n}\n\n/**\n * Version record with library name included from JOIN query.\n * Used when we need both version data and the associated library name.\n */\nexport interface DbVersionWithLibrary extends DbVersion {\n library_name: string;\n}\n\n/**\n * Enhanced version record with computed statistics.\n * Used when we need both database fields and aggregated document stats.\n */\nexport interface VersionWithStats extends DbVersion {\n document_count: number;\n unique_url_count: number;\n}\n\n/**\n * API-friendly version details for external consumption.\n * Uses camelCase naming for API compatibility.\n */\nexport interface LibraryVersionDetails {\n version: string; // Normalized to empty string for unversioned\n documentCount: number;\n uniqueUrlCount: number;\n indexedAt: string | null; // ISO 8601 format\n}\n\n/**\n * Helper function to convert NULL version name to empty string for API compatibility.\n * Database stores NULL for unversioned content, but APIs expect empty string.\n */\nexport function normalizeVersionName(name: string | null): string {\n return name ?? \"\";\n}\n\n/**\n * Helper function to convert empty string to NULL for database storage.\n * APIs use empty string for unversioned content, but database stores NULL.\n */\nexport function denormalizeVersionName(name: string): string | null {\n return name === \"\" ? null : name;\n}\n\n/**\n * Result type for findBestVersion, indicating the best semver match\n * and whether unversioned documents exist.\n */\nexport interface FindVersionResult {\n bestMatch: string | null;\n hasUnversioned: boolean;\n}\n\n/**\n * Validates if a status transition is allowed.\n * Prevents invalid state changes and ensures data consistency.\n */\nexport function isValidStatusTransition(\n currentStatus: VersionStatus,\n newStatus: VersionStatus,\n): boolean {\n // Define valid transitions for each status\n const validTransitions: Record<VersionStatus, VersionStatus[]> = {\n [VersionStatus.NOT_INDEXED]: [VersionStatus.QUEUED],\n [VersionStatus.QUEUED]: [VersionStatus.RUNNING, VersionStatus.CANCELLED],\n [VersionStatus.RUNNING]: [\n VersionStatus.COMPLETED,\n VersionStatus.FAILED,\n VersionStatus.CANCELLED,\n ],\n [VersionStatus.COMPLETED]: [VersionStatus.UPDATING],\n [VersionStatus.UPDATING]: [VersionStatus.RUNNING, VersionStatus.CANCELLED],\n [VersionStatus.FAILED]: [\n VersionStatus.QUEUED, // Allow retry\n ],\n [VersionStatus.CANCELLED]: [\n VersionStatus.QUEUED, // Allow retry\n ],\n };\n\n return validTransitions[currentStatus]?.includes(newStatus) ?? false;\n}\n\n/**\n * Gets a human-readable description of a version status.\n */\nexport function getStatusDescription(status: VersionStatus): string {\n const descriptions: Record<VersionStatus, string> = {\n [VersionStatus.NOT_INDEXED]: \"Version created but not yet indexed\",\n [VersionStatus.QUEUED]: \"Waiting in queue for indexing\",\n [VersionStatus.RUNNING]: \"Currently being indexed\",\n [VersionStatus.COMPLETED]: \"Successfully indexed\",\n [VersionStatus.FAILED]: \"Indexing failed\",\n [VersionStatus.CANCELLED]: \"Indexing was cancelled\",\n [VersionStatus.UPDATING]: \"Re-indexing in progress\",\n };\n\n return descriptions[status] || \"Unknown status\";\n}\n\n/**\n * Checks if a status represents a final state (job completed).\n */\nexport function isFinalStatus(status: VersionStatus): boolean {\n return [\n VersionStatus.COMPLETED,\n VersionStatus.FAILED,\n VersionStatus.CANCELLED,\n ].includes(status);\n}\n\n/**\n * Checks if a status represents an active state (job in progress).\n */\nexport function isActiveStatus(status: VersionStatus): boolean {\n return [VersionStatus.QUEUED, VersionStatus.RUNNING, VersionStatus.UPDATING].includes(\n status,\n );\n}\n","interface VersionBadgeProps {\n version: string | null;\n}\n\nconst VersionBadge = ({ version }: VersionBadgeProps) => {\n if (!version) {\n return null; // Don't render if no version is provided\n }\n\n return (\n <span class=\"bg-purple-100 text-purple-800 text-xs font-medium me-2 px-1.5 py-0.5 rounded dark:bg-purple-900 dark:text-purple-300\">\n <span safe>{version}</span>\n </span>\n );\n};\n\nexport default VersionBadge;\n","/**\n * StatusBadge component displays version status with appropriate styling.\n * Uses database VersionStatus and helper functions for proper display.\n */\n\nimport { VersionStatus, getStatusDescription } from \"../../store/types\";\n\ninterface StatusBadgeProps {\n status: VersionStatus;\n showDescription?: boolean;\n}\n\n/**\n * Get CSS classes for status badge based on status type.\n */\nfunction getStatusClasses(status: VersionStatus): string {\n const baseClasses = \"px-1.5 py-0.5 text-xs font-medium rounded\";\n\n switch (status) {\n case VersionStatus.COMPLETED:\n return `${baseClasses} bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300`;\n case VersionStatus.RUNNING:\n case VersionStatus.UPDATING:\n return `${baseClasses} bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300`;\n case VersionStatus.QUEUED:\n return `${baseClasses} bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300`;\n case VersionStatus.FAILED:\n return `${baseClasses} bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300`;\n case VersionStatus.CANCELLED:\n return `${baseClasses} bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300`;\n case VersionStatus.NOT_INDEXED:\n default:\n return `${baseClasses} bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400`;\n }\n}\n\nconst StatusBadge = ({ status, showDescription = true }: StatusBadgeProps) => (\n <span class={getStatusClasses(status)}>\n {showDescription ? getStatusDescription(status) : status}\n </span>\n);\n\nexport default StatusBadge;\n","/**\n * ProgressBar component displays indexing progress.\n * Shows pages processed out of total discovered pages with visual progress bar.\n * The progress reflects actual queue-based progress: processed vs. discovered pages.\n */\n\ninterface ProgressBarProps {\n progress: {\n pages: number;\n totalPages: number; // Effective total pages (limited by maxPages config)\n totalDiscovered: number; // Total pages actually discovered\n };\n showText?: boolean;\n}\n\nconst ProgressBar = ({ progress, showText = true }: ProgressBarProps) => {\n // Handle the initial case where we only know about 1 page (starting URL)\n // and haven't discovered any additional pages yet.\n const isIndeterminate = progress.totalDiscovered === 1;\n\n const percentage =\n progress.totalPages > 0\n ? Math.round((progress.pages / progress.totalPages) * 100)\n : 0;\n\n // Create the progress text\n const getProgressText = () => {\n if (isIndeterminate) {\n return \"Discovering pages...\";\n }\n\n const baseText = `${progress.pages}/${progress.totalPages} pages (${percentage}%)`;\n\n // If we discovered more pages than the limit, show the total discovered\n if (progress.totalDiscovered > progress.totalPages) {\n return `${baseText} • ${progress.totalDiscovered} total`;\n }\n\n return baseText;\n };\n\n return (\n <div class=\"w-full\">\n {showText && (\n <div class=\"flex justify-between text-xs text-gray-600 dark:text-gray-400 mb-1\">\n <span>Progress</span>\n <span>{getProgressText()}</span>\n </div>\n )}\n <div class=\"w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700\">\n {isIndeterminate ? (\n // Indeterminate progress bar with animation\n <div\n class=\"bg-blue-600 h-2 rounded-full animate-pulse\"\n style=\"width: 30%\"\n ></div>\n ) : (\n <div\n class=\"bg-blue-600 h-2 rounded-full transition-all duration-300\"\n style={`width: ${percentage}%`}\n ></div>\n )}\n </div>\n </div>\n );\n};\n\nexport default ProgressBar;\n","/**\n * Renders an SVG loading spinner icon.\n * Used for indicating loading states in buttons or other elements.\n */\nconst LoadingSpinner = () => (\n <svg\n class=\"animate-spin h-4 w-4 text-white\" // Adjusted size to h-4 w-4 to match usage\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n class=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"4\"\n ></circle>\n <path\n class=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n);\n\nexport default LoadingSpinner;\n","import type { JobInfo } from \"../../tools/GetJobInfoTool\";\nimport { PipelineJobStatus } from \"../../pipeline/types\";\nimport { VersionStatus, isActiveStatus } from \"../../store/types\";\nimport VersionBadge from \"./VersionBadge\";\nimport StatusBadge from \"./StatusBadge\";\nimport ProgressBar from \"./ProgressBar\";\nimport LoadingSpinner from \"./LoadingSpinner\";\n\n/**\n * Props for the JobItem component.\n */\ninterface JobItemProps {\n job: JobInfo;\n}\n\n/**\n * Renders a single job item with its details and status.\n * @param props - Component props including the job information.\n */\nconst JobItem = ({ job }: JobItemProps) => {\n // Use database status if available, fallback to pipeline status\n const displayStatus = job.dbStatus || job.status;\n const isActiveJob = job.dbStatus\n ? isActiveStatus(job.dbStatus)\n : job.status === PipelineJobStatus.QUEUED ||\n job.status === PipelineJobStatus.RUNNING;\n\n return (\n <div class=\"block p-3 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600\">\n <div class=\"flex items-start justify-between\">\n <div class=\"flex-1\">\n <p class=\"text-sm font-medium text-gray-900 dark:text-white\">\n <span safe>{job.library}</span>{\" \"}\n <VersionBadge version={job.version} />\n </p>\n\n {/* Timestamps */}\n <div class=\"text-xs text-gray-500 dark:text-gray-400 mt-1\">\n {job.startedAt ? (\n <div>\n Last Indexed:{\" \"}\n <span safe>{new Date(job.startedAt).toLocaleString()}</span>\n </div>\n ) : null}\n </div>\n\n {/* Progress bar for active jobs */}\n {job.progress && job.progress.totalPages > 0 && isActiveJob ? (\n <div class=\"mt-2\">\n <ProgressBar progress={job.progress} />\n </div>\n ) : null}\n\n {/* Error message display */}\n {(job.errorMessage || job.error) && (\n <div class=\"mt-2 p-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded text-xs\">\n <div class=\"font-medium text-red-800 dark:text-red-300 mb-1\">\n Error:\n </div>\n <div class=\"text-red-700 dark:text-red-400\">\n {job.errorMessage || job.error}\n </div>\n </div>\n )}\n </div>\n\n <div class=\"flex flex-col items-end gap-2 ml-4\">\n {/* Status badge */}\n <div class=\"flex items-center gap-2\">\n {job.dbStatus ? (\n <StatusBadge status={job.dbStatus} />\n ) : (\n <span\n class={`px-1.5 py-0.5 text-xs font-medium rounded ${\n job.status === PipelineJobStatus.COMPLETED\n ? \"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300\"\n : job.error\n ? \"bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300\"\n : \"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300\"\n }`}\n >\n {job.status}\n </span>\n )}\n\n {/* Stop button for active jobs */}\n {isActiveJob && (\n <button\n type=\"button\"\n class=\"font-medium rounded-lg text-xs p-1 text-center inline-flex items-center transition-colors duration-150 ease-in-out border border-gray-300 bg-white text-red-600 hover:bg-red-50 focus:ring-4 focus:outline-none focus:ring-red-100 dark:border-gray-600 dark:bg-gray-800 dark:text-red-400 dark:hover:bg-gray-700 dark:focus:ring-red-900\"\n title=\"Stop this job\"\n x-data=\"{}\"\n x-on:click={`\n if ($store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}') {\n $store.confirmingAction.isStopping = true;\n fetch('/web/jobs/' + '${job.id}' + '/cancel', {\n method: 'POST',\n headers: { 'Accept': 'application/json' },\n })\n .then(r => r.json())\n .then(() => {\n $store.confirmingAction.type = null;\n $store.confirmingAction.id = null;\n $store.confirmingAction.isStopping = false;\n if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }\n document.dispatchEvent(new CustomEvent('job-list-refresh'));\n })\n .catch(() => { $store.confirmingAction.isStopping = false; });\n } else {\n if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }\n $store.confirmingAction.type = 'job-cancel';\n $store.confirmingAction.id = '${job.id}';\n $store.confirmingAction.isStopping = false;\n $store.confirmingAction.timeoutId = setTimeout(() => {\n $store.confirmingAction.type = null;\n $store.confirmingAction.id = null;\n $store.confirmingAction.isStopping = false;\n $store.confirmingAction.timeoutId = null;\n }, 3000);\n }\n `}\n x-bind:disabled={`$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}' && $store.confirmingAction.isStopping`}\n >\n <span\n x-show={`$store.confirmingAction.type !== 'job-cancel' || $store.confirmingAction.id !== '${job.id}' || $store.confirmingAction.isStopping`}\n >\n {/* Red Stop Icon */}\n <svg\n class=\"w-4 h-4\"\n aria-hidden=\"true\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <rect x=\"5\" y=\"5\" width=\"10\" height=\"10\" rx=\"2\" />\n </svg>\n <span class=\"sr-only\">Stop job</span>\n </span>\n <span\n x-show={`$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}' && !$store.confirmingAction.isStopping`}\n class=\"px-2\"\n >\n Cancel?\n </span>\n <span\n x-show={`$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}' && $store.confirmingAction.isStopping`}\n >\n <LoadingSpinner />\n <span class=\"sr-only\">Stopping...</span>\n </span>\n </button>\n )}\n </div>\n {job.error && (\n // Keep the error badge for clarity if an error occurred\n <span class=\"bg-red-100 text-red-800 text-xs font-medium px-1.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300\">\n Error\n </span>\n )}\n </div>\n </div>\n </div>\n );\n};\n\nexport default JobItem;\n","import type { JobInfo } from \"../../tools/GetJobInfoTool\";\nimport JobItem from \"./JobItem\"; // Adjusted import path\n\n/**\n * Props for the JobList component.\n */\ninterface JobListProps {\n jobs: JobInfo[];\n}\n\n/**\n * Renders a list of JobItem components or a message if the list is empty.\n * Adds a listener for the 'job-list-refresh' event to trigger a reload of the job list using HTMX.\n * @param props - Component props including the array of jobs.\n */\nconst JobList = ({ jobs }: JobListProps) => (\n <div id=\"job-list\" class=\"space-y-2\">\n {jobs.length === 0 ? (\n <p class=\"text-center text-gray-500 dark:text-gray-400\">\n No pending jobs.\n </p>\n ) : (\n jobs.map((job) => <JobItem job={job} />)\n )}\n {/* NOTE: To enable live job list refresh after stopping a job, add the following script to your main HTML layout or main.client.ts:\n document.addEventListener('job-list-refresh', function () {\n if (window.htmx) {\n window.htmx.ajax('GET', '/web/jobs', '#job-list');\n } else {\n window.location.reload();\n }\n });\n */}\n </div>\n);\n\nexport default JobList;\n","import type { FastifyInstance } from \"fastify\";\nimport type { ListJobsTool } from \"../../../tools/ListJobsTool\"; // Adjusted import path\nimport JobList from \"../../components/JobList\"; // Import the extracted component\n\n/**\n * Registers the API route for listing jobs.\n * @param server - The Fastify instance.\n * @param listJobsTool - The tool instance for listing jobs.\n */\nexport function registerJobListRoutes(\n server: FastifyInstance,\n listJobsTool: ListJobsTool\n) {\n // GET /web/jobs - List current jobs (only the list)\n server.get(\"/web/jobs\", async () => {\n const result = await listJobsTool.execute({});\n return <JobList jobs={result.jobs} />;\n });\n}\n","import type { PropsWithChildren } from \"@kitajs/html\";\n\n/**\n * Defines the possible types for the Alert component.\n */\ntype AlertType = \"success\" | \"error\" | \"warning\" | \"info\";\n\n/**\n * Props for the Alert component.\n */\ninterface AlertProps extends PropsWithChildren {\n type: AlertType;\n title?: string;\n message: string | JSX.Element; // Allow JSX for messages\n}\n\n/**\n * Reusable Alert component using Flowbite styling.\n * Displays messages with appropriate colors and icons based on the type.\n * @param props - Component props including type, title (optional), and message.\n */\nconst Alert = ({ type, title, message }: AlertProps) => {\n let iconSvg: JSX.Element;\n let colorClasses: string;\n let defaultTitle: string;\n\n switch (type) {\n case \"success\":\n defaultTitle = \"Success:\";\n colorClasses =\n \"text-green-800 border-green-300 bg-green-50 dark:bg-gray-800 dark:text-green-400 dark:border-green-800\";\n iconSvg = (\n <svg\n class=\"flex-shrink-0 inline w-4 h-4 me-3\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path d=\"M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm9.5 9.5A9.5 9.5 0 0 1 10 19a9.46 9.46 0 0 1-1.671-.14c-.165-.05-.3-.19-.42-.335l-.165-.165c-.19-.2-.3-.425-.3-.655A4.2 4.2 0 0 1 4.5 10a4.25 4.25 0 0 1 7.462-2.882l1.217 1.217a3.175 3.175 0 0 0 4.5.01l.106-.106a.934.934 0 0 0 .1-.36ZM10 11a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z\" />\n </svg>\n );\n break;\n case \"error\":\n defaultTitle = \"Error:\";\n colorClasses =\n \"text-red-800 border-red-300 bg-red-50 dark:bg-gray-800 dark:text-red-400 dark:border-red-800\";\n iconSvg = (\n <svg\n class=\"flex-shrink-0 inline w-4 h-4 me-3\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path d=\"M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3h-1a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z\" />\n </svg>\n );\n break;\n case \"warning\":\n defaultTitle = \"Warning:\";\n colorClasses =\n \"text-yellow-800 border-yellow-300 bg-yellow-50 dark:bg-gray-800 dark:text-yellow-300 dark:border-yellow-800\";\n iconSvg = (\n <svg\n class=\"flex-shrink-0 inline w-4 h-4 me-3\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path d=\"M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3h-1a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z\" />\n </svg>\n );\n break;\n case \"info\":\n default: // Default to info style\n defaultTitle = \"Info:\";\n colorClasses =\n \"text-blue-800 border-blue-300 bg-blue-50 dark:bg-gray-800 dark:text-blue-400 dark:border-blue-800\";\n iconSvg = (\n <svg\n class=\"flex-shrink-0 inline w-4 h-4 me-3\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path d=\"M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z\" />\n </svg>\n );\n break;\n }\n\n const displayTitle = title ?? defaultTitle;\n\n return (\n <div\n class={`flex items-center p-4 mb-4 text-sm border rounded-lg ${colorClasses}`}\n role=\"alert\"\n >\n {iconSvg}\n <span class=\"sr-only\">Info</span>\n <div>\n {displayTitle ? (\n <span class=\"font-medium\" safe>\n {displayTitle}\n </span>\n ) : null}{\" \"}\n {message}\n </div>\n </div>\n );\n};\n\nexport default Alert;\n","import type { PropsWithChildren } from \"@kitajs/html\";\n\n/**\n * Props for the Tooltip component.\n */\ninterface TooltipProps extends PropsWithChildren {\n text: string | Promise<string> | Element;\n position?: \"top\" | \"right\" | \"bottom\" | \"left\";\n}\n\n/**\n * Reusable Tooltip component using Alpine.js for state management.\n * Displays a help icon that shows a tooltip on hover/focus.\n *\n * @param props - Component props including text and optional position.\n */\nconst Tooltip = ({ text, position = \"top\" }: TooltipProps) => {\n // Map position to Tailwind classes\n const positionClasses = {\n top: \"bottom-full left-1/2 transform -translate-x-1/2 -translate-y-1 mb-1\",\n right: \"left-full top-1/2 transform -translate-y-1/2 translate-x-1 ml-1\",\n bottom: \"top-full left-1/2 transform -translate-x-1/2 translate-y-1 mt-1\",\n left: \"right-full top-1/2 transform -translate-y-1/2 -translate-x-1 mr-1\",\n };\n\n return (\n <div\n class=\"relative ml-1.5 flex items-center\"\n x-data=\"{ isVisible: false }\"\n >\n <button\n type=\"button\"\n class=\"text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400 focus:outline-none flex items-center\"\n aria-label=\"Help\"\n x-on:mouseenter=\"isVisible = true\"\n x-on:mouseleave=\"isVisible = false\"\n x-on:focus=\"isVisible = true\"\n x-on:blur=\"isVisible = false\"\n tabindex=\"0\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n class=\"w-4 h-4\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z\"\n />\n </svg>\n </button>\n <div\n x-show=\"isVisible\"\n x-cloak\n class={`absolute z-10 w-64 p-2 text-sm text-gray-500 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400 ${positionClasses[position]}`}\n >\n {text as \"safe\"}\n </div>\n </div>\n );\n};\n\nexport default Tooltip;\n","import { ScrapeMode } from \"../../scraper/types\"; // Adjusted import path\nimport Alert from \"./Alert\";\nimport Tooltip from \"./Tooltip\";\n\n/**\n * Renders the form fields for queuing a new scrape job.\n * Includes basic fields (URL, Library, Version) and advanced options.\n */\nconst ScrapeFormContent = () => (\n <div class=\"mt-4 p-4 bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-300 dark:border-gray-600\">\n <h3 class=\"text-xl font-semibold text-gray-900 dark:text-white mb-2\">\n Queue New Scrape Job\n </h3>\n <form\n hx-post=\"/web/jobs/scrape\"\n hx-target=\"#job-response\"\n hx-swap=\"innerHTML\"\n class=\"space-y-2\"\n x-data=\"{\n url: '',\n hasPath: false,\n headers: [],\n checkUrlPath() {\n try {\n const url = new URL(this.url);\n this.hasPath = url.pathname !== '/' && url.pathname !== '';\n } catch (e) {\n this.hasPath = false;\n }\n }\n }\"\n >\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"url\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n URL\n </label>\n <Tooltip\n text={\n <div>\n <p>Enter the URL of the documentation you want to scrape.</p>\n <p class=\"mt-2\">\n For local files/folders, you must use the <code>file://</code>{\" \"}\n prefix and ensure the path is accessible to the server.\n </p>\n <p class=\"mt-2\">\n If running in Docker, <b>mount the folder</b> (see README for\n details).\n </p>\n </div>\n }\n />\n </div>\n <input\n type=\"url\"\n name=\"url\"\n id=\"url\"\n required\n x-model=\"url\"\n x-on:input=\"checkUrlPath\"\n x-on:paste=\"$nextTick(() => checkUrlPath())\"\n class=\"mt-0.5 block w-full px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n />\n <div\n x-show=\"hasPath && !(url.startsWith('file://'))\"\n x-cloak\n x-transition:enter=\"transition ease-out duration-300\"\n x-transition:enter-start=\"opacity-0 transform -translate-y-2\"\n x-transition:enter-end=\"opacity-100 transform translate-y-0\"\n class=\"mt-2\"\n >\n <Alert\n type=\"info\"\n message=\"By default, only subpages under the given URL will be scraped. To scrape the whole website, adjust the 'Scope' option in Advanced Options.\"\n />\n </div>\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"library\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Library Name\n </label>\n <Tooltip text=\"The name of the library you're documenting. This will be used when searching.\" />\n </div>\n <input\n type=\"text\"\n name=\"library\"\n id=\"library\"\n required\n class=\"mt-0.5 block w-full px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n />\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"version\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Version (optional)\n </label>\n <Tooltip text=\"Specify the version of the library documentation you're indexing. This allows for version-specific searches.\" />\n </div>\n <input\n type=\"text\"\n name=\"version\"\n id=\"version\"\n class=\"mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n />\n </div>\n\n {/* Consider using Flowbite Accordion here */}\n <details class=\"bg-gray-50 dark:bg-gray-900 p-2 rounded-md\">\n <summary class=\"cursor-pointer text-sm font-medium text-gray-600 dark:text-gray-400\">\n Advanced Options\n </summary>\n <div class=\"mt-2 space-y-2\" x-data=\"{ headers: [] }\">\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"maxPages\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Max Pages\n </label>\n <Tooltip text=\"The maximum number of pages to scrape. Default is 1000. Setting this too high may result in longer processing times.\" />\n </div>\n <input\n type=\"number\"\n name=\"maxPages\"\n id=\"maxPages\"\n min=\"1\"\n placeholder=\"1000\"\n class=\"mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n />\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"maxDepth\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Max Depth\n </label>\n <Tooltip text=\"How many links deep the scraper should follow. Default is 3. Higher values capture more content but increase processing time.\" />\n </div>\n <input\n type=\"number\"\n name=\"maxDepth\"\n id=\"maxDepth\"\n min=\"0\"\n placeholder=\"3\"\n class=\"mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n />\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"scope\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Scope\n </label>\n <Tooltip\n text={\n <div>\n Controls which pages are scraped:\n <ul class=\"list-disc pl-5\">\n <li>'Subpages' only scrapes under the given URL path,</li>\n <li>\n 'Hostname' scrapes all content on the same host (e.g.,\n all of docs.example.com),\n </li>\n <li>\n 'Domain' scrapes all content on the domain and its\n subdomains (e.g., all of example.com).\n </li>\n </ul>\n </div>\n }\n />\n </div>\n <select\n name=\"scope\"\n id=\"scope\"\n class=\"mt-0.5 block w-full max-w-sm pl-2 pr-10 py-1 text-base border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n >\n <option value=\"subpages\" selected>\n Subpages (Default)\n </option>\n <option value=\"hostname\">Hostname</option>\n <option value=\"domain\">Domain</option>\n </select>\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"includePatterns\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Include Patterns\n </label>\n <Tooltip text=\"Glob or regex patterns for URLs to include. One per line or comma-separated. Regex patterns must be wrapped in slashes, e.g. /pattern/.\" />\n </div>\n <textarea\n name=\"includePatterns\"\n id=\"includePatterns\"\n rows=\"2\"\n placeholder=\"e.g. docs/* or /api\\/v1.*/\"\n class=\"mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n ></textarea>\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"excludePatterns\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Exclude Patterns\n </label>\n <Tooltip text=\"Glob or regex patterns for URLs to exclude. One per line or comma-separated. Exclude takes precedence over include. Regex patterns must be wrapped in slashes, e.g. /pattern/.\" />\n </div>\n <textarea\n name=\"excludePatterns\"\n id=\"excludePatterns\"\n rows=\"2\"\n placeholder=\"e.g. private/* or /internal/\"\n class=\"mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n ></textarea>\n </div>\n <div>\n <div class=\"flex items-center\">\n <label\n for=\"scrapeMode\"\n class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\"\n >\n Scrape Mode\n </label>\n <Tooltip\n text={\n <div>\n <ul class=\"list-disc pl-5\">\n <li>'Auto' automatically selects the best method,</li>\n <li>\n 'Fetch' uses simple HTTP requests (faster but may miss\n dynamic content),\n </li>\n <li>\n 'Playwright' uses a headless browser (slower but better\n for JS-heavy sites).\n </li>\n </ul>\n </div>\n }\n />\n </div>\n <select\n name=\"scrapeMode\"\n id=\"scrapeMode\"\n class=\"mt-0.5 block w-full max-w-sm pl-2 pr-10 py-1 text-base border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white\"\n >\n <option value={ScrapeMode.Auto} selected>\n Auto (Default)\n </option>\n <option value={ScrapeMode.Fetch}>Fetch</option>\n <option value={ScrapeMode.Playwright}>Playwright</option>\n </select>\n </div>\n <div>\n <div class=\"flex items-center mb-1\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\">\n Custom HTTP Headers\n </label>\n <Tooltip text=\"Add custom HTTP headers (e.g., for authentication). These will be sent with every HTTP request.\" />\n </div>\n <div>\n {/* AlpineJS dynamic header rows */}\n <template x-for=\"(header, idx) in headers\">\n <div class=\"flex space-x-2 mb-1\">\n <input\n type=\"text\"\n class=\"w-1/3 px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-xs\"\n placeholder=\"Header Name\"\n x-model=\"header.name\"\n required\n />\n <span class=\"text-gray-500\">:</span>\n <input\n type=\"text\"\n class=\"w-1/2 px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-xs\"\n placeholder=\"Header Value\"\n x-model=\"header.value\"\n required\n />\n <button\n type=\"button\"\n class=\"text-red-500 hover:text-red-700 text-xs\"\n x-on:click=\"headers.splice(idx, 1)\"\n >\n Remove\n </button>\n <input\n type=\"hidden\"\n name=\"header[]\"\n x-bind:value=\"header.name && header.value ? header.name + ':' + header.value : ''\"\n />\n </div>\n </template>\n <button\n type=\"button\"\n class=\"mt-1 px-2 py-0.5 bg-indigo-100 dark:bg-indigo-900 text-indigo-700 dark:text-indigo-200 rounded text-xs\"\n x-on:click=\"headers.push({ name: '', value: '' })\"\n >\n + Add Header\n </button>\n </div>\n </div>\n <div class=\"flex items-center\">\n <input\n id=\"followRedirects\"\n name=\"followRedirects\"\n type=\"checkbox\"\n checked\n class=\"h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700\"\n />\n <label\n for=\"followRedirects\"\n class=\"ml-1 block text-sm text-gray-900 dark:text-gray-300\"\n >\n Follow Redirects\n </label>\n </div>\n <div class=\"flex items-center\">\n <input\n id=\"ignoreErrors\"\n name=\"ignoreErrors\"\n type=\"checkbox\"\n checked\n class=\"h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700\"\n />\n <label\n for=\"ignoreErrors\"\n class=\"ml-1 block text-sm text-gray-900 dark:text-gray-300\"\n >\n Ignore Errors During Scraping\n </label>\n </div>\n </div>\n </details>\n\n <div>\n <button\n type=\"submit\"\n class=\"w-full flex justify-center py-1.5 px-3 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500\"\n >\n Queue Job\n </button>\n </div>\n </form>\n {/* Target div for HTMX response */}\n <div id=\"job-response\" class=\"mt-2 text-sm\"></div>\n </div>\n);\n\nexport default ScrapeFormContent;\n","import ScrapeFormContent from \"./ScrapeFormContent\"; // Adjusted import path\n\n/**\n * Wrapper component for the ScrapeFormContent.\n * Provides a container div, often used as a target for HTMX OOB swaps.\n */\nconst ScrapeForm = () => (\n <div id=\"scrape-form-container\">\n <ScrapeFormContent />\n </div>\n);\n\nexport default ScrapeForm;\n","import type { FastifyInstance, FastifyRequest } from \"fastify\";\nimport type { ScrapeTool } from \"../../../tools/ScrapeTool\"; // Adjusted import path\nimport { ScrapeMode } from \"../../../scraper/types\"; // Adjusted import path\nimport { logger } from \"../../../utils/logger\"; // Adjusted import path\nimport ScrapeForm from \"../../components/ScrapeForm\"; // Import extracted component\nimport Alert from \"../../components/Alert\"; // Import Alert component\nimport ScrapeFormContent from \"../../components/ScrapeFormContent\"; // Import for OOB swap\n\n/**\n * Registers the API routes for creating new jobs.\n * @param server - The Fastify instance.\n * @param scrapeTool - The tool instance for scraping documents.\n */\nexport function registerNewJobRoutes(\n server: FastifyInstance,\n scrapeTool: ScrapeTool\n) {\n // GET /web/jobs/new - Return the form component wrapped in its container\n server.get(\"/web/jobs/new\", async () => {\n // Return the wrapper component which includes the container div\n return <ScrapeForm />;\n });\n\n // POST /web/jobs/scrape - Queue a new scrape job\n server.post(\n \"/web/jobs/scrape\",\n async (\n request: FastifyRequest<{\n Body: {\n url: string;\n library: string;\n version?: string;\n maxPages?: string;\n maxDepth?: string;\n scope?: \"subpages\" | \"hostname\" | \"domain\";\n scrapeMode?: ScrapeMode;\n followRedirects?: \"on\" | undefined; // Checkbox value is 'on' if checked\n ignoreErrors?: \"on\" | undefined;\n includePatterns?: string;\n excludePatterns?: string;\n \"header[]\"?: string[] | string; // Added header field for custom headers\n };\n }>,\n reply\n ) => {\n const body = request.body;\n reply.type(\"text/html\"); // Set content type for all responses from this handler\n try {\n // Basic validation\n if (!body.url || !body.library) {\n reply.status(400);\n // Use Alert component for validation error\n return (\n <Alert\n type=\"error\"\n title=\"Validation Error:\"\n message=\"URL and Library Name are required.\"\n />\n );\n }\n\n // Parse includePatterns and excludePatterns from textarea input\n function parsePatterns(input?: string): string[] | undefined {\n if (!input) return undefined;\n return input\n .split(/\\n|,/)\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n }\n\n // Parse custom headers from repeated header[] fields (format: name:value)\n function parseHeaders(\n input?: string[] | string\n ): Record<string, string> | undefined {\n if (!input) return undefined;\n const arr = Array.isArray(input) ? input : [input];\n const headers: Record<string, string> = {};\n for (const entry of arr) {\n const idx = entry.indexOf(\":\");\n if (idx > 0) {\n const name = entry.slice(0, idx).trim();\n const value = entry.slice(idx + 1).trim();\n if (name) headers[name] = value;\n }\n }\n return Object.keys(headers).length > 0 ? headers : undefined;\n }\n\n // Prepare options for ScrapeTool\n const scrapeOptions = {\n url: body.url,\n library: body.library,\n version: body.version || null, // Handle empty string as null\n waitForCompletion: false, // Don't wait in UI\n options: {\n maxPages: body.maxPages\n ? Number.parseInt(body.maxPages, 10)\n : undefined,\n maxDepth: body.maxDepth\n ? Number.parseInt(body.maxDepth, 10)\n : undefined,\n scope: body.scope,\n scrapeMode: body.scrapeMode,\n // Checkboxes send 'on' when checked, otherwise undefined\n followRedirects: body.followRedirects === \"on\",\n ignoreErrors: body.ignoreErrors === \"on\",\n includePatterns: parsePatterns(body.includePatterns),\n excludePatterns: parsePatterns(body.excludePatterns),\n headers: parseHeaders(body[\"header[]\"]), // <-- propagate custom headers from web UI\n },\n };\n\n // Execute the scrape tool\n const result = await scrapeTool.execute(scrapeOptions);\n\n if (\"jobId\" in result) {\n // Success: Use Alert component and OOB swap\n return (\n <>\n {/* Main target response */}\n <Alert\n type=\"success\"\n message={\n <>\n Job queued successfully! ID:{\" \"}\n <span safe>{result.jobId}</span>\n </>\n }\n />\n {/* OOB target response - contains only the inner form content */}\n <div id=\"scrape-form-container\" hx-swap-oob=\"innerHTML\">\n <ScrapeFormContent />\n </div>\n </>\n );\n }\n\n // This case shouldn't happen with waitForCompletion: false, but handle defensively\n // Use Alert component for unexpected success\n return (\n <Alert type=\"warning\" message=\"Job finished unexpectedly quickly.\" />\n );\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : \"Unknown error\";\n logger.error(`Scrape job submission failed: ${error}`);\n reply.status(500); // Keep status code for errors\n // Use Alert component for server error\n return (\n <Alert\n type=\"error\"\n message={<>Failed to queue job: {errorMessage}</>}\n />\n );\n }\n }\n );\n}\n","import type { LibraryVersionDetails } from \"../../store/types\";\nimport VersionBadge from \"./VersionBadge\"; // Adjusted import path\nimport LoadingSpinner from \"./LoadingSpinner\"; // Import spinner\n\n/**\n * Props for the VersionDetailsRow component.\n */\ninterface VersionDetailsRowProps {\n version: LibraryVersionDetails;\n libraryName: string;\n showDelete?: boolean; // Optional prop to control delete button visibility\n}\n\n/**\n * Renders details for a single library version in a row format.\n * Includes version, stats, and an optional delete button.\n * @param props - Component props including version, libraryName, and showDelete flag.\n */\nconst VersionDetailsRow = ({\n version,\n libraryName,\n showDelete = true, // Default to true\n}: VersionDetailsRowProps) => {\n // Format the indexed date nicely, handle null case\n const indexedDate = version.indexedAt\n ? new Date(version.indexedAt).toLocaleDateString()\n : \"N/A\";\n // Display 'Unversioned' if version string is empty\n const versionLabel = version.version || \"Unversioned\";\n // Use empty string for unversioned in param and rowId\n const versionParam = version.version || \"\";\n\n // Sanitize both libraryName and versionParam for valid CSS selector\n const sanitizedLibraryName = libraryName.replace(/[^a-zA-Z0-9-_]/g, \"-\");\n const sanitizedVersionParam = versionParam.replace(/[^a-zA-Z0-9-_]/g, \"-\");\n const rowId = `row-${sanitizedLibraryName}-${sanitizedVersionParam}`;\n\n // Define state-specific button classes for Alpine toggling\n const defaultStateClasses =\n \"text-red-700 border border-red-700 hover:bg-red-700 hover:text-white focus:ring-4 focus:outline-none focus:ring-red-300 dark:border-red-500 dark:text-red-500 dark:hover:text-white dark:focus:ring-red-800 dark:hover:bg-red-500\";\n const confirmingStateClasses =\n \"bg-red-600 text-white border-red-600 focus:ring-4 focus:outline-none focus:ring-red-300 dark:bg-red-700 dark:border-red-700 dark:focus:ring-red-800\";\n\n return (\n // Use flexbox for layout, add border between rows\n <div\n id={rowId}\n class=\"flex justify-between items-center py-1 border-b border-gray-200 dark:border-gray-600 last:border-b-0\"\n >\n {/* Version Label */}\n <span\n class=\"text-sm text-gray-900 dark:text-white w-1/4 truncate\"\n title={versionLabel}\n >\n {version.version ? (\n <VersionBadge version={version.version} />\n ) : (\n <span>Unversioned</span>\n )}\n </span>\n\n {/* Stats Group */}\n <div class=\"flex space-x-2 text-sm text-gray-600 dark:text-gray-400 w-3/4 justify-end items-center\">\n <span title=\"Number of unique pages indexed\">\n Pages:{\" \"}\n <span class=\"font-semibold\" safe>\n {version.uniqueUrlCount.toLocaleString()}\n </span>\n </span>\n <span title=\"Number of indexed snippets\">\n Snippets:{\" \"}\n <span class=\"font-semibold\" safe>\n {version.documentCount.toLocaleString()}\n </span>\n </span>\n <span title=\"Date last indexed\">\n Last Update:{\" \"}\n <span class=\"font-semibold\" safe>\n {indexedDate}\n </span>\n </span>\n </div>\n\n {/**\n * Conditionally renders a delete button for the version row.\n * The button has three states:\n * 1. Default: Displays a trash icon.\n * 2. Confirming: Displays a confirmation text with an accessible label.\n * 3. Deleting: Displays a spinner icon indicating the deletion process.\n * The button uses AlpineJS for state management and htmx for server interaction.\n */}\n {showDelete && (\n <button\n type=\"button\"\n class=\"ml-2 font-medium rounded-lg text-sm p-1 text-center inline-flex items-center transition-colors duration-150 ease-in-out\"\n title=\"Remove this version\"\n x-data=\"{}\"\n x-bind:class={`$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' ? '${confirmingStateClasses}' : '${defaultStateClasses}'`}\n x-bind:disabled={`$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && $store.confirmingAction.isDeleting`}\n x-on:click={`\n if ($store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}') {\n $store.confirmingAction.isDeleting = true;\n $el.dispatchEvent(new CustomEvent('confirmed-delete', { bubbles: true }));\n } else {\n if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }\n $store.confirmingAction.type = 'version-delete';\n $store.confirmingAction.id = '${libraryName}:${versionParam}';\n $store.confirmingAction.isDeleting = false;\n $store.confirmingAction.timeoutId = setTimeout(() => {\n $store.confirmingAction.type = null;\n $store.confirmingAction.id = null;\n $store.confirmingAction.isDeleting = false;\n $store.confirmingAction.timeoutId = null;\n }, 3000);\n }\n `}\n hx-delete={`/web/libraries/${encodeURIComponent(libraryName)}/versions/${encodeURIComponent(versionParam)}`}\n hx-target={`#${rowId}`}\n hx-swap=\"outerHTML\"\n hx-trigger=\"confirmed-delete\"\n >\n {/* Default State: Trash Icon */}\n <span\n x-show={`!($store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && $store.confirmingAction.isDeleting)`}\n >\n <svg\n class=\"w-4 h-4\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 18 20\"\n >\n <path\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M1 5h16M7 8v8m4-8v8M7 1h4a1 1 0 0 1 1 1v3H6V2a1 1 0 0 1-1-1ZM3 5h12v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5Z\"\n />\n </svg>\n <span class=\"sr-only\">Remove version</span>\n </span>\n\n {/* Confirming State: Text */}\n <span\n x-show={`$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && !$store.confirmingAction.isDeleting`}\n >\n Confirm?<span class=\"sr-only\">Confirm delete</span>\n </span>\n\n {/* Deleting State: Spinner Icon */}\n <span\n x-show={`$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && $store.confirmingAction.isDeleting`}\n >\n <LoadingSpinner />\n <span class=\"sr-only\">Loading...</span>\n </span>\n </button>\n )}\n </div>\n );\n};\n\nexport default VersionDetailsRow;\n","import type { LibraryInfo } from \"../../tools/ListLibrariesTool\";\nimport VersionDetailsRow from \"./VersionDetailsRow\"; // Adjusted import path\n\n/**\n * Props for the LibraryDetailCard component.\n */\ninterface LibraryDetailCardProps {\n library: LibraryInfo;\n}\n\n/**\n * Renders a card displaying library details and its versions.\n * Uses VersionDetailsRow without the delete button.\n * @param props - Component props including the library information.\n */\nconst LibraryDetailCard = ({ library }: LibraryDetailCardProps) => (\n // Use Flowbite Card structure with updated padding and border, and white background\n <div class=\"block p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-300 dark:border-gray-600 mb-4\">\n <h3 class=\"text-lg font-medium text-gray-900 dark:text-white mb-1\">\n <span safe>{library.name}</span>\n </h3>\n {/* Container for version rows */}\n <div class=\"mt-1\">\n {library.versions.length > 0 ? (\n library.versions.map((version) => (\n <VersionDetailsRow\n libraryName={library.name}\n version={version}\n showDelete={false} // Explicitly hide delete button\n />\n ))\n ) : (\n // Display message if no versions are indexed\n <p class=\"text-sm text-gray-500 dark:text-gray-400 italic\">\n No versions indexed.\n </p>\n )}\n </div>\n </div>\n);\n\nexport default LibraryDetailCard;\n","import type { LibraryInfo } from \"../../tools/ListLibrariesTool\";\nimport LoadingSpinner from \"./LoadingSpinner\"; // Import spinner\n\n/**\n * Props for the LibrarySearchCard component.\n */\ninterface LibrarySearchCardProps {\n library: LibraryInfo;\n}\n\n/**\n * Renders the search form card for a specific library.\n * Includes a version dropdown and query input.\n * @param props - Component props including the library information.\n */\nconst LibrarySearchCard = ({ library }: LibrarySearchCardProps) => {\n return (\n <div class=\"block p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-300 dark:border-gray-600 mb-4\">\n <h2 class=\"text-xl font-semibold mb-2 text-gray-900 dark:text-white\" safe>\n Search {library.name} Documentation\n </h2>\n <form\n hx-get={`/web/libraries/${encodeURIComponent(library.name)}/search`}\n hx-target=\"#searchResultsContainer .search-results\"\n hx-swap=\"innerHTML\"\n hx-indicator=\"#searchResultsContainer\"\n class=\"flex space-x-2\"\n >\n <select\n name=\"version\"\n class=\"w-40 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\"\n >\n <option value=\"\">Latest</option> {/* Default to latest */}\n {library.versions.map((version) => (\n <option value={version.version || \"unversioned\"} safe>\n {version.version || \"Unversioned\"}\n </option>\n ))}\n </select>\n <input\n type=\"text\"\n name=\"query\"\n placeholder=\"Search query...\"\n required\n class=\"flex-grow bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\"\n />\n <button\n type=\"submit\"\n class=\"text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 relative\"\n >\n <span class=\"search-text\">Search</span>\n {/* Spinner for HTMX loading - shown via htmx-indicator class on parent */}\n <span class=\"spinner absolute inset-0 flex items-center justify-center\">\n <LoadingSpinner />\n </span>\n </button>\n </form>\n {/* Add style for htmx-indicator behavior on button */}\n {/* Styles moved to Layout.tsx */}\n </div>\n );\n};\n\nexport default LibrarySearchCard;\n","import { unified } from \"unified\"; // Import unified\nimport remarkParse from \"remark-parse\"; // Import unified plugins\nimport remarkGfm from \"remark-gfm\";\nimport remarkHtml from \"remark-html\";\nimport DOMPurify from \"dompurify\"; // Import DOMPurify\nimport { createJSDOM } from \"../../utils/dom\"; // Import JSDOM helper\nimport type { StoreSearchResult } from \"../../store/types\";\n\n/**\n * Props for the SearchResultItem component.\n */\ninterface SearchResultItemProps {\n result: StoreSearchResult;\n}\n\n/**\n * Renders a single search result item.\n * Converts markdown content to HTML using unified.\n * @param props - Component props including the search result data.\n */\nconst SearchResultItem = async ({ result }: SearchResultItemProps) => {\n // Use unified pipeline to convert markdown to HTML\n const processor = unified().use(remarkParse).use(remarkGfm).use(remarkHtml);\n const file = await processor.process(result.content);\n const rawHtml = String(file);\n\n // Create JSDOM instance and initialize DOMPurify\n const jsdom = createJSDOM(\"\");\n const purifier = DOMPurify(jsdom.window);\n\n // Sanitize the HTML content\n const safeHtml = purifier.sanitize(rawHtml);\n\n return (\n <div class=\"block px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-300 dark:border-gray-600 mb-2\">\n <div class=\"text-sm text-gray-600 dark:text-gray-400 mb-1\">\n <a\n href={result.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline underline-offset-4\"\n safe\n >\n {result.url}\n </a>\n </div>\n {/* Render the sanitized HTML content */}\n <div class=\"format dark:format-invert max-w-none\">{safeHtml}</div>\n </div>\n );\n};\n\nexport default SearchResultItem;\n","import type { StoreSearchResult } from \"../../store/types\";\nimport SearchResultItem from \"./SearchResultItem\"; // Adjusted import path\n\n/**\n * Props for the SearchResultList component.\n */\ninterface SearchResultListProps {\n results: StoreSearchResult[];\n}\n\n/**\n * Renders the list of search results using SearchResultItem.\n * Displays a message if no results are found.\n * @param props - Component props including the array of search results.\n */\nconst SearchResultList = ({ results }: SearchResultListProps) => {\n if (results.length === 0) {\n return (\n <p class=\"text-gray-500 dark:text-gray-400 italic\">No results found.</p>\n );\n }\n return (\n <div class=\"space-y-2\">\n {results.map((result) => (\n <SearchResultItem result={result} />\n ))}\n </div>\n );\n};\n\nexport default SearchResultList;\n","/**\n * Renders a skeleton placeholder for a search result item.\n * Used to indicate loading state while search results are being fetched.\n */\nconst SearchResultSkeletonItem = () => (\n <div class=\"block px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm mb-2 animate-pulse\">\n <div class=\"h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-3/4 mb-2\"></div>\n <div class=\"h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-full mb-2\"></div>\n <div class=\"h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-5/6\"></div>\n </div>\n);\n\nexport default SearchResultSkeletonItem;\n","import type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport type { ListLibrariesTool } from \"../../../tools/ListLibrariesTool\";\nimport { SearchTool } from \"../../../tools/SearchTool\";\nimport Layout from \"../../components/Layout\";\nimport LibraryDetailCard from \"../../components/LibraryDetailCard\";\nimport LibrarySearchCard from \"../../components/LibrarySearchCard\";\nimport SearchResultList from \"../../components/SearchResultList\";\nimport SearchResultSkeletonItem from \"../../components/SearchResultSkeletonItem\";\n\n/**\n * Registers the route for displaying library details.\n * @param server - The Fastify instance.\n * @param listLibrariesTool - The tool instance for listing libraries.\n * @param searchTool - The tool instance for searching documentation.\n */\nexport function registerLibraryDetailRoutes(\n server: FastifyInstance,\n listLibrariesTool: ListLibrariesTool,\n searchTool: SearchTool\n) {\n // Route for the library detail page\n server.get(\n \"/libraries/:libraryName\",\n async (\n request: FastifyRequest<{ Params: { libraryName: string } }>,\n reply: FastifyReply\n ) => {\n const { libraryName } = request.params;\n try {\n // Fetch all libraries and find the requested one\n const result = await listLibrariesTool.execute();\n const libraryInfo = result.libraries.find(\n (lib) => lib.name === libraryName\n );\n\n if (!libraryInfo) {\n reply.status(404).send(\"Library not found\");\n return;\n }\n\n reply.type(\"text/html; charset=utf-8\");\n // Use the Layout component\n return (\n \"<!DOCTYPE html>\" +\n (\n <Layout title={`MCP Docs - ${libraryInfo.name}`}>\n {/* Library Detail Card */}\n <LibraryDetailCard library={libraryInfo} />\n\n {/* Library Search Card */}\n <LibrarySearchCard library={libraryInfo} />\n\n {/* Search Results Container */}\n <div id=\"searchResultsContainer\">\n {/* Skeleton loader - Initially present */}\n <div class=\"search-skeleton space-y-2\">\n <SearchResultSkeletonItem />\n <SearchResultSkeletonItem />\n <SearchResultSkeletonItem />\n </div>\n {/* Search results will be loaded here via HTMX */}\n <div class=\"search-results\">\n {/* Initially empty, HTMX will swap content here */}\n </div>\n </div>\n </Layout>\n )\n );\n } catch (error) {\n server.log.error(\n error,\n `Failed to load library details for ${libraryName}`\n );\n reply.status(500).send(\"Internal Server Error\");\n }\n }\n );\n\n // API route for searching a specific library\n server.get(\n \"/web/libraries/:libraryName/search\",\n async (\n request: FastifyRequest<{\n Params: { libraryName: string };\n Querystring: { query: string; version?: string };\n }>,\n reply: FastifyReply\n ) => {\n const { libraryName } = request.params;\n const { query, version } = request.query;\n\n if (!query) {\n reply.status(400).send(\"Search query is required.\");\n return;\n }\n\n // Map \"unversioned\" string to undefined for the tool\n const versionParam = version === \"unversioned\" ? undefined : version;\n\n try {\n const searchResult = await searchTool.execute({\n library: libraryName,\n query,\n version: versionParam,\n limit: 10, // Limit search results\n });\n\n // Return only the results list or error message\n reply.type(\"text/html; charset=utf-8\");\n return <SearchResultList results={searchResult.results} />;\n } catch (error) {\n server.log.error(error, `Failed to search library ${libraryName}`);\n // Return error message on catch\n reply.type(\"text/html; charset=utf-8\");\n return (\n <p class=\"text-red-500 dark:text-red-400 italic\">\n An unexpected error occurred during the search.\n </p>\n );\n }\n }\n );\n}\n","import type { LibraryInfo } from \"../../tools/ListLibrariesTool\";\nimport VersionDetailsRow from \"./VersionDetailsRow\"; // Adjusted import path\n\n/**\n * Props for the LibraryItem component.\n */\ninterface LibraryItemProps {\n library: LibraryInfo;\n}\n\n/**\n * Renders a card for a single library, listing its versions with details.\n * Uses VersionDetailsRow to display each version.\n * @param props - Component props including the library information.\n */\nconst LibraryItem = ({ library }: LibraryItemProps) => (\n // Use Flowbite Card structure with updated padding and border, and white background\n <div class=\"block px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-300 dark:border-gray-600\">\n <h3 class=\"text-lg font-medium text-gray-900 dark:text-white mb-1\">\n <a\n href={`/libraries/${encodeURIComponent(library.name)}`}\n class=\"hover:underline\"\n >\n <span safe>{library.name}</span>\n </a>\n </h3>\n {/* Container for version rows */}\n <div class=\"mt-1\">\n {library.versions.length > 0 ? (\n library.versions.map((version) => (\n <VersionDetailsRow libraryName={library.name} version={version} />\n ))\n ) : (\n // Display message if no versions are indexed\n <p class=\"text-sm text-gray-500 dark:text-gray-400 italic\">\n No versions indexed.\n </p>\n )}\n </div>\n </div>\n);\n\nexport default LibraryItem;\n","import type { LibraryInfo } from \"../../tools/ListLibrariesTool\";\nimport LibraryItem from \"./LibraryItem\"; // Adjusted import path\n\n/**\n * Props for the LibraryList component.\n */\ninterface LibraryListProps {\n libraries: LibraryInfo[];\n}\n\n/**\n * Renders a list of LibraryItem components.\n * @param props - Component props including the array of libraries.\n */\nconst LibraryList = ({ libraries }: LibraryListProps) => {\n return (\n <>\n <div class=\"space-y-2\">\n {libraries.map((library) => (\n <LibraryItem library={library} />\n ))}\n </div>\n </>\n );\n};\n\nexport default LibraryList;\n","import type { FastifyInstance } from \"fastify\";\nimport type { ListLibrariesTool } from \"../../../tools/ListLibrariesTool\";\nimport { RemoveTool } from \"../../../tools\";\nimport LibraryList from \"../../components/LibraryList\";\n\n/**\n * Registers the API routes for library management.\n * @param server - The Fastify instance.\n * @param listLibrariesTool - The tool instance for listing libraries.\n * @param removeTool - The tool instance for removing library versions.\n */\nexport function registerLibrariesRoutes(\n server: FastifyInstance,\n listLibrariesTool: ListLibrariesTool,\n removeTool: RemoveTool // Accept RemoveTool\n) {\n server.get(\"/web/libraries\", async (_request, reply) => {\n // Add reply\n try {\n const result = await listLibrariesTool.execute();\n // Set content type to HTML for JSX rendering\n reply.type(\"text/html; charset=utf-8\");\n // Render the component directly\n return <LibraryList libraries={result.libraries} />;\n } catch (error) {\n server.log.error(error, \"Failed to list libraries\");\n reply.status(500).send(\"Internal Server Error\"); // Handle errors\n }\n });\n\n // Add DELETE route for removing versions\n server.delete<{ Params: { libraryName: string; versionParam: string } }>(\n \"/web/libraries/:libraryName/versions/:versionParam\",\n async (request, reply) => {\n const { libraryName, versionParam } = request.params;\n const version = versionParam === \"unversioned\" ? undefined : versionParam;\n try {\n await removeTool.execute({ library: libraryName, version });\n reply.status(204).send(); // No Content on success\n } catch (error: any) {\n server.log.error(\n error,\n `Failed to remove ${libraryName}@${versionParam}`\n );\n // Check for specific errors if needed, e.g., NotFoundError\n reply\n .status(500)\n .send({ message: error.message || \"Failed to remove version.\" });\n }\n }\n );\n}\n","/**\n * Web service that registers all web interface routes for human interaction.\n * Extracted from src/web/web.ts to enable modular server composition.\n */\n\nimport type { FastifyInstance } from \"fastify\";\nimport type { IPipeline } from \"../pipeline/interfaces\";\nimport type { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport { SearchTool } from \"../tools\";\nimport { CancelJobTool } from \"../tools/CancelJobTool\";\nimport { ClearCompletedJobsTool } from \"../tools/ClearCompletedJobsTool\";\nimport { ListJobsTool } from \"../tools/ListJobsTool\";\nimport { ListLibrariesTool } from \"../tools/ListLibrariesTool\";\nimport { RemoveTool } from \"../tools/RemoveTool\";\nimport { ScrapeTool } from \"../tools/ScrapeTool\";\nimport { registerIndexRoute } from \"../web/routes/index\";\nimport { registerCancelJobRoute } from \"../web/routes/jobs/cancel\";\nimport { registerClearCompletedJobsRoute } from \"../web/routes/jobs/clear-completed\";\nimport { registerJobListRoutes } from \"../web/routes/jobs/list\";\nimport { registerNewJobRoutes } from \"../web/routes/jobs/new\";\nimport { registerLibraryDetailRoutes } from \"../web/routes/libraries/detail\";\nimport { registerLibrariesRoutes } from \"../web/routes/libraries/list\";\n\n/**\n * Register web interface routes on a Fastify server instance.\n * This includes all human-facing UI routes.\n * Note: Static file serving and form body parsing are handled by AppServer.\n */\nexport async function registerWebService(\n server: FastifyInstance,\n docService: DocumentManagementService,\n pipeline: IPipeline,\n): Promise<void> {\n // Instantiate tools for web routes\n const listLibrariesTool = new ListLibrariesTool(docService);\n const listJobsTool = new ListJobsTool(pipeline);\n const scrapeTool = new ScrapeTool(pipeline);\n const removeTool = new RemoveTool(docService, pipeline);\n const searchTool = new SearchTool(docService);\n const cancelJobTool = new CancelJobTool(pipeline);\n const clearCompletedJobsTool = new ClearCompletedJobsTool(pipeline);\n\n // Register all web routes\n registerIndexRoute(server);\n registerLibrariesRoutes(server, listLibrariesTool, removeTool);\n registerLibraryDetailRoutes(server, listLibrariesTool, searchTool);\n registerJobListRoutes(server, listJobsTool);\n registerNewJobRoutes(server, scrapeTool);\n registerCancelJobRoute(server, cancelJobTool);\n registerClearCompletedJobsRoute(server, clearCompletedJobsTool);\n}\n","/**\n * Worker service that enables the embedded pipeline worker functionality.\n * This service starts the pipeline and configures it for background job processing.\n */\n\nimport type { IPipeline } from \"../pipeline/interfaces\";\nimport { logger } from \"../utils/logger\";\n\n/**\n * Register worker service to enable embedded pipeline processing.\n * This starts the pipeline and configures callbacks for job processing.\n */\nexport async function registerWorkerService(pipeline: IPipeline): Promise<void> {\n // Configure progress callbacks for logging\n pipeline.setCallbacks({\n onJobProgress: async (job, progress) => {\n logger.debug(\n `📊 Job ${job.id} progress: ${progress.pagesScraped}/${progress.totalPages} pages`,\n );\n },\n onJobStatusChange: async (job) => {\n logger.debug(`🔄 Job ${job.id} status changed to: ${job.status}`);\n },\n onJobError: async (job, error, document) => {\n logger.warn(\n `⚠️ Job ${job.id} error ${document ? `on document ${document.metadata.url}` : \"\"}: ${error.message}`,\n );\n },\n });\n\n // Start the pipeline for job processing\n await pipeline.start();\n logger.debug(\"Worker service started\");\n}\n\n/**\n * Stop the worker service and cleanup resources.\n */\nexport async function stopWorkerService(pipeline: IPipeline): Promise<void> {\n await pipeline.stop();\n logger.debug(\"Worker service stopped\");\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nlet projectRoot: string | null = null;\n\n/**\n * Finds the project root directory by searching upwards from the current file\n * for a directory containing 'package.json'. Caches the result.\n *\n * @returns {string} The absolute path to the project root.\n * @throws {Error} If package.json cannot be found.\n */\nexport function getProjectRoot(): string {\n // Return cached result if available\n if (projectRoot) {\n return projectRoot;\n }\n\n // Start from the directory of the current module\n const currentFilePath = fileURLToPath(import.meta.url);\n let currentDir = path.dirname(currentFilePath);\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const packageJsonPath = path.join(currentDir, \"package.json\");\n if (fs.existsSync(packageJsonPath)) {\n projectRoot = currentDir; // Cache the result\n return projectRoot;\n }\n\n const parentDir = path.dirname(currentDir);\n // Check if we have reached the filesystem root\n if (parentDir === currentDir) {\n throw new Error(\"Could not find project root containing package.json.\");\n }\n currentDir = parentDir;\n }\n}\n","/**\n * Central application server that can be configured to run different combinations of services.\n * This replaces the separate server implementations with a single, modular approach.\n */\n\nimport path from \"node:path\";\nimport formBody from \"@fastify/formbody\";\nimport fastifyStatic from \"@fastify/static\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport Fastify, { type FastifyInstance } from \"fastify\";\nimport type { IPipeline } from \"../pipeline/interfaces\";\nimport { cleanupMcpService, registerMcpService } from \"../services/mcpService\";\nimport { registerPipelineApiService } from \"../services/pipelineApiService\";\nimport { registerWebService } from \"../services/webService\";\nimport { registerWorkerService, stopWorkerService } from \"../services/workerService\";\nimport type { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport { logger } from \"../utils/logger\";\nimport { getProjectRoot } from \"../utils/paths\";\nimport type { AppServerConfig } from \"./AppServerConfig\";\n\n/**\n * Central application server that provides modular service composition.\n */\nexport class AppServer {\n private server: FastifyInstance;\n private mcpServer: McpServer | null = null;\n private config: AppServerConfig;\n\n constructor(\n private docService: DocumentManagementService,\n private pipeline: IPipeline,\n config: AppServerConfig,\n ) {\n this.config = config;\n this.server = Fastify({\n logger: false, // Use our own logger\n });\n }\n\n /**\n * Validate the server configuration for invalid service combinations.\n */\n private validateConfig(): void {\n // Web interface needs either worker or external worker URL\n if (this.config.enableWebInterface) {\n if (!this.config.enableWorker && !this.config.externalWorkerUrl) {\n throw new Error(\n \"Web interface requires either embedded worker (enableWorker: true) or external worker (externalWorkerUrl)\",\n );\n }\n }\n\n // MCP server needs pipeline access (worker or external)\n if (this.config.enableMcpServer) {\n if (!this.config.enableWorker && !this.config.externalWorkerUrl) {\n throw new Error(\n \"MCP server requires either embedded worker (enableWorker: true) or external worker (externalWorkerUrl)\",\n );\n }\n }\n\n // Pipeline API should be enabled if we have a worker\n if (this.config.enableWorker && !this.config.enablePipelineApi) {\n logger.warn(\n \"Warning: Worker is enabled but Pipeline API is disabled. Consider enabling Pipeline API for better observability.\",\n );\n }\n }\n\n /**\n * Start the application server with the configured services.\n */\n async start(): Promise<FastifyInstance> {\n this.validateConfig();\n await this.setupServer();\n\n try {\n const address = await this.server.listen({\n port: this.config.port,\n host: \"0.0.0.0\",\n });\n\n this.logStartupInfo(address);\n return this.server;\n } catch (error) {\n logger.error(`❌ Failed to start AppServer: ${error}`);\n await this.server.close();\n throw error;\n }\n }\n\n /**\n * Stop the application server and cleanup all services.\n */\n async stop(): Promise<void> {\n try {\n // Stop worker service if enabled\n if (this.config.enableWorker) {\n await stopWorkerService(this.pipeline);\n }\n\n // Cleanup MCP service if enabled\n if (this.mcpServer) {\n await cleanupMcpService(this.mcpServer);\n }\n\n // Close Fastify server\n await this.server.close();\n logger.info(\"🛑 AppServer stopped\");\n } catch (error) {\n logger.error(`❌ Failed to stop AppServer gracefully: ${error}`);\n throw error;\n }\n }\n\n /**\n * Setup the server with plugins and conditionally enabled services.\n */\n private async setupServer(): Promise<void> {\n // Register core Fastify plugins\n await this.server.register(formBody);\n\n // Conditionally enable services based on configuration\n if (this.config.enableWebInterface) {\n await this.enableWebInterface();\n }\n\n if (this.config.enableMcpServer) {\n await this.enableMcpServer();\n }\n\n if (this.config.enablePipelineApi) {\n await this.enablePipelineApi();\n }\n\n if (this.config.enableWorker) {\n await this.enableWorker();\n }\n\n // Setup static file serving as fallback (must be last)\n if (this.config.enableWebInterface) {\n await this.setupStaticFiles();\n }\n }\n\n /**\n * Enable web interface service.\n */\n private async enableWebInterface(): Promise<void> {\n await registerWebService(this.server, this.docService, this.pipeline);\n logger.debug(\"Web interface service enabled\");\n }\n\n /**\n * Enable MCP server service.\n */\n private async enableMcpServer(): Promise<void> {\n this.mcpServer = await registerMcpService(\n this.server,\n this.docService,\n this.pipeline,\n );\n logger.debug(\"MCP server service enabled\");\n }\n\n /**\n * Enable Pipeline API service.\n */\n private async enablePipelineApi(): Promise<void> {\n await registerPipelineApiService(this.server, this.pipeline);\n logger.debug(\"Pipeline API service enabled\");\n }\n\n /**\n * Enable worker service.\n */\n private async enableWorker(): Promise<void> {\n await registerWorkerService(this.pipeline);\n logger.debug(\"Worker service enabled\");\n }\n\n /**\n * Setup static file serving with root prefix as fallback.\n */\n private async setupStaticFiles(): Promise<void> {\n await this.server.register(fastifyStatic, {\n root: path.join(getProjectRoot(), \"public\"),\n prefix: \"/\",\n index: false,\n });\n }\n\n /**\n * Log startup information showing which services are enabled.\n */\n private logStartupInfo(address: string): void {\n logger.info(`🚀 AppServer available at ${address}`);\n\n const enabledServices: string[] = [];\n\n if (this.config.enableWebInterface) {\n enabledServices.push(`Web interface: ${address}`);\n }\n\n if (this.config.enableMcpServer) {\n enabledServices.push(`MCP endpoint: ${address}/mcp, ${address}/sse`);\n }\n\n if (this.config.enablePipelineApi) {\n enabledServices.push(`Pipeline API: ${address}/api`);\n }\n\n if (this.config.enableWorker) {\n enabledServices.push(\"Embedded worker: enabled\");\n } else if (this.config.externalWorkerUrl) {\n enabledServices.push(`External worker: ${this.config.externalWorkerUrl}`);\n }\n\n for (const service of enabledServices) {\n logger.info(` • ${service}`);\n }\n }\n}\n","/**\n * App module exports for the central application server architecture.\n */\n\n/**\n * Application server module exports.\n * Provides AppServer and configuration types, plus convenience functions for CLI integration.\n */\n\nexport { AppServer } from \"./AppServer\";\nexport type { AppServerConfig } from \"./AppServerConfig\";\n\nimport type { IPipeline } from \"../pipeline/interfaces\";\nimport type { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport { AppServer } from \"./AppServer\";\nimport type { AppServerConfig } from \"./AppServerConfig\";\n\n/**\n * Start an AppServer with the given configuration.\n * Convenience function for CLI integration.\n */\nexport async function startAppServer(\n docService: DocumentManagementService,\n pipeline: IPipeline,\n config: AppServerConfig,\n): Promise<AppServer> {\n const appServer = new AppServer(docService, pipeline, config);\n await appServer.start();\n return appServer;\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { LogLevel, logger, setLogLevel } from \"../utils/logger\";\nimport { createMcpServerInstance } from \"./mcpServer\";\nimport type { McpServerTools } from \"./tools\";\n\n/**\n * Starts the MCP server using the Stdio transport.\n * @param tools The shared tool instances.\n * @returns The created McpServer instance.\n */\nexport async function startStdioServer(tools: McpServerTools): Promise<McpServer> {\n setLogLevel(LogLevel.ERROR);\n\n // Create a server instance using the factory and shared tools\n const server = createMcpServerInstance(tools);\n\n // Start server with Stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n logger.info(\"🤖 MCP server listening on stdio\");\n\n // Return the server instance\n return server;\n}\n","/**\n * HTTP client implementation of the Pipeline interface.\n * Delegates all pipeline operations to an external worker via HTTP API.\n */\n\nimport type { ScraperOptions } from \"../scraper/types\";\nimport { logger } from \"../utils/logger\";\nimport type { IPipeline } from \"./interfaces\";\nimport type { PipelineJob, PipelineJobStatus, PipelineManagerCallbacks } from \"./types\";\n\n/**\n * Deserializes a job object from JSON, converting date strings back to Date objects.\n * Only includes public fields - no internal job management fields.\n */\nfunction deserializeJob(serializedJob: Record<string, unknown>): PipelineJob {\n return {\n ...serializedJob,\n createdAt: new Date(serializedJob.createdAt as string),\n startedAt: serializedJob.startedAt\n ? new Date(serializedJob.startedAt as string)\n : null,\n finishedAt: serializedJob.finishedAt\n ? new Date(serializedJob.finishedAt as string)\n : null,\n updatedAt: serializedJob.updatedAt\n ? new Date(serializedJob.updatedAt as string)\n : undefined,\n } as PipelineJob;\n}\n\n/**\n * HTTP client that implements the IPipeline interface by delegating to external worker.\n */\nexport class PipelineClient implements IPipeline {\n private readonly baseUrl: string;\n private pollingInterval: number = 1000; // 1 second\n private activePolling = new Set<string>(); // Track jobs being polled for completion\n\n constructor(serverUrl: string) {\n // Use the provided URL as-is, just remove trailing slash for consistency\n this.baseUrl = serverUrl.replace(/\\/$/, \"\");\n logger.debug(`PipelineClient created for: ${this.baseUrl}`);\n }\n\n async start(): Promise<void> {\n // Check if external worker is available\n try {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`External worker health check failed: ${response.status}`);\n }\n logger.debug(\"PipelineClient connected to external worker\");\n } catch (error) {\n throw new Error(\n `Failed to connect to external worker at ${this.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async stop(): Promise<void> {\n // Clear any active polling\n this.activePolling.clear();\n logger.debug(\"PipelineClient stopped\");\n }\n\n async enqueueJob(\n library: string,\n version: string | undefined | null,\n options: ScraperOptions,\n ): Promise<string> {\n try {\n const response = await fetch(`${this.baseUrl}/jobs`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n library,\n version,\n options,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to enqueue job: ${response.status} ${errorText}`);\n }\n\n const result = await response.json();\n const jobId = result.jobId;\n\n logger.debug(`Job ${jobId} enqueued successfully`);\n return jobId;\n } catch (error) {\n throw new Error(\n `Failed to enqueue job: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async getJob(jobId: string): Promise<PipelineJob | undefined> {\n try {\n const response = await fetch(`${this.baseUrl}/jobs/${jobId}`);\n\n if (response.status === 404) {\n return undefined;\n }\n\n if (!response.ok) {\n throw new Error(`Failed to get job: ${response.status} ${response.statusText}`);\n }\n\n const serializedJob = await response.json();\n return deserializeJob(serializedJob);\n } catch (error) {\n throw new Error(\n `Failed to get job ${jobId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async getJobs(status?: PipelineJobStatus): Promise<PipelineJob[]> {\n try {\n const url = new URL(`${this.baseUrl}/jobs`);\n if (status) {\n url.searchParams.set(\"status\", status);\n }\n\n const response = await fetch(url.toString());\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to get jobs: ${response.status} ${errorText}`);\n }\n\n const result = await response.json();\n const serializedJobs = result.jobs || [];\n return serializedJobs.map(deserializeJob);\n } catch (error) {\n logger.error(`Failed to get jobs from external worker: ${error}`);\n throw error;\n }\n }\n\n async cancelJob(jobId: string): Promise<void> {\n try {\n const response = await fetch(`${this.baseUrl}/jobs/${jobId}`, {\n method: \"DELETE\",\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to cancel job: ${response.status} ${errorText}`);\n }\n\n logger.debug(`Job cancelled via external worker: ${jobId}`);\n } catch (error) {\n logger.error(`Failed to cancel job ${jobId} via external worker: ${error}`);\n throw error;\n }\n }\n\n async clearCompletedJobs(): Promise<number> {\n try {\n const response = await fetch(`${this.baseUrl}/jobs`, {\n method: \"DELETE\",\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Failed to clear completed jobs: ${response.status} ${errorText}`,\n );\n }\n\n const result = await response.json();\n logger.debug(`Cleared ${result.count} completed jobs via external worker`);\n return result.count || 0;\n } catch (error) {\n logger.error(`Failed to clear completed jobs via external worker: ${error}`);\n throw error;\n }\n }\n\n async waitForJobCompletion(jobId: string): Promise<void> {\n if (this.activePolling.has(jobId)) {\n throw new Error(`Already waiting for completion of job ${jobId}`);\n }\n\n this.activePolling.add(jobId);\n\n try {\n while (this.activePolling.has(jobId)) {\n const job = await this.getJob(jobId);\n if (!job) {\n throw new Error(`Job ${jobId} not found`);\n }\n\n // Check if job is in final state\n if (\n job.status === \"completed\" ||\n job.status === \"failed\" ||\n job.status === \"cancelled\"\n ) {\n if (job.status === \"failed\" && job.error) {\n // Normalize to real Error instance\n throw new Error(job.error.message);\n }\n return;\n }\n\n // Poll every second\n await new Promise((resolve) => setTimeout(resolve, this.pollingInterval));\n }\n } finally {\n this.activePolling.delete(jobId);\n }\n }\n\n setCallbacks(_callbacks: PipelineManagerCallbacks): void {\n // For external pipeline, callbacks are not used since all updates come via polling\n logger.debug(\"PipelineClient.setCallbacks called - no-op for external worker\");\n }\n}\n","import psl from \"psl\";\nimport { InvalidUrlError } from \"./errors\";\n\ninterface UrlNormalizerOptions {\n ignoreCase?: boolean;\n removeHash?: boolean;\n removeTrailingSlash?: boolean;\n removeQuery?: boolean;\n removeIndex?: boolean;\n}\n\nconst defaultNormalizerOptions: UrlNormalizerOptions = {\n ignoreCase: true,\n removeHash: true,\n removeTrailingSlash: true,\n removeQuery: false,\n removeIndex: true,\n};\n\nexport function normalizeUrl(\n url: string,\n options: UrlNormalizerOptions = defaultNormalizerOptions,\n): string {\n try {\n const parsedUrl = new URL(url);\n const finalOptions = { ...defaultNormalizerOptions, ...options };\n\n // Create a new URL to ensure proper structure\n const normalized = new URL(parsedUrl.origin + parsedUrl.pathname);\n\n // Remove index files first, before handling trailing slashes\n if (finalOptions.removeIndex) {\n normalized.pathname = normalized.pathname.replace(\n /\\/index\\.(html|htm|asp|php|jsp)$/i,\n \"/\",\n );\n }\n\n // Handle trailing slash\n if (finalOptions.removeTrailingSlash && normalized.pathname.length > 1) {\n normalized.pathname = normalized.pathname.replace(/\\/+$/, \"\");\n }\n\n // Keep original parts we want to preserve\n const preservedHash = !finalOptions.removeHash ? parsedUrl.hash : \"\";\n const preservedSearch = !finalOptions.removeQuery ? parsedUrl.search : \"\";\n\n // Construct final URL string in correct order (query before hash)\n let result = normalized.origin + normalized.pathname;\n if (preservedSearch) {\n result += preservedSearch;\n }\n if (preservedHash) {\n result += preservedHash;\n }\n\n // Apply case normalization if configured\n if (finalOptions.ignoreCase) {\n result = result.toLowerCase();\n }\n\n return result;\n } catch {\n return url; // Return original URL if parsing fails\n }\n}\n\n/**\n * Validates if a string is a valid URL\n * @throws {InvalidUrlError} If the URL is invalid\n */\nexport function validateUrl(url: string): void {\n try {\n new URL(url);\n } catch (error) {\n throw new InvalidUrlError(url, error instanceof Error ? error : undefined);\n }\n}\n\n/**\n * Checks if two URLs have the exact same hostname\n */\nexport function hasSameHostname(urlA: URL, urlB: URL): boolean {\n return urlA.hostname.toLowerCase() === urlB.hostname.toLowerCase();\n}\n\n/**\n * Checks if two URLs are on the same domain (including subdomains)\n * Using the public suffix list to properly handle domains like .co.uk\n */\nexport function hasSameDomain(urlA: URL, urlB: URL): boolean {\n const domainA = psl.get(urlA.hostname.toLowerCase());\n const domainB = psl.get(urlB.hostname.toLowerCase());\n return domainA !== null && domainA === domainB;\n}\n\n/**\n * Checks if a target URL is under the same path as the base URL\n * Example: base = https://example.com/docs/\n * target = https://example.com/docs/getting-started\n * result = true\n */\nexport function isSubpath(baseUrl: URL, targetUrl: URL): boolean {\n // Normalize paths to ensure consistent comparison\n const basePath = baseUrl.pathname.endsWith(\"/\")\n ? baseUrl.pathname\n : `${baseUrl.pathname}/`;\n\n return targetUrl.pathname.startsWith(basePath);\n}\n\nexport type { UrlNormalizerOptions };\n","import { minimatch } from \"minimatch\";\n\n/**\n * Utility functions for pattern matching (glob and regex) for URL filtering.\n * Supports auto-detection and conversion of glob patterns to RegExp.\n *\n * Patterns starting and ending with '/' are treated as regex, otherwise as glob (minimatch syntax).\n * Glob wildcards supported: '*' (any chars except '/'), '**' (any chars, including '/').\n *\n * @module patternMatcher\n */\n\n/**\n * Detects if a pattern is a regex (starts and ends with '/')\n */\nexport function isRegexPattern(pattern: string): boolean {\n return pattern.length > 2 && pattern.startsWith(\"/\") && pattern.endsWith(\"/\");\n}\n\n/**\n * Converts a pattern string to a RegExp instance (auto-detects glob/regex).\n * For globs, uses minimatch's internal conversion.\n */\nexport function patternToRegExp(pattern: string): RegExp {\n if (isRegexPattern(pattern)) {\n return new RegExp(pattern.slice(1, -1));\n }\n // For globs, minimatch.makeRe returns a RegExp\n const re = minimatch.makeRe(pattern, { dot: true });\n if (!re) throw new Error(`Invalid glob pattern: ${pattern}`);\n return re;\n}\n\n/**\n * Checks if a given path matches any pattern in the list.\n * For globs, uses minimatch. For regex, uses RegExp.\n */\nexport function matchesAnyPattern(path: string, patterns?: string[]): boolean {\n if (!patterns || patterns.length === 0) return false;\n // Always match from a leading slash for path-based globs\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return patterns.some((pattern) => {\n if (isRegexPattern(pattern)) {\n return patternToRegExp(pattern).test(normalizedPath);\n }\n // minimatch expects no leading slash for relative globs, but we keep it for consistency\n // so we strip the leading slash for minimatch\n return minimatch(normalizedPath.replace(/^\\//, \"\"), pattern, { dot: true });\n });\n}\n\n/**\n * Extracts the path and query from a URL string (no domain).\n */\nexport function extractPathAndQuery(url: string): string {\n try {\n const u = new URL(url);\n return u.pathname + (u.search || \"\");\n } catch {\n return url; // fallback: return as-is\n }\n}\n\n/**\n * Determines if a URL should be included based on include/exclude patterns.\n * Exclude patterns take precedence. If no include patterns, all are included by default.\n */\nexport function shouldIncludeUrl(\n url: string,\n includePatterns?: string[],\n excludePatterns?: string[],\n): boolean {\n // Always match from a leading slash for path-based globs\n const path = extractPathAndQuery(url);\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n // For file:// URLs, also match against the basename (strip leading slash from pattern for basename matching)\n let basename: string | undefined;\n if (url.startsWith(\"file://\")) {\n try {\n const u = new URL(url);\n basename = u.pathname ? u.pathname.split(\"/\").pop() : undefined;\n } catch {}\n }\n // Helper to strip leading slash from patterns for basename matching\n const stripSlash = (patterns?: string[]) =>\n patterns?.map((p) => (p.startsWith(\"/\") ? p.slice(1) : p));\n // Exclude patterns take precedence\n if (\n matchesAnyPattern(normalizedPath, excludePatterns) ||\n (basename && matchesAnyPattern(basename, stripSlash(excludePatterns)))\n )\n return false;\n if (!includePatterns || includePatterns.length === 0) return true;\n return (\n matchesAnyPattern(normalizedPath, includePatterns) ||\n (basename ? matchesAnyPattern(basename, stripSlash(includePatterns)) : false)\n );\n}\n","// Utility for scope filtering, extracted from WebScraperStrategy\nimport type { URL } from \"node:url\";\n\n/**\n * Returns true if the targetUrl is in scope of the baseUrl for the given scope.\n * - \"subpages\": same hostname, and target path starts with the parent directory of the base path\n * - \"hostname\": same hostname\n * - \"domain\": same top-level domain (e.g. example.com)\n */\nexport function isInScope(\n baseUrl: URL,\n targetUrl: URL,\n scope: \"subpages\" | \"hostname\" | \"domain\",\n): boolean {\n if (baseUrl.protocol !== targetUrl.protocol) return false;\n switch (scope) {\n case \"subpages\": {\n if (baseUrl.hostname !== targetUrl.hostname) return false;\n // Use the parent directory of the base path\n const baseDir = baseUrl.pathname.endsWith(\"/\")\n ? baseUrl.pathname\n : baseUrl.pathname.replace(/\\/[^/]*$/, \"/\");\n return targetUrl.pathname.startsWith(baseDir);\n }\n case \"hostname\":\n return baseUrl.hostname === targetUrl.hostname;\n case \"domain\": {\n // Compare the last two segments of the hostname (e.g. example.com)\n const getDomain = (host: string) => host.split(\".\").slice(-2).join(\".\");\n return getDomain(baseUrl.hostname) === getDomain(targetUrl.hostname);\n }\n default:\n return false;\n }\n}\n","import { URL } from \"node:url\";\nimport { CancellationError } from \"../../pipeline/errors\";\nimport type { Document, ProgressCallback } from \"../../types\";\nimport { DEFAULT_MAX_PAGES } from \"../../utils/config\";\nimport { logger } from \"../../utils/logger\";\nimport { normalizeUrl, type UrlNormalizerOptions } from \"../../utils/url\";\nimport type { ScraperOptions, ScraperProgress, ScraperStrategy } from \"../types\";\nimport { shouldIncludeUrl } from \"../utils/patternMatcher\";\nimport { isInScope } from \"../utils/scope\";\n\n// Define defaults for optional options\nconst DEFAULT_MAX_DEPTH = 3;\nconst DEFAULT_CONCURRENCY = 3;\n\nexport type QueueItem = {\n url: string;\n depth: number;\n};\n\nexport interface BaseScraperStrategyOptions {\n urlNormalizerOptions?: UrlNormalizerOptions;\n}\n\nexport abstract class BaseScraperStrategy implements ScraperStrategy {\n protected visited = new Set<string>();\n protected pageCount = 0;\n protected totalDiscovered = 0; // Track total URLs discovered (unlimited)\n protected effectiveTotal = 0; // Track effective total (limited by maxPages)\n\n abstract canHandle(url: string): boolean;\n\n protected options: BaseScraperStrategyOptions;\n\n constructor(options: BaseScraperStrategyOptions = {}) {\n this.options = options;\n }\n\n /**\n * Determines if a URL should be processed based on scope and include/exclude patterns in ScraperOptions.\n * Scope is checked first, then patterns.\n */\n protected shouldProcessUrl(url: string, options: ScraperOptions): boolean {\n if (options.scope) {\n try {\n const base = new URL(options.url);\n const target = new URL(url);\n if (!isInScope(base, target, options.scope)) return false;\n } catch {\n return false;\n }\n }\n return shouldIncludeUrl(url, options.includePatterns, options.excludePatterns);\n }\n\n /**\n * Process a single item from the queue.\n *\n * @returns A list of URLs to add to the queue\n */\n protected abstract processItem(\n item: QueueItem,\n options: ScraperOptions,\n progressCallback?: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal, // Add signal\n ): Promise<{\n document?: Document;\n links?: string[];\n }>;\n\n // Removed getProcessor method as processing is now handled by strategies using middleware pipelines\n\n protected async processBatch(\n batch: QueueItem[],\n baseUrl: URL,\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal, // Add signal\n ): Promise<QueueItem[]> {\n const maxPages = options.maxPages ?? DEFAULT_MAX_PAGES;\n const results = await Promise.all(\n batch.map(async (item) => {\n // Check signal before processing each item in the batch\n if (signal?.aborted) {\n throw new CancellationError(\"Scraping cancelled during batch processing\");\n }\n // Resolve default for maxDepth check\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n if (item.depth > maxDepth) {\n return [];\n }\n\n try {\n // Pass signal to processItem\n const result = await this.processItem(item, options, undefined, signal);\n\n if (result.document) {\n this.pageCount++;\n // maxDepth already resolved above\n logger.info(\n `🌐 Scraping page ${this.pageCount}/${this.effectiveTotal} (depth ${item.depth}/${maxDepth}): ${item.url}`,\n );\n await progressCallback({\n pagesScraped: this.pageCount,\n totalPages: this.effectiveTotal,\n totalDiscovered: this.totalDiscovered,\n currentUrl: item.url,\n depth: item.depth,\n maxDepth: maxDepth,\n document: result.document,\n });\n }\n\n const nextItems = result.links || [];\n return nextItems\n .map((value) => {\n try {\n const targetUrl = new URL(value, baseUrl);\n // Filter using shouldProcessUrl\n if (!this.shouldProcessUrl(targetUrl.href, options)) {\n return null;\n }\n return {\n url: targetUrl.href,\n depth: item.depth + 1,\n } satisfies QueueItem;\n } catch (_error) {\n // Invalid URL or path\n logger.warn(`❌ Invalid URL: ${value}`);\n }\n return null;\n })\n .filter((item) => item !== null);\n } catch (error) {\n if (options.ignoreErrors) {\n logger.error(`❌ Failed to process ${item.url}: ${error}`);\n return [];\n }\n throw error;\n }\n }),\n );\n\n // After all concurrent processing is done, deduplicate the results\n const allLinks = results.flat();\n const uniqueLinks: QueueItem[] = [];\n\n // Now perform deduplication once, after all parallel processing is complete\n for (const item of allLinks) {\n const normalizedUrl = normalizeUrl(item.url, this.options.urlNormalizerOptions);\n if (!this.visited.has(normalizedUrl)) {\n this.visited.add(normalizedUrl);\n uniqueLinks.push(item);\n\n // Always increment the unlimited counter\n this.totalDiscovered++;\n\n // Only increment effective total if we haven't exceeded maxPages\n if (this.effectiveTotal < maxPages) {\n this.effectiveTotal++;\n }\n }\n }\n\n return uniqueLinks;\n }\n\n async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal, // Add signal\n ): Promise<void> {\n this.visited.clear();\n this.pageCount = 0;\n this.totalDiscovered = 1; // Start with the initial URL (unlimited counter)\n this.effectiveTotal = 1; // Start with the initial URL (limited counter)\n\n const baseUrl = new URL(options.url);\n const queue = [{ url: options.url, depth: 0 } satisfies QueueItem];\n\n // Track values we've seen (either queued or visited)\n this.visited.add(normalizeUrl(options.url, this.options.urlNormalizerOptions));\n\n // Resolve optional values to defaults using temporary variables\n const maxPages = options.maxPages ?? DEFAULT_MAX_PAGES;\n const maxConcurrency = options.maxConcurrency ?? DEFAULT_CONCURRENCY;\n\n while (queue.length > 0 && this.pageCount < maxPages) {\n // Use variable\n // Check for cancellation at the start of each loop iteration\n if (signal?.aborted) {\n logger.debug(\"Scraping cancelled by signal.\");\n throw new CancellationError(\"Scraping cancelled by signal\");\n }\n\n const remainingPages = maxPages - this.pageCount; // Use variable\n if (remainingPages <= 0) {\n break;\n }\n\n const batchSize = Math.min(\n maxConcurrency, // Use variable\n remainingPages,\n queue.length,\n );\n\n const batch = queue.splice(0, batchSize);\n // Pass signal to processBatch\n const newUrls = await this.processBatch(\n batch,\n baseUrl,\n options,\n progressCallback,\n signal,\n );\n\n queue.push(...newUrls);\n }\n }\n}\n","import type { Document, ProgressCallback } from \"../../types\";\nimport { logger } from \"../../utils/logger\";\nimport type { UrlNormalizerOptions } from \"../../utils/url\";\nimport { hasSameDomain, hasSameHostname, isSubpath } from \"../../utils/url\";\nimport { HttpFetcher } from \"../fetcher\";\nimport type { RawContent } from \"../fetcher/types\";\nimport { HtmlPipeline } from \"../pipelines/HtmlPipeline\";\nimport { MarkdownPipeline } from \"../pipelines/MarkdownPipeline\";\nimport type { ContentPipeline, ProcessedContent } from \"../pipelines/types\";\nimport type { ScraperOptions, ScraperProgress } from \"../types\";\nimport { BaseScraperStrategy, type QueueItem } from \"./BaseScraperStrategy\";\n\nexport interface WebScraperStrategyOptions {\n urlNormalizerOptions?: UrlNormalizerOptions;\n shouldFollowLink?: (baseUrl: URL, targetUrl: URL) => boolean;\n}\n\nexport class WebScraperStrategy extends BaseScraperStrategy {\n private readonly httpFetcher = new HttpFetcher();\n private readonly shouldFollowLinkFn?: (baseUrl: URL, targetUrl: URL) => boolean;\n private readonly htmlPipeline: HtmlPipeline;\n private readonly markdownPipeline: MarkdownPipeline;\n private readonly pipelines: ContentPipeline[];\n\n constructor(options: WebScraperStrategyOptions = {}) {\n super({ urlNormalizerOptions: options.urlNormalizerOptions });\n this.shouldFollowLinkFn = options.shouldFollowLink;\n this.htmlPipeline = new HtmlPipeline();\n this.markdownPipeline = new MarkdownPipeline();\n this.pipelines = [this.htmlPipeline, this.markdownPipeline];\n }\n\n canHandle(url: string): boolean {\n try {\n const parsedUrl = new URL(url);\n return parsedUrl.protocol === \"http:\" || parsedUrl.protocol === \"https:\";\n } catch {\n return false;\n }\n }\n\n /**\n * Determines if a target URL should be followed based on the scope setting.\n */\n private isInScope(\n baseUrl: URL,\n targetUrl: URL,\n scope: \"subpages\" | \"hostname\" | \"domain\",\n ): boolean {\n try {\n // First check if the URLs are on the same domain or hostname\n if (scope === \"domain\") {\n return hasSameDomain(baseUrl, targetUrl);\n }\n if (scope === \"hostname\") {\n return hasSameHostname(baseUrl, targetUrl);\n }\n // 'subpages' (default)\n return hasSameHostname(baseUrl, targetUrl) && isSubpath(baseUrl, targetUrl);\n } catch {\n return false;\n }\n }\n\n /**\n * Processes a single queue item by fetching its content and processing it through pipelines.\n * @param item - The queue item to process.\n * @param options - Scraper options including headers for HTTP requests.\n * @param _progressCallback - Optional progress callback (not used here).\n * @param signal - Optional abort signal for request cancellation.\n * @returns An object containing the processed document and extracted links.\n */\n protected override async processItem(\n item: QueueItem,\n options: ScraperOptions,\n _progressCallback?: ProgressCallback<ScraperProgress>, // Base class passes it, but not used here\n signal?: AbortSignal, // Add signal\n ): Promise<{ document?: Document; links?: string[] }> {\n const { url } = item;\n\n try {\n // Define fetch options, passing signal, followRedirects, and headers\n const fetchOptions = {\n signal,\n followRedirects: options.followRedirects,\n headers: options.headers, // Forward custom headers\n };\n\n // Pass options to fetcher\n const rawContent: RawContent = await this.httpFetcher.fetch(url, fetchOptions);\n\n // --- Start Pipeline Processing ---\n let processed: ProcessedContent | undefined;\n for (const pipeline of this.pipelines) {\n if (pipeline.canProcess(rawContent)) {\n processed = await pipeline.process(rawContent, options, this.httpFetcher);\n break;\n }\n }\n\n if (!processed) {\n logger.warn(\n `⚠️ Unsupported content type \"${rawContent.mimeType}\" for URL ${url}. Skipping processing.`,\n );\n return { document: undefined, links: [] };\n }\n\n // Log errors from pipeline\n for (const err of processed.errors) {\n logger.warn(`⚠️ Processing error for ${url}: ${err.message}`);\n }\n\n // Check if content processing resulted in usable content\n if (!processed.textContent || !processed.textContent.trim()) {\n logger.warn(\n `⚠️ No processable content found for ${url} after pipeline execution.`,\n );\n return { document: undefined, links: processed.links };\n }\n\n // Filter extracted links based on scope and custom filter\n const baseUrl = new URL(options.url);\n const filteredLinks = processed.links.filter((link) => {\n try {\n const targetUrl = new URL(link);\n const scope = options.scope || \"subpages\";\n return (\n this.isInScope(baseUrl, targetUrl, scope) &&\n (!this.shouldFollowLinkFn || this.shouldFollowLinkFn(baseUrl, targetUrl))\n );\n } catch {\n return false;\n }\n });\n\n return {\n document: {\n content: processed.textContent,\n metadata: {\n url,\n title:\n typeof processed.metadata.title === \"string\"\n ? processed.metadata.title\n : \"Untitled\",\n library: options.library,\n version: options.version,\n ...processed.metadata,\n },\n } satisfies Document,\n links: filteredLinks,\n };\n } catch (error) {\n // Log fetch errors or pipeline execution errors (if run throws)\n logger.error(`❌ Failed processing page ${url}: ${error}`);\n throw error;\n }\n }\n\n /**\n * Overrides the base scrape method to ensure the Playwright browser is closed\n * after the scraping process completes or errors out.\n */\n override async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal,\n ): Promise<void> {\n try {\n // Call the base class scrape method\n await super.scrape(options, progressCallback, signal);\n } finally {\n // Ensure the browser instance is closed\n await this.htmlPipeline.close();\n await this.markdownPipeline.close();\n }\n }\n}\n","import type { ProgressCallback } from \"../../types\";\nimport type { ScraperOptions, ScraperProgress, ScraperStrategy } from \"../types\";\nimport { WebScraperStrategy } from \"./WebScraperStrategy\";\n\nexport class GitHubScraperStrategy implements ScraperStrategy {\n private defaultStrategy: WebScraperStrategy;\n\n canHandle(url: string): boolean {\n const { hostname } = new URL(url);\n return [\"github.com\", \"www.github.com\"].includes(hostname);\n }\n\n constructor() {\n const shouldFollowLink = (baseUrl: URL, targetUrl: URL) => {\n // Must be in same repository\n if (this.getRepoPath(baseUrl) !== this.getRepoPath(targetUrl)) {\n return false;\n }\n\n const path = targetUrl.pathname;\n\n // Root README (repository root)\n if (path === this.getRepoPath(targetUrl)) {\n return true;\n }\n\n // Wiki pages\n if (path.startsWith(`${this.getRepoPath(targetUrl)}/wiki`)) {\n return true;\n }\n\n // Markdown files under /blob/\n if (\n path.startsWith(`${this.getRepoPath(targetUrl)}/blob/`) &&\n path.endsWith(\".md\")\n ) {\n return true;\n }\n\n return false;\n };\n\n this.defaultStrategy = new WebScraperStrategy({\n urlNormalizerOptions: {\n ignoreCase: true,\n removeHash: true,\n removeTrailingSlash: true,\n removeQuery: true, // Remove query parameters like ?tab=readme-ov-file\n },\n shouldFollowLink,\n });\n }\n\n private getRepoPath(url: URL): string {\n // Extract /<org>/<repo> from github.com/<org>/<repo>/...\n const match = url.pathname.match(/^\\/[^/]+\\/[^/]+/);\n return match?.[0] || \"\";\n }\n\n async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal,\n ): Promise<void> {\n // Validate it's a GitHub URL\n const url = new URL(options.url);\n if (!url.hostname.includes(\"github.com\")) {\n throw new Error(\"URL must be a GitHub URL\");\n }\n\n // Pass signal down to the delegated strategy\n await this.defaultStrategy.scrape(options, progressCallback, signal);\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Document, ProgressCallback } from \"../../types\";\nimport { logger } from \"../../utils/logger\";\nimport { FileFetcher } from \"../fetcher\";\nimport type { RawContent } from \"../fetcher/types\";\nimport { HtmlPipeline } from \"../pipelines/HtmlPipeline\";\nimport { MarkdownPipeline } from \"../pipelines/MarkdownPipeline\";\nimport type { ScraperOptions, ScraperProgress } from \"../types\";\nimport { BaseScraperStrategy, type QueueItem } from \"./BaseScraperStrategy\";\n\n/**\n * LocalFileStrategy handles crawling and scraping of local files and folders using file:// URLs.\n *\n * All files with a MIME type of `text/*` are processed. This includes HTML, Markdown, plain text, and source code files such as `.js`, `.ts`, `.tsx`, `.css`, etc. Binary files, PDFs, images, and other non-text formats are ignored.\n *\n * Supports include/exclude filters and percent-encoded paths.\n */\nexport class LocalFileStrategy extends BaseScraperStrategy {\n private readonly fileFetcher = new FileFetcher();\n private readonly htmlPipeline: HtmlPipeline;\n private readonly markdownPipeline: MarkdownPipeline;\n private readonly pipelines: [HtmlPipeline, MarkdownPipeline];\n\n constructor() {\n super();\n this.htmlPipeline = new HtmlPipeline();\n this.markdownPipeline = new MarkdownPipeline();\n this.pipelines = [this.htmlPipeline, this.markdownPipeline];\n }\n\n canHandle(url: string): boolean {\n return url.startsWith(\"file://\");\n }\n\n protected async processItem(\n item: QueueItem,\n options: ScraperOptions,\n _progressCallback?: ProgressCallback<ScraperProgress>,\n _signal?: AbortSignal,\n ): Promise<{ document?: Document; links?: string[] }> {\n // Always decode the file path from file:// URL\n const filePath = decodeURIComponent(item.url.replace(/^file:\\/\\//, \"\"));\n const stats = await fs.stat(filePath);\n\n if (stats.isDirectory()) {\n const contents = await fs.readdir(filePath);\n // Only return links that pass shouldProcessUrl\n const links = contents\n .map((name) => `file://${path.join(filePath, name)}`)\n .filter((url) => this.shouldProcessUrl(url, options));\n return { links };\n }\n\n logger.info(`🗂️ Processing file ${this.pageCount}/${options.maxPages}: ${filePath}`);\n\n const rawContent: RawContent = await this.fileFetcher.fetch(item.url);\n\n let processed: Awaited<ReturnType<HtmlPipeline[\"process\"]>> | undefined;\n\n for (const pipeline of this.pipelines) {\n if (pipeline.canProcess(rawContent)) {\n processed = await pipeline.process(rawContent, options, this.fileFetcher);\n break;\n }\n }\n\n if (!processed) {\n logger.warn(\n `⚠️ Unsupported content type \"${rawContent.mimeType}\" for file ${filePath}. Skipping processing.`,\n );\n return { document: undefined, links: [] };\n }\n\n for (const err of processed.errors) {\n logger.warn(`⚠️ Processing error for ${filePath}: ${err.message}`);\n }\n\n return {\n document: {\n content: typeof processed.textContent === \"string\" ? processed.textContent : \"\",\n metadata: {\n url: rawContent.source,\n title:\n typeof processed.metadata.title === \"string\"\n ? processed.metadata.title\n : \"Untitled\",\n library: options.library,\n version: options.version,\n },\n } satisfies Document,\n };\n }\n\n async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal,\n ): Promise<void> {\n try {\n await super.scrape(options, progressCallback, signal);\n } finally {\n await this.htmlPipeline.close();\n await this.markdownPipeline.close();\n }\n }\n}\n","import type { ProgressCallback } from \"../../types\";\nimport type { ScraperOptions, ScraperProgress, ScraperStrategy } from \"../types\";\nimport { WebScraperStrategy } from \"./WebScraperStrategy\";\n\nexport class NpmScraperStrategy implements ScraperStrategy {\n private defaultStrategy: WebScraperStrategy;\n\n canHandle(url: string): boolean {\n const { hostname } = new URL(url);\n return [\"npmjs.org\", \"npmjs.com\", \"www.npmjs.com\"].includes(hostname);\n }\n\n constructor() {\n this.defaultStrategy = new WebScraperStrategy({\n urlNormalizerOptions: {\n ignoreCase: true,\n removeHash: true,\n removeTrailingSlash: true,\n removeQuery: true, // Enable removeQuery for NPM packages\n },\n });\n }\n\n async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal,\n ): Promise<void> {\n // Use default strategy with our configuration, passing the signal\n await this.defaultStrategy.scrape(options, progressCallback, signal);\n }\n}\n","import type { ProgressCallback } from \"../../types\";\nimport type { ScraperOptions, ScraperProgress, ScraperStrategy } from \"../types\";\nimport { WebScraperStrategy } from \"./WebScraperStrategy\";\n\nexport class PyPiScraperStrategy implements ScraperStrategy {\n private defaultStrategy: WebScraperStrategy;\n\n canHandle(url: string): boolean {\n const { hostname } = new URL(url);\n return [\"pypi.org\", \"www.pypi.org\"].includes(hostname);\n }\n\n constructor() {\n this.defaultStrategy = new WebScraperStrategy({\n urlNormalizerOptions: {\n ignoreCase: true,\n removeHash: true,\n removeTrailingSlash: true,\n removeQuery: true, // Enable removeQuery for PyPI packages\n },\n });\n }\n\n async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal,\n ): Promise<void> {\n // Use default strategy with our configuration, passing the signal\n await this.defaultStrategy.scrape(options, progressCallback, signal);\n }\n}\n","import { ScraperError } from \"../utils/errors\";\nimport { validateUrl } from \"../utils/url\";\nimport { GitHubScraperStrategy } from \"./strategies/GitHubScraperStrategy\";\nimport { LocalFileStrategy } from \"./strategies/LocalFileStrategy\";\nimport { NpmScraperStrategy } from \"./strategies/NpmScraperStrategy\";\nimport { PyPiScraperStrategy } from \"./strategies/PyPiScraperStrategy\";\nimport { WebScraperStrategy } from \"./strategies/WebScraperStrategy\";\nimport type { ScraperStrategy } from \"./types\";\n\nexport class ScraperRegistry {\n private strategies: ScraperStrategy[];\n\n constructor() {\n this.strategies = [\n new NpmScraperStrategy(),\n new PyPiScraperStrategy(),\n new GitHubScraperStrategy(),\n new WebScraperStrategy(),\n new LocalFileStrategy(),\n ];\n }\n\n getStrategy(url: string): ScraperStrategy {\n validateUrl(url);\n const strategy = this.strategies.find((s) => s.canHandle(url));\n if (!strategy) {\n throw new ScraperError(`No strategy found for URL: ${url}`);\n }\n return strategy;\n }\n}\n","import type { ProgressCallback } from \"../types\";\nimport { ScraperError } from \"../utils/errors\";\nimport type { ScraperRegistry } from \"./ScraperRegistry\";\nimport type { ScraperOptions, ScraperProgress } from \"./types\";\n\n/**\n * Orchestrates document scraping operations using registered scraping strategies.\n * Automatically selects appropriate strategy based on URL patterns.\n */\nexport class ScraperService {\n private registry: ScraperRegistry;\n\n constructor(registry: ScraperRegistry) {\n this.registry = registry;\n }\n\n /**\n * Scrapes content from the provided URL using the appropriate strategy.\n * Reports progress via callback and handles errors.\n */\n async scrape(\n options: ScraperOptions,\n progressCallback: ProgressCallback<ScraperProgress>,\n signal?: AbortSignal, // Add optional signal parameter\n ): Promise<void> {\n // Find strategy for this URL\n const strategy = this.registry.getStrategy(options.url);\n if (!strategy) {\n throw new ScraperError(`No scraper strategy found for URL: ${options.url}`, false);\n }\n\n // Pass the signal down to the strategy\n await strategy.scrape(options, progressCallback, signal);\n }\n}\n","import type { ScraperService } from \"../scraper\";\nimport type { ScraperProgress } from \"../scraper/types\";\nimport type { DocumentManagementService } from \"../store\";\nimport { logger } from \"../utils/logger\";\nimport { CancellationError } from \"./errors\";\nimport type { InternalPipelineJob, PipelineManagerCallbacks } from \"./types\";\n\n/**\n * Executes a single document processing job.\n * Handles scraping, storing documents, and reporting progress/errors via callbacks.\n */\nexport class PipelineWorker {\n // Dependencies are passed in, making the worker stateless regarding specific jobs\n private readonly store: DocumentManagementService;\n private readonly scraperService: ScraperService;\n\n // Constructor accepts dependencies needed for execution\n constructor(store: DocumentManagementService, scraperService: ScraperService) {\n this.store = store;\n this.scraperService = scraperService;\n }\n\n /**\n * Executes the given pipeline job.\n * @param job - The job to execute.\n * @param callbacks - Callbacks provided by the manager for reporting.\n */\n async executeJob(\n job: InternalPipelineJob,\n callbacks: PipelineManagerCallbacks,\n ): Promise<void> {\n const {\n id: jobId,\n library,\n version,\n sourceUrl,\n scraperOptions,\n abortController,\n } = job;\n const signal = abortController.signal;\n\n logger.debug(`[${jobId}] Worker starting job for ${library}@${version}`);\n\n try {\n // Clear existing documents for this library/version before scraping\n await this.store.removeAllDocuments(library, version);\n logger.info(\n `💾 Cleared store for ${library}@${version || \"[no version]\"} before scraping.`,\n );\n\n // Construct runtime options from job context + stored configuration\n const runtimeOptions = {\n url: sourceUrl ?? \"\",\n library,\n version,\n ...scraperOptions,\n };\n\n // --- Core Job Logic ---\n await this.scraperService.scrape(\n runtimeOptions,\n async (progress: ScraperProgress) => {\n // Check for cancellation signal before processing each document\n if (signal.aborted) {\n throw new CancellationError(\"Job cancelled during scraping progress\");\n }\n\n // Update job object directly (manager holds the reference)\n // Report progress via manager's callback (single source of truth)\n await callbacks.onJobProgress?.(job, progress);\n\n if (progress.document) {\n try {\n await this.store.addDocument(library, version, {\n pageContent: progress.document.content,\n metadata: progress.document.metadata,\n });\n logger.debug(\n `[${jobId}] Stored document: ${progress.document.metadata.url}`,\n );\n } catch (docError) {\n logger.error(\n `❌ [${jobId}] Failed to store document ${progress.document.metadata.url}: ${docError}`,\n );\n // Report document-specific errors via manager's callback\n await callbacks.onJobError?.(\n job,\n docError instanceof Error ? docError : new Error(String(docError)),\n progress.document,\n );\n // Decide if a single document error should fail the whole job\n // For now, we log and continue. To fail, re-throw here.\n }\n }\n },\n signal, // Pass signal to scraper service\n );\n // --- End Core Job Logic ---\n\n // Check signal one last time after scrape finishes\n if (signal.aborted) {\n throw new CancellationError(\"Job cancelled\");\n }\n\n // If successful and not cancelled, the manager will handle status update\n logger.debug(`[${jobId}] Worker finished job successfully.`);\n } catch (error) {\n // Re-throw error to be caught by the manager in _runJob\n logger.warn(`⚠️ [${jobId}] Worker encountered error: ${error}`);\n throw error;\n }\n // Note: The manager (_runJob) is responsible for updating final job status (COMPLETED/FAILED/CANCELLED)\n // and resolving/rejecting the completion promise based on the outcome here.\n }\n\n // --- Old methods removed ---\n // process()\n // stop()\n // setCallbacks()\n // handleScrapingProgress()\n}\n","/**\n * PipelineManager orchestrates a queue of scraping/indexing jobs.\n * - Controls concurrency, recovery, and job lifecycle\n * - Bridges in-memory job state with the persistent store\n * - Delegates execution to PipelineWorker and emits callbacks\n * Note: completionPromise has an attached no-op catch to avoid unhandled\n * promise rejection warnings when a job fails before a consumer awaits it.\n */\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { ScraperRegistry, ScraperService } from \"../scraper\";\nimport type { ScraperOptions, ScraperProgress } from \"../scraper/types\";\nimport type { DocumentManagementService } from \"../store\";\nimport { VersionStatus } from \"../store/types\";\nimport { DEFAULT_MAX_CONCURRENCY } from \"../utils/config\";\nimport { logger } from \"../utils/logger\";\nimport { CancellationError, PipelineStateError } from \"./errors\";\nimport type { IPipeline } from \"./interfaces\";\nimport { PipelineWorker } from \"./PipelineWorker\"; // Import the worker\nimport type { InternalPipelineJob, PipelineJob, PipelineManagerCallbacks } from \"./types\";\nimport { PipelineJobStatus } from \"./types\";\n\n/**\n * Manages a queue of document processing jobs, controlling concurrency and tracking progress.\n */\nexport class PipelineManager implements IPipeline {\n private jobMap: Map<string, InternalPipelineJob> = new Map();\n private jobQueue: string[] = [];\n private activeWorkers: Set<string> = new Set();\n private isRunning = false;\n private concurrency: number;\n private callbacks: PipelineManagerCallbacks = {};\n private composedCallbacks: PipelineManagerCallbacks = {};\n private store: DocumentManagementService;\n private scraperService: ScraperService;\n private shouldRecoverJobs: boolean;\n\n constructor(\n store: DocumentManagementService,\n concurrency: number = DEFAULT_MAX_CONCURRENCY,\n options: { recoverJobs?: boolean } = {},\n ) {\n this.store = store;\n this.concurrency = concurrency;\n this.shouldRecoverJobs = options.recoverJobs ?? true; // Default to true for backward compatibility\n // ScraperService needs a registry. We create one internally for the manager.\n const registry = new ScraperRegistry();\n this.scraperService = new ScraperService(registry);\n\n // Initialize composed callbacks to ensure progress persistence even before setCallbacks is called\n this.rebuildComposedCallbacks();\n }\n\n /**\n * Registers callback handlers for pipeline manager events.\n */\n setCallbacks(callbacks: PipelineManagerCallbacks): void {\n this.callbacks = callbacks || {};\n this.rebuildComposedCallbacks();\n }\n\n /** Build composed callbacks that ensure persistence then delegate to user callbacks */\n private rebuildComposedCallbacks(): void {\n const user = this.callbacks;\n this.composedCallbacks = {\n onJobProgress: async (job, progress) => {\n await this.updateJobProgress(job, progress);\n await user.onJobProgress?.(job, progress);\n },\n onJobStatusChange: async (job) => {\n await user.onJobStatusChange?.(job);\n },\n onJobError: async (job, error, document) => {\n await user.onJobError?.(job, error, document);\n },\n };\n }\n\n /**\n * Converts internal job representation to public job interface.\n */\n private toPublicJob(job: InternalPipelineJob): PipelineJob {\n return {\n id: job.id,\n library: job.library,\n version: job.version || null, // Convert empty string to null for public API\n status: job.status,\n progress: job.progress,\n error: job.error ? { message: job.error.message } : null,\n createdAt: job.createdAt,\n startedAt: job.startedAt,\n finishedAt: job.finishedAt,\n versionId: job.versionId,\n versionStatus: job.versionStatus,\n progressPages: job.progressPages,\n progressMaxPages: job.progressMaxPages,\n errorMessage: job.errorMessage,\n updatedAt: job.updatedAt,\n sourceUrl: job.sourceUrl,\n scraperOptions: job.scraperOptions,\n };\n }\n\n /**\n * Starts the pipeline manager's worker processing.\n */\n async start(): Promise<void> {\n if (this.isRunning) {\n logger.warn(\"⚠️ PipelineManager is already running.\");\n return;\n }\n this.isRunning = true;\n logger.debug(\n `PipelineManager started with concurrency ${this.concurrency}, recoverJobs: ${this.shouldRecoverJobs}.`,\n );\n\n // Recover pending jobs from database on startup only if enabled\n if (this.shouldRecoverJobs) {\n await this.recoverPendingJobs();\n } else {\n logger.debug(\"Job recovery disabled for this PipelineManager instance\");\n }\n\n this._processQueue().catch((error) => {\n logger.error(`❌ Error in processQueue during start: ${error}`);\n }); // Start processing any existing jobs\n }\n\n /**\n * Recovers pending jobs from the database after server restart.\n * Finds versions with RUNNING status and resets them to QUEUED for re-processing.\n * Also loads all QUEUED versions back into the pipeline queue.\n */\n async recoverPendingJobs(): Promise<void> {\n try {\n // Reset RUNNING jobs to QUEUED (they were interrupted by server restart)\n const runningVersions = await this.store.getRunningVersions();\n for (const version of runningVersions) {\n await this.store.updateVersionStatus(version.id, VersionStatus.QUEUED);\n logger.info(\n `🔄 Reset interrupted job to QUEUED: ${version.library_name}@${version.name || \"unversioned\"}`,\n );\n }\n\n // Load all QUEUED versions back into pipeline\n const queuedVersions = await this.store.getVersionsByStatus([VersionStatus.QUEUED]);\n for (const version of queuedVersions) {\n // Create complete job with all database state restored\n const jobId = uuidv4();\n const abortController = new AbortController();\n let resolveCompletion!: () => void;\n let rejectCompletion!: (reason?: unknown) => void;\n\n const completionPromise = new Promise<void>((resolve, reject) => {\n resolveCompletion = resolve;\n rejectCompletion = reject;\n });\n // Prevent unhandled rejection warnings if rejection occurs before consumers attach handlers\n completionPromise.catch(() => {});\n\n // Parse stored scraper options\n let parsedScraperOptions = null;\n if (version.scraper_options) {\n try {\n parsedScraperOptions = JSON.parse(version.scraper_options);\n } catch (error) {\n logger.warn(\n `⚠️ Failed to parse scraper options for ${version.library_name}@${version.name || \"unversioned\"}: ${error}`,\n );\n }\n }\n\n const job: InternalPipelineJob = {\n id: jobId,\n library: version.library_name,\n version: version.name || \"\",\n status: PipelineJobStatus.QUEUED,\n progress: null,\n error: null,\n createdAt: new Date(version.created_at),\n // For recovered QUEUED jobs, startedAt must be null to reflect queued state.\n startedAt: null,\n finishedAt: null,\n abortController,\n completionPromise,\n resolveCompletion,\n rejectCompletion,\n\n // Database fields (single source of truth)\n versionId: version.id,\n versionStatus: version.status,\n progressPages: version.progress_pages,\n progressMaxPages: version.progress_max_pages,\n errorMessage: version.error_message,\n updatedAt: new Date(version.updated_at),\n sourceUrl: version.source_url,\n scraperOptions: parsedScraperOptions,\n };\n\n this.jobMap.set(jobId, job);\n this.jobQueue.push(jobId);\n }\n\n if (queuedVersions.length > 0) {\n logger.info(`📥 Recovered ${queuedVersions.length} pending job(s) from database`);\n } else {\n logger.debug(\"No pending jobs to recover from database\");\n }\n } catch (error) {\n logger.error(`❌ Failed to recover pending jobs: ${error}`);\n }\n }\n\n /**\n * Stops the pipeline manager and attempts to gracefully shut down workers.\n * Currently, it just stops processing new jobs. Cancellation of active jobs\n * needs explicit `cancelJob` calls.\n */\n async stop(): Promise<void> {\n if (!this.isRunning) {\n logger.warn(\"⚠️ PipelineManager is not running.\");\n return;\n }\n this.isRunning = false;\n logger.debug(\"PipelineManager stopping. No new jobs will be started.\");\n // Note: Does not automatically cancel active jobs.\n }\n\n /**\n * Enqueues a new document processing job, aborting any existing QUEUED/RUNNING job for the same library+version (including unversioned).\n */\n async enqueueJob(\n library: string,\n version: string | undefined | null,\n options: ScraperOptions,\n ): Promise<string> {\n // Normalize version: treat undefined/null as \"\" (unversioned)\n const normalizedVersion = version ?? \"\";\n\n // Extract URL and convert ScraperOptions to VersionScraperOptions\n const {\n url,\n library: _library,\n version: _version,\n signal: _signal,\n ...versionOptions\n } = options;\n\n // Abort any existing QUEUED or RUNNING job for the same library+version\n const allJobs = await this.getJobs();\n const duplicateJobs = allJobs.filter(\n (job) =>\n job.library === library &&\n (job.version ?? \"\") === normalizedVersion && // Normalize null to empty string for comparison\n [PipelineJobStatus.QUEUED, PipelineJobStatus.RUNNING].includes(job.status),\n );\n for (const job of duplicateJobs) {\n logger.info(\n `🚫 Aborting duplicate job for ${library}@${normalizedVersion}: ${job.id}`,\n );\n await this.cancelJob(job.id);\n }\n\n const jobId = uuidv4();\n const abortController = new AbortController();\n let resolveCompletion!: () => void;\n let rejectCompletion!: (reason?: unknown) => void;\n\n const completionPromise = new Promise<void>((resolve, reject) => {\n resolveCompletion = resolve;\n rejectCompletion = reject;\n });\n // Prevent unhandled rejection warnings if rejection occurs before consumers attach handlers\n completionPromise.catch(() => {});\n\n const job: InternalPipelineJob = {\n id: jobId,\n library,\n version: normalizedVersion,\n status: PipelineJobStatus.QUEUED,\n progress: null,\n error: null,\n createdAt: new Date(),\n startedAt: null,\n finishedAt: null,\n abortController,\n completionPromise,\n resolveCompletion,\n rejectCompletion,\n // Database fields (single source of truth)\n // Will be populated by updateJobStatus\n progressPages: 0,\n progressMaxPages: 0,\n errorMessage: null,\n updatedAt: new Date(),\n sourceUrl: url,\n scraperOptions: versionOptions,\n };\n\n this.jobMap.set(jobId, job);\n this.jobQueue.push(jobId);\n logger.info(\n `📝 Job enqueued: ${jobId} for ${library}${normalizedVersion ? `@${normalizedVersion}` : \" (unversioned)\"}`,\n );\n\n // Update database status to QUEUED\n await this.updateJobStatus(job, PipelineJobStatus.QUEUED);\n\n // Trigger processing if manager is running\n if (this.isRunning) {\n this._processQueue().catch((error) => {\n logger.error(`❌ Error in processQueue during enqueue: ${error}`);\n });\n }\n\n return jobId;\n }\n\n /**\n * Enqueues a job using stored scraper options from a previous indexing run.\n * If no stored options are found, throws an error.\n */\n async enqueueJobWithStoredOptions(\n library: string,\n version: string | undefined | null,\n ): Promise<string> {\n const normalizedVersion = version ?? \"\";\n\n try {\n // Get the version ID to retrieve stored options\n const versionId = await this.store.ensureLibraryAndVersion(\n library,\n normalizedVersion,\n );\n const versionRecord = await this.store.getVersionWithStoredOptions(versionId);\n\n if (!versionRecord?.scraper_options || !versionRecord.source_url) {\n throw new Error(\n `No stored scraper options found for ${library}@${normalizedVersion || \"unversioned\"}`,\n );\n }\n\n const storedOptions = JSON.parse(versionRecord.scraper_options);\n\n // Reconstruct complete scraper options\n const completeOptions: ScraperOptions = {\n url: versionRecord.source_url,\n library,\n version: normalizedVersion,\n ...storedOptions,\n };\n\n logger.info(\n `🔄 Re-indexing ${library}@${normalizedVersion || \"unversioned\"} with stored options from ${versionRecord.source_url}`,\n );\n\n return this.enqueueJob(library, normalizedVersion, completeOptions);\n } catch (error) {\n logger.error(`❌ Failed to enqueue job with stored options: ${error}`);\n throw error;\n }\n }\n\n /**\n * Retrieves the current state of a specific job.\n */\n async getJob(jobId: string): Promise<PipelineJob | undefined> {\n const internalJob = this.jobMap.get(jobId);\n return internalJob ? this.toPublicJob(internalJob) : undefined;\n }\n\n /**\n * Retrieves the current state of all jobs (or a subset based on status).\n */\n async getJobs(status?: PipelineJobStatus): Promise<PipelineJob[]> {\n const allJobs = Array.from(this.jobMap.values());\n const filteredJobs = status\n ? allJobs.filter((job) => job.status === status)\n : allJobs;\n return filteredJobs.map((job) => this.toPublicJob(job));\n }\n\n /**\n * Returns a promise that resolves when the specified job completes, fails, or is cancelled.\n * For cancelled jobs, this resolves successfully rather than rejecting.\n */\n async waitForJobCompletion(jobId: string): Promise<void> {\n const job = this.jobMap.get(jobId);\n if (!job) {\n throw new PipelineStateError(`Job not found: ${jobId}`);\n }\n\n try {\n await job.completionPromise;\n } catch (error) {\n // If the job was cancelled, treat it as successful completion\n if (\n error instanceof CancellationError ||\n job.status === PipelineJobStatus.CANCELLED\n ) {\n return; // Resolve successfully for cancelled jobs\n }\n // Re-throw other errors (failed jobs)\n throw error;\n }\n }\n\n /**\n * Attempts to cancel a queued or running job.\n */\n async cancelJob(jobId: string): Promise<void> {\n const job = this.jobMap.get(jobId);\n if (!job) {\n logger.warn(`❓ Attempted to cancel non-existent job: ${jobId}`);\n return;\n }\n\n switch (job.status) {\n case PipelineJobStatus.QUEUED:\n // Remove from queue and mark as cancelled\n this.jobQueue = this.jobQueue.filter((id) => id !== jobId);\n await this.updateJobStatus(job, PipelineJobStatus.CANCELLED);\n job.finishedAt = new Date();\n logger.info(`🚫 Job cancelled (was queued): ${jobId}`);\n job.rejectCompletion(new PipelineStateError(\"Job cancelled before starting\"));\n break;\n\n case PipelineJobStatus.RUNNING:\n // Signal cancellation via AbortController\n await this.updateJobStatus(job, PipelineJobStatus.CANCELLING);\n job.abortController.abort();\n logger.info(`🚫 Signalling cancellation for running job: ${jobId}`);\n // The worker is responsible for transitioning to CANCELLED and rejecting\n break;\n\n case PipelineJobStatus.COMPLETED:\n case PipelineJobStatus.FAILED:\n case PipelineJobStatus.CANCELLED:\n case PipelineJobStatus.CANCELLING:\n logger.warn(\n `⚠️ Job ${jobId} cannot be cancelled in its current state: ${job.status}`,\n );\n break;\n\n default:\n logger.error(`❌ Unhandled job status for cancellation: ${job.status}`);\n break;\n }\n }\n\n /**\n * Removes all jobs that are in a final state (completed, cancelled, or failed).\n * Only removes jobs that are not currently in the queue or actively running.\n * @returns The number of jobs that were cleared.\n */\n async clearCompletedJobs(): Promise<number> {\n const completedStatuses = [\n PipelineJobStatus.COMPLETED,\n PipelineJobStatus.CANCELLED,\n PipelineJobStatus.FAILED,\n ];\n\n let clearedCount = 0;\n const jobsToRemove: string[] = [];\n\n // Find all jobs that can be cleared\n for (const [jobId, job] of this.jobMap.entries()) {\n if (completedStatuses.includes(job.status)) {\n jobsToRemove.push(jobId);\n clearedCount++;\n }\n }\n\n // Remove the jobs from the map\n for (const jobId of jobsToRemove) {\n this.jobMap.delete(jobId);\n }\n\n if (clearedCount > 0) {\n logger.info(`🧹 Cleared ${clearedCount} completed job(s) from the queue`);\n } else {\n logger.debug(\"No completed jobs to clear\");\n }\n\n return clearedCount;\n }\n\n // --- Private Methods ---\n\n /**\n * Processes the job queue, starting new workers if capacity allows.\n */\n private async _processQueue(): Promise<void> {\n if (!this.isRunning) return;\n\n while (this.activeWorkers.size < this.concurrency && this.jobQueue.length > 0) {\n const jobId = this.jobQueue.shift();\n if (!jobId) continue; // Should not happen, but safety check\n\n const job = this.jobMap.get(jobId);\n if (!job || job.status !== PipelineJobStatus.QUEUED) {\n logger.warn(`⏭️ Skipping job ${jobId} in queue (not found or not queued).`);\n continue;\n }\n\n this.activeWorkers.add(jobId);\n await this.updateJobStatus(job, PipelineJobStatus.RUNNING);\n job.startedAt = new Date();\n\n // Start the actual job execution asynchronously\n this._runJob(job).catch(async (error) => {\n // Catch unexpected errors during job setup/execution not handled by _runJob itself\n logger.error(`❌ Unhandled error during job ${jobId} execution: ${error}`);\n if (\n job.status !== PipelineJobStatus.FAILED &&\n job.status !== PipelineJobStatus.CANCELLED\n ) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n await this.updateJobStatus(job, PipelineJobStatus.FAILED, errorMessage);\n job.error = error instanceof Error ? error : new Error(String(error));\n job.finishedAt = new Date();\n job.rejectCompletion(job.error);\n }\n this.activeWorkers.delete(jobId);\n this._processQueue().catch((error) => {\n logger.error(`❌ Error in processQueue after job completion: ${error}`);\n }); // Check if another job can start\n });\n }\n }\n\n /**\n * Executes a single pipeline job by delegating to a PipelineWorker.\n * Handles final status updates and promise resolution/rejection.\n */\n private async _runJob(job: InternalPipelineJob): Promise<void> {\n const { id: jobId, abortController } = job;\n const signal = abortController.signal; // Get signal for error checking\n\n // Instantiate a worker for this job.\n // Dependencies (store, scraperService) are held by the manager.\n const worker = new PipelineWorker(this.store, this.scraperService);\n\n try {\n // Delegate the actual work to the worker using composed callbacks\n await worker.executeJob(job, this.composedCallbacks);\n\n // If executeJob completes without throwing, and we weren't cancelled meanwhile...\n if (signal.aborted) {\n // Check signal again in case cancellation happened *during* the very last await in executeJob\n throw new CancellationError(\"Job cancelled just before completion\");\n }\n\n // Mark as completed\n await this.updateJobStatus(job, PipelineJobStatus.COMPLETED);\n job.finishedAt = new Date();\n job.resolveCompletion();\n\n logger.info(`✅ Job completed: ${jobId}`);\n } catch (error) {\n // Handle errors thrown by the worker, including CancellationError\n if (error instanceof CancellationError || signal.aborted) {\n // Explicitly check for CancellationError or if the signal was aborted\n await this.updateJobStatus(job, PipelineJobStatus.CANCELLED);\n job.finishedAt = new Date();\n // Don't set job.error for cancellations - cancellation is not an error condition\n const cancellationError =\n error instanceof CancellationError\n ? error\n : new CancellationError(\"Job cancelled by signal\");\n logger.info(`🚫 Job execution cancelled: ${jobId}: ${cancellationError.message}`);\n job.rejectCompletion(cancellationError);\n } else {\n // Handle other errors\n const errorMessage = error instanceof Error ? error.message : String(error);\n await this.updateJobStatus(job, PipelineJobStatus.FAILED, errorMessage);\n job.error = error instanceof Error ? error : new Error(String(error));\n job.finishedAt = new Date();\n logger.error(`❌ Job failed: ${jobId}: ${job.error}`);\n job.rejectCompletion(job.error);\n }\n } finally {\n // Ensure worker slot is freed and queue processing continues\n this.activeWorkers.delete(jobId);\n this._processQueue().catch((error) => {\n logger.error(`❌ Error in processQueue after job cleanup: ${error}`);\n });\n }\n }\n\n /**\n * Maps PipelineJobStatus to VersionStatus for database storage.\n */\n private mapJobStatusToVersionStatus(jobStatus: PipelineJobStatus): VersionStatus {\n switch (jobStatus) {\n case PipelineJobStatus.QUEUED:\n return VersionStatus.QUEUED;\n case PipelineJobStatus.RUNNING:\n return VersionStatus.RUNNING;\n case PipelineJobStatus.COMPLETED:\n return VersionStatus.COMPLETED;\n case PipelineJobStatus.FAILED:\n return VersionStatus.FAILED;\n case PipelineJobStatus.CANCELLED:\n return VersionStatus.CANCELLED;\n case PipelineJobStatus.CANCELLING:\n return VersionStatus.RUNNING; // Keep as running in DB until actually cancelled\n default:\n return VersionStatus.NOT_INDEXED;\n }\n }\n\n /**\n * Updates both in-memory job status and database version status (write-through).\n */\n private async updateJobStatus(\n job: InternalPipelineJob,\n newStatus: PipelineJobStatus,\n errorMessage?: string,\n ): Promise<void> {\n // Update in-memory status\n job.status = newStatus;\n if (errorMessage) {\n job.errorMessage = errorMessage;\n }\n job.updatedAt = new Date();\n\n // Update database status\n try {\n // Ensure the library and version exist and get the version ID\n const versionId = await this.store.ensureLibraryAndVersion(\n job.library,\n job.version,\n );\n\n // Update job object with database fields (single source of truth)\n job.versionId = versionId;\n job.versionStatus = this.mapJobStatusToVersionStatus(newStatus);\n\n const dbStatus = this.mapJobStatusToVersionStatus(newStatus);\n await this.store.updateVersionStatus(versionId, dbStatus, errorMessage);\n\n // Store scraper options when job is first queued\n if (newStatus === PipelineJobStatus.QUEUED && job.scraperOptions) {\n try {\n // Reconstruct ScraperOptions for storage (DocumentStore will filter runtime fields)\n const fullOptions = {\n url: job.sourceUrl ?? \"\",\n library: job.library,\n version: job.version,\n ...job.scraperOptions,\n };\n await this.store.storeScraperOptions(versionId, fullOptions);\n logger.debug(\n `💾 Stored scraper options for ${job.library}@${job.version}: ${job.sourceUrl}`,\n );\n } catch (optionsError) {\n // Log warning but don't fail the job - options storage is not critical\n logger.warn(\n `⚠️ Failed to store scraper options for job ${job.id}: ${optionsError}`,\n );\n }\n }\n } catch (error) {\n logger.error(`❌ Failed to update database status for job ${job.id}: ${error}`);\n // Don't throw - we don't want to break the pipeline for database issues\n }\n\n // Fire callback\n await this.callbacks.onJobStatusChange?.(job);\n }\n\n /**\n * Updates both in-memory job progress and database progress (write-through).\n */\n async updateJobProgress(\n job: InternalPipelineJob,\n progress: ScraperProgress,\n ): Promise<void> {\n // Update in-memory progress\n job.progress = progress;\n job.progressPages = progress.pagesScraped;\n job.progressMaxPages = progress.totalPages;\n job.updatedAt = new Date();\n\n // Update database progress if we have a version ID\n if (job.versionId) {\n try {\n await this.store.updateVersionProgress(\n job.versionId,\n progress.pagesScraped,\n progress.totalPages,\n );\n } catch (error) {\n logger.error(`❌ Failed to update database progress for job ${job.id}: ${error}`);\n // Don't throw - we don't want to break the pipeline for database issues\n }\n }\n\n // Note: Do not invoke onJobProgress callback here.\n // Callbacks are wired by services (e.g., workerService/CLI) and already call this method.\n }\n}\n","import type { DocumentManagementService } from \"../store\";\nimport { DEFAULT_MAX_CONCURRENCY } from \"../utils/config\";\nimport { logger } from \"../utils/logger\";\nimport type { IPipeline, PipelineOptions } from \"./interfaces\";\nimport { PipelineClient } from \"./PipelineClient\";\nimport { PipelineManager } from \"./PipelineManager\";\n\n/**\n * Factory for creating pipeline interfaces based on functionality requirements.\n */\nexport namespace PipelineFactory {\n /**\n * Creates the appropriate pipeline interface based on desired functionality.\n *\n * @param docService - Document management service instance\n * @param options - Pipeline configuration options\n * @returns Pipeline interface (PipelineManager or future PipelineClient)\n */\n export async function createPipeline(\n docService: DocumentManagementService,\n options: PipelineOptions = {},\n ): Promise<IPipeline> {\n const {\n recoverJobs = false, // Default to false for safety\n serverUrl,\n concurrency = DEFAULT_MAX_CONCURRENCY,\n } = options;\n\n logger.debug(\n `Creating pipeline: recoverJobs=${recoverJobs}, serverUrl=${serverUrl || \"none\"}, concurrency=${concurrency}`,\n );\n\n if (serverUrl) {\n // External pipeline requested\n logger.debug(`Creating PipelineClient for external worker at: ${serverUrl}`);\n return new PipelineClient(serverUrl);\n }\n\n // Local embedded pipeline with specified behavior\n return new PipelineManager(docService, concurrency, { recoverJobs });\n }\n}\n","/**\n * Base error class for all splitter-related errors\n */\nexport class SplitterError extends Error {}\n\n/**\n * Thrown when content cannot be split further while maintaining its validity\n * (e.g., markdown tables require headers, code blocks require language and backticks)\n */\nexport class MinimumChunkSizeError extends SplitterError {\n constructor(size: number, maxSize: number) {\n super(\n `Cannot split content any further. Content requires minimum chunk size of ${size} bytes, but maximum allowed is ${maxSize} bytes.`,\n );\n }\n}\n\n/**\n * Generic error for content splitting failures\n */\nexport class ContentSplitterError extends SplitterError {}\n","import type { ContentChunk, DocumentSplitter, SectionContentType } from \"./types\";\n\n/**\n * Takes small document chunks and greedily concatenates them into larger, more meaningful units\n * while preserving document structure and semantic boundaries.\n *\n * This approach improves embedding quality by:\n * - Maintaining context by keeping related content together\n * - Respecting natural document breaks at major section boundaries (H1/H2)\n * - Ensuring chunks are large enough to capture meaningful relationships\n * - Preventing chunks from becoming too large for effective embedding\n */\nexport class GreedySplitter implements DocumentSplitter {\n private baseSplitter: DocumentSplitter;\n private minChunkSize: number;\n private preferredChunkSize: number;\n\n /**\n * Combines a base document splitter with size constraints to produce optimally-sized chunks.\n * The base splitter handles the initial semantic splitting, while this class handles\n * the concatenation strategy.\n */\n constructor(\n baseSplitter: DocumentSplitter,\n minChunkSize: number,\n preferredChunkSize: number,\n ) {\n this.baseSplitter = baseSplitter;\n this.minChunkSize = minChunkSize;\n this.preferredChunkSize = preferredChunkSize;\n }\n\n /**\n * Uses a greedy concatenation strategy to build optimally-sized chunks. Small chunks\n * are combined until they reach the minimum size, but splits are preserved at major\n * section boundaries to maintain document structure. This balances the need for\n * context with semantic coherence.\n */\n async splitText(markdown: string): Promise<ContentChunk[]> {\n const initialChunks = await this.baseSplitter.splitText(markdown);\n const concatenatedChunks: ContentChunk[] = [];\n let currentChunk: ContentChunk | null = null;\n\n for (const nextChunk of initialChunks) {\n if (currentChunk) {\n if (this.wouldExceedMaxSize(currentChunk, nextChunk)) {\n concatenatedChunks.push(currentChunk);\n currentChunk = this.cloneChunk(nextChunk);\n continue;\n }\n if (\n currentChunk.content.length >= this.minChunkSize &&\n this.startsNewMajorSection(nextChunk)\n ) {\n concatenatedChunks.push(currentChunk);\n currentChunk = this.cloneChunk(nextChunk);\n continue;\n }\n currentChunk.content += `\\n${nextChunk.content}`;\n currentChunk.section = this.mergeSectionInfo(currentChunk, nextChunk);\n currentChunk.types = this.mergeTypes(currentChunk.types, nextChunk.types);\n } else {\n currentChunk = this.cloneChunk(nextChunk);\n }\n }\n\n if (currentChunk) {\n concatenatedChunks.push(currentChunk);\n }\n\n return concatenatedChunks;\n }\n\n private cloneChunk(chunk: ContentChunk): ContentChunk {\n return {\n types: [...chunk.types],\n content: chunk.content,\n section: {\n level: chunk.section.level,\n path: [...chunk.section.path],\n },\n };\n }\n\n /**\n * H1 and H2 headings represent major conceptual breaks in the document.\n * Preserving these splits helps maintain the document's logical structure.\n */\n private startsNewMajorSection(chunk: ContentChunk): boolean {\n return chunk.section.level === 1 || chunk.section.level === 2;\n }\n\n /**\n * Size limit check to ensure chunks remain within embedding model constraints.\n * Essential for maintaining consistent embedding quality and avoiding truncation.\n */\n private wouldExceedMaxSize(\n currentChunk: ContentChunk | null,\n nextChunk: ContentChunk,\n ): boolean {\n if (!currentChunk) {\n return false;\n }\n return (\n currentChunk.content.length + nextChunk.content.length > this.preferredChunkSize\n );\n }\n\n /**\n * Checks if one path is a prefix of another path, indicating a parent-child relationship\n */\n private isPathIncluded(parentPath: string[], childPath: string[]): boolean {\n if (parentPath.length >= childPath.length) return false;\n return parentPath.every((part, i) => part === childPath[i]);\n }\n\n /**\n * Merges section metadata when concatenating chunks, following these rules:\n * 1. Level: Always uses the lowest (most general) level between chunks\n * 2. Path selection:\n * - For parent-child relationships (one path includes the other), uses the child's path\n * - For siblings/unrelated sections, uses the common parent path\n * - If no common path exists, uses the root path ([])\n */\n private mergeSectionInfo(\n currentChunk: ContentChunk,\n nextChunk: ContentChunk,\n ): ContentChunk[\"section\"] {\n // Always use the lowest level\n const level = Math.min(currentChunk.section.level, nextChunk.section.level);\n\n // If sections are exactly equal, preserve all metadata\n if (\n currentChunk.section.level === nextChunk.section.level &&\n currentChunk.section.path.length === nextChunk.section.path.length &&\n currentChunk.section.path.every((p, i) => p === nextChunk.section.path[i])\n ) {\n return currentChunk.section;\n }\n\n // Check if one path includes the other\n if (this.isPathIncluded(currentChunk.section.path, nextChunk.section.path)) {\n return {\n path: nextChunk.section.path,\n level,\n };\n }\n\n if (this.isPathIncluded(nextChunk.section.path, currentChunk.section.path)) {\n return {\n path: currentChunk.section.path,\n level,\n };\n }\n\n // Find common parent path\n const commonPath = this.findCommonPrefix(\n currentChunk.section.path,\n nextChunk.section.path,\n );\n\n return {\n path: commonPath,\n level,\n };\n }\n\n private mergeTypes(\n currentTypes: SectionContentType[],\n nextTypes: SectionContentType[],\n ): SectionContentType[] {\n return [...new Set([...currentTypes, ...nextTypes])];\n }\n\n /**\n * Returns longest common prefix between two paths\n */\n private findCommonPrefix(path1: string[], path2: string[]): string[] {\n const common: string[] = [];\n for (let i = 0; i < Math.min(path1.length, path2.length); i++) {\n if (path1[i] === path2[i]) {\n common.push(path1[i]);\n } else {\n break;\n }\n }\n return common;\n }\n}\n","/**\n * Thoroughly removes all types of whitespace characters from both ends of a string.\n * Handles spaces, tabs, line breaks, and carriage returns.\n */\nexport const fullTrim = (str: string): string => {\n return str.replace(/^[\\s\\r\\n\\t]+|[\\s\\r\\n\\t]+$/g, \"\");\n};\n","import { MinimumChunkSizeError } from \"../errors\";\nimport type { ContentSplitter, ContentSplitterOptions } from \"./types\";\n\n/**\n * Splits code content while preserving language information and formatting.\n * Uses line boundaries for splitting and ensures each chunk is properly\n * wrapped with language-specific code block markers.\n */\nexport class CodeContentSplitter implements ContentSplitter {\n constructor(private options: ContentSplitterOptions) {}\n\n async split(content: string): Promise<string[]> {\n // Determine language and strip triple backticks from content\n const language = content.match(/^```(\\w+)\\n/)?.[1];\n const strippedContent = content.replace(/^```(\\w*)\\n/, \"\").replace(/```\\s*$/, \"\");\n\n const lines = strippedContent.split(\"\\n\");\n const chunks: string[] = [];\n let currentChunkLines: string[] = [];\n\n for (const line of lines) {\n // Check if a single line with code block markers exceeds chunkSize\n const singleLineSize = this.wrap(line, language).length;\n if (singleLineSize > this.options.chunkSize) {\n throw new MinimumChunkSizeError(singleLineSize, this.options.chunkSize);\n }\n\n currentChunkLines.push(line);\n const newChunkContent = this.wrap(currentChunkLines.join(\"\\n\"), language);\n const newChunkSize = newChunkContent.length;\n\n if (newChunkSize > this.options.chunkSize && currentChunkLines.length > 1) {\n // remove last item\n const lastLine = currentChunkLines.pop();\n // wrap content and create chunk\n chunks.push(this.wrap(currentChunkLines.join(\"\\n\"), language));\n currentChunkLines = [lastLine as string];\n }\n }\n\n if (currentChunkLines.length > 0) {\n chunks.push(this.wrap(currentChunkLines.join(\"\\n\"), language));\n }\n\n return chunks;\n }\n\n protected wrap(content: string, language?: string | null): string {\n return `\\`\\`\\`${language || \"\"}\\n${content.replace(/\\n+$/, \"\")}\\n\\`\\`\\``;\n }\n}\n","import { MinimumChunkSizeError } from \"../errors\";\nimport type { ContentSplitter, ContentSplitterOptions } from \"./types\";\n\n/**\n * Interface representing the structure of a parsed markdown table\n */\ninterface ParsedTable {\n headers: string[];\n separator: string;\n rows: string[];\n}\n\n/**\n * Splits table content while preserving headers and table formatting.\n * Each chunk maintains the table structure with headers and separator row.\n */\nexport class TableContentSplitter implements ContentSplitter {\n constructor(private options: ContentSplitterOptions) {}\n\n /**\n * Splits table content into chunks while preserving table structure\n */\n async split(content: string): Promise<string[]> {\n const parsedTable = this.parseTable(content);\n if (!parsedTable) {\n return [content];\n }\n\n const { headers, rows } = parsedTable;\n\n const chunks: string[] = [];\n let currentRows: string[] = [];\n\n for (const row of rows) {\n // Check if a single row with headers exceeds chunkSize\n const singleRowSize = this.wrap(row, headers).length;\n if (singleRowSize > this.options.chunkSize) {\n throw new MinimumChunkSizeError(singleRowSize, this.options.chunkSize);\n }\n\n const newChunkContent = this.wrap([...currentRows, row].join(\"\\n\"), headers);\n const newChunkSize = newChunkContent.length;\n if (newChunkSize > this.options.chunkSize && currentRows.length > 0) {\n // Add current chunk, start new\n chunks.push(this.wrap(currentRows.join(\"\\n\"), headers));\n currentRows = [row];\n } else {\n currentRows.push(row);\n }\n }\n\n if (currentRows.length > 0) {\n chunks.push(this.wrap(currentRows.join(\"\\n\"), headers));\n }\n\n // No merging of table chunks\n return chunks;\n }\n\n protected wrap(content: string, headers: string[]): string {\n const headerRow = `| ${headers.join(\" | \")} |`;\n const separatorRow = `|${headers.map(() => \"---\").join(\"|\")}|`;\n return [headerRow, separatorRow, content].join(\"\\n\");\n }\n\n private parseTable(content: string): ParsedTable | null {\n const lines = content.trim().split(\"\\n\");\n if (lines.length < 3) return null; // Need at least headers, separator, and one data row\n\n const headers = this.parseRow(lines[0]);\n if (!headers) return null;\n\n const separator = lines[1];\n if (!this.isValidSeparator(separator)) return null;\n\n const rows = lines.slice(2).filter((row) => row.trim() !== \"\");\n\n return { headers, separator, rows };\n }\n\n /**\n * Parses a table row into cells\n */\n private parseRow(row: string): string[] | null {\n if (!row.includes(\"|\")) return null;\n return row\n .split(\"|\")\n .map((cell) => cell.trim())\n .filter((cell) => cell !== \"\");\n }\n\n /**\n * Validates the separator row of the table\n */\n private isValidSeparator(separator: string): boolean {\n return separator.includes(\"|\") && /^\\|?[\\s-|]+\\|?$/.test(separator);\n }\n}\n","import { RecursiveCharacterTextSplitter } from \"langchain/text_splitter\";\nimport { fullTrim } from \"../../utils/string\";\nimport { MinimumChunkSizeError } from \"../errors\";\nimport type { ContentSplitter, ContentSplitterOptions } from \"./types\";\n\n/**\n * Splits text content using a hierarchical approach:\n * 1. Try splitting by paragraphs (double newlines)\n * 2. If chunks still too large, split by single newlines\n * 3. Finally, use word boundaries via LangChain's splitter\n */\nexport class TextContentSplitter implements ContentSplitter {\n constructor(private options: ContentSplitterOptions) {}\n\n /**\n * Splits text content into chunks while trying to preserve semantic boundaries.\n * Prefers paragraph breaks, then line breaks, finally falling back to word boundaries.\n */\n async split(content: string): Promise<string[]> {\n const trimmedContent = fullTrim(content);\n\n if (trimmedContent.length <= this.options.chunkSize) {\n return [trimmedContent];\n }\n\n // Check for unsplittable content (e.g., a single word longer than chunkSize)\n const words = trimmedContent.split(/\\s+/);\n const longestWord = words.reduce((max, word) =>\n word.length > max.length ? word : max,\n );\n if (longestWord.length > this.options.chunkSize) {\n throw new MinimumChunkSizeError(longestWord.length, this.options.chunkSize);\n }\n\n // First try splitting by paragraphs (double newlines)\n const paragraphChunks = this.splitByParagraphs(trimmedContent);\n if (this.areChunksValid(paragraphChunks)) {\n // No merging for paragraph chunks; they are already semantically separated\n return paragraphChunks;\n }\n\n // If that doesn't work, try splitting by single newlines\n const lineChunks = this.splitByLines(trimmedContent);\n if (this.areChunksValid(lineChunks)) {\n return this.mergeChunks(lineChunks, \"\\n\");\n }\n\n // Finally, fall back to word-based splitting using LangChain\n const wordChunks = await this.splitByWords(trimmedContent);\n return this.mergeChunks(wordChunks, \" \");\n }\n\n /**\n * Checks if all chunks are within the maximum size limit\n */\n private areChunksValid(chunks: string[]): boolean {\n return chunks.every((chunk) => chunk.length <= this.options.chunkSize);\n }\n\n /**\n * Splits text into chunks by paragraph boundaries (double newlines)\n */\n private splitByParagraphs(text: string): string[] {\n const paragraphs = text\n .split(/\\n\\s*\\n/)\n .map((p) => fullTrim(p))\n .filter(Boolean);\n\n return paragraphs.filter((chunk) => chunk.length > 2);\n }\n\n /**\n * Splits text into chunks by line boundaries\n */\n private splitByLines(text: string): string[] {\n const lines = text\n .split(/\\n/)\n .map((line) => fullTrim(line))\n .filter(Boolean);\n\n return lines.filter((chunk) => chunk.length > 1);\n }\n\n /**\n * Uses LangChain's recursive splitter for word-based splitting as a last resort\n */\n private async splitByWords(text: string): Promise<string[]> {\n const splitter = new RecursiveCharacterTextSplitter({\n chunkSize: this.options.chunkSize,\n chunkOverlap: 0,\n });\n\n const chunks = await splitter.splitText(text);\n return chunks;\n }\n\n /**\n * Attempts to merge small chunks with previous chunks to minimize fragmentation.\n * Only merges if combined size is within maxChunkSize.\n */\n protected mergeChunks(chunks: string[], separator: string): string[] {\n const mergedChunks: string[] = [];\n let currentChunk: string | null = null;\n\n for (const chunk of chunks) {\n if (currentChunk === null) {\n currentChunk = chunk;\n continue;\n }\n\n const currentChunkSize = this.getChunkSize(currentChunk);\n const nextChunkSize = this.getChunkSize(chunk);\n\n if (currentChunkSize + nextChunkSize + separator.length <= this.options.chunkSize) {\n // Merge chunks\n currentChunk = `${currentChunk}${separator}${chunk}`;\n } else {\n // Add the current chunk to the result and start a new one\n mergedChunks.push(currentChunk);\n currentChunk = chunk;\n }\n }\n\n if (currentChunk) {\n mergedChunks.push(currentChunk);\n }\n\n return mergedChunks;\n }\n\n protected getChunkSize(chunk: string): number {\n return chunk.length;\n }\n\n protected wrap(content: string): string {\n return content;\n }\n}\n","import { RecursiveCharacterTextSplitter } from \"langchain/text_splitter\";\nimport remarkGfm from \"remark-gfm\";\nimport remarkHtml from \"remark-html\";\nimport remarkParse from \"remark-parse\";\nimport TurndownService from \"turndown\";\nimport { unified } from \"unified\";\nimport { createJSDOM } from \"../utils/dom\";\nimport { logger } from \"../utils/logger\";\nimport { fullTrim } from \"../utils/string\";\nimport { ContentSplitterError, MinimumChunkSizeError } from \"./errors\";\nimport { CodeContentSplitter } from \"./splitters/CodeContentSplitter\";\nimport { TableContentSplitter } from \"./splitters/TableContentSplitter\";\nimport { TextContentSplitter } from \"./splitters/TextContentSplitter\";\nimport type { ContentChunk, DocumentSplitter, SectionContentType } from \"./types\";\n\n/**\n * Represents a section of content within a document,\n * typically defined by a heading\n */\ninterface DocumentSection {\n level: number;\n path: string[]; // Full path including parent headings\n content: {\n type: SectionContentType;\n text: string;\n }[];\n}\n\n/**\n * Splits markdown documents into semantic chunks while preserving\n * structure and distinguishing between different content types.\n *\n * The splitting process happens in two steps:\n * 1. Split document into sections based on headings (H1-H3 only)\n * 2. Split section content into smaller chunks based on preferredChunkSize\n */\nexport class SemanticMarkdownSplitter implements DocumentSplitter {\n private turndownService: TurndownService;\n public textSplitter: TextContentSplitter;\n public codeSplitter: CodeContentSplitter;\n public tableSplitter: TableContentSplitter;\n\n constructor(\n private preferredChunkSize: number,\n private maxChunkSize: number,\n ) {\n this.turndownService = new TurndownService({\n headingStyle: \"atx\",\n hr: \"---\",\n bulletListMarker: \"-\",\n codeBlockStyle: \"fenced\",\n emDelimiter: \"_\",\n strongDelimiter: \"**\",\n linkStyle: \"inlined\",\n });\n\n // Add table rule to preserve markdown table format\n this.turndownService.addRule(\"table\", {\n filter: [\"table\"],\n replacement: (_content, node) => {\n const table = node as HTMLTableElement;\n const headers = Array.from(table.querySelectorAll(\"th\")).map(\n (th) => th.textContent?.trim() || \"\",\n );\n const rows = Array.from(table.querySelectorAll(\"tr\")).filter(\n (tr) => !tr.querySelector(\"th\"),\n );\n\n if (headers.length === 0 && rows.length === 0) return \"\";\n\n let markdown = \"\\n\";\n if (headers.length > 0) {\n markdown += `| ${headers.join(\" | \")} |\\n`;\n markdown += `|${headers.map(() => \"---\").join(\"|\")}|\\n`;\n }\n\n for (const row of rows) {\n const cells = Array.from(row.querySelectorAll(\"td\")).map(\n (td) => td.textContent?.trim() || \"\",\n );\n markdown += `| ${cells.join(\" | \")} |\\n`;\n }\n\n return markdown;\n },\n });\n\n // Text splitter uses preferred chunk size (keeps paragraphs together if possible)\n this.textSplitter = new TextContentSplitter({\n chunkSize: this.preferredChunkSize,\n });\n // Code/table splitters use the hard chunk size (avoid splitting unless necessary)\n this.codeSplitter = new CodeContentSplitter({\n chunkSize: this.maxChunkSize,\n });\n this.tableSplitter = new TableContentSplitter({\n chunkSize: this.maxChunkSize,\n });\n }\n\n /**\n * Main entry point for splitting markdown content\n */\n async splitText(markdown: string): Promise<ContentChunk[]> {\n const html = await this.markdownToHtml(markdown);\n const dom = await this.parseHtml(html);\n const sections = await this.splitIntoSections(dom);\n return this.splitSectionContent(sections);\n }\n\n /**\n * Step 1: Split document into sections based on H1-H6 headings,\n * as well as code blocks and tables.\n */\n private async splitIntoSections(dom: Document): Promise<DocumentSection[]> {\n const body = dom.querySelector(\"body\");\n if (!body) {\n throw new Error(\"Invalid HTML structure: no body element found\");\n }\n\n let currentSection = this.createRootSection();\n const sections: DocumentSection[] = [];\n const stack: DocumentSection[] = [currentSection];\n\n // Process each child of the body\n for (const element of Array.from(body.children)) {\n const headingMatch = element.tagName.match(/H([1-6])/);\n\n if (headingMatch) {\n // Create new section for H1-H6 heading\n const level = Number.parseInt(headingMatch[1], 10);\n const title = fullTrim(element.textContent || \"\");\n\n // Pop sections from stack until we find the parent level\n while (stack.length > 1 && stack[stack.length - 1].level >= level) {\n stack.pop();\n }\n\n // Start new section with the header\n currentSection = {\n level,\n path: [\n ...stack.slice(1).reduce((acc: string[], s) => {\n const lastPath = s.path[s.path.length - 1];\n if (lastPath) acc.push(lastPath);\n return acc;\n }, []),\n title,\n ],\n content: [\n {\n type: \"heading\",\n text: `${\"#\".repeat(level)} ${title}`,\n },\n ],\n };\n\n sections.push(currentSection);\n stack.push(currentSection);\n } else if (element.tagName === \"PRE\") {\n // Code blocks are kept as separate chunks\n const code = element.querySelector(\"code\");\n const language = code?.className.replace(\"language-\", \"\") || \"\";\n const content = code?.textContent || element.textContent || \"\";\n const markdown = `${\"```\"}${language}\\n${content}\\n${\"```\"}`;\n\n currentSection = {\n level: currentSection.level,\n path: currentSection.path,\n content: [\n {\n type: \"code\",\n text: markdown,\n },\n ],\n } satisfies DocumentSection;\n sections.push(currentSection);\n } else if (element.tagName === \"TABLE\") {\n // Tables are kept as separate chunks\n const markdown = fullTrim(this.turndownService.turndown(element.outerHTML));\n\n currentSection = {\n level: currentSection.level,\n path: currentSection.path,\n content: [\n {\n type: \"table\",\n text: markdown,\n },\n ],\n } satisfies DocumentSection;\n sections.push(currentSection);\n } else {\n const markdown = fullTrim(this.turndownService.turndown(element.innerHTML));\n if (markdown) {\n // Create a new section for the text content\n currentSection = {\n level: currentSection.level,\n path: currentSection.path,\n content: [\n {\n type: \"text\",\n text: markdown,\n },\n ],\n } satisfies DocumentSection;\n sections.push(currentSection);\n }\n }\n }\n\n return sections;\n }\n\n /**\n * Step 2: Split section content into smaller chunks\n */\n private async splitSectionContent(\n sections: DocumentSection[],\n ): Promise<ContentChunk[]> {\n const chunks: ContentChunk[] = [];\n\n for (const section of sections) {\n for (const content of section.content) {\n let splitContent: string[] = [];\n\n try {\n switch (content.type) {\n case \"heading\":\n case \"text\": {\n splitContent = await this.textSplitter.split(content.text);\n break;\n }\n case \"code\": {\n splitContent = await this.codeSplitter.split(content.text);\n break;\n }\n case \"table\": {\n splitContent = await this.tableSplitter.split(content.text);\n break;\n }\n }\n } catch (err) {\n // If it's a MinimumChunkSizeError, use RecursiveCharacterTextSplitter directly\n if (err instanceof MinimumChunkSizeError) {\n logger.warn(\n `⚠ Cannot split ${content.type} chunk normally, using RecursiveCharacterTextSplitter: ${err.message}`,\n );\n\n // Create a RecursiveCharacterTextSplitter with aggressive settings to ensure splitting\n const splitter = new RecursiveCharacterTextSplitter({\n chunkSize: this.maxChunkSize,\n chunkOverlap: Math.min(20, Math.floor(this.maxChunkSize * 0.1)),\n // Use more aggressive separators including empty string as last resort\n separators: [\n \"\\n\\n\",\n \"\\n\",\n \" \",\n \"\\t\",\n \".\",\n \",\",\n \";\",\n \":\",\n \"-\",\n \"(\",\n \")\",\n \"[\",\n \"]\",\n \"{\",\n \"}\",\n \"\",\n ],\n });\n\n const chunks = await splitter.splitText(content.text);\n if (chunks.length === 0) {\n // If still no chunks, use the most extreme approach: just truncate\n splitContent = [content.text.substring(0, this.maxChunkSize)];\n } else {\n splitContent = chunks;\n }\n } else {\n // Convert other error message to string, handling non-Error objects\n const errMessage = err instanceof Error ? err.message : String(err);\n throw new ContentSplitterError(\n `Failed to split ${content.type} content: ${errMessage}`,\n );\n }\n }\n\n // Create chunks from split content\n chunks.push(\n ...splitContent.map(\n (text): ContentChunk => ({\n types: [content.type],\n content: text,\n section: {\n level: section.level,\n path: section.path,\n },\n }),\n ),\n );\n }\n }\n\n return chunks;\n }\n\n /**\n * Helper to create the root section\n */\n private createRootSection(): DocumentSection {\n return {\n level: 0,\n path: [],\n content: [],\n };\n }\n\n /**\n * Convert markdown to HTML using remark\n */\n private async markdownToHtml(markdown: string): Promise<string> {\n const html = await unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkHtml)\n .process(markdown);\n\n return `<!DOCTYPE html>\n <html>\n <body>\n ${String(html)}\n </body>\n </html>`;\n }\n\n /**\n * Parse HTML\n */\n private async parseHtml(html: string): Promise<Document> {\n // Use createJSDOM which includes default options like virtualConsole\n const { window } = createJSDOM(html);\n return window.document;\n }\n}\n","import type { Document } from \"@langchain/core/documents\";\nimport type { DocumentStore } from \"./DocumentStore\";\nimport type { StoreSearchResult } from \"./types\";\n\nconst CHILD_LIMIT = 5;\nconst SIBLING_LIMIT = 2;\n\nexport class DocumentRetrieverService {\n private documentStore: DocumentStore;\n\n constructor(documentStore: DocumentStore) {\n this.documentStore = documentStore;\n }\n\n /**\n * Collects all related chunk IDs for a given initial hit.\n * Returns an object with url, hitId, relatedIds (Set), and score.\n */\n private async getRelatedChunkIds(\n library: string,\n version: string,\n doc: Document,\n siblingLimit = SIBLING_LIMIT,\n childLimit = CHILD_LIMIT,\n ): Promise<{\n url: string;\n hitId: string;\n relatedIds: Set<string>;\n score: number;\n }> {\n const id = doc.id as string;\n const url = doc.metadata.url as string;\n const score = doc.metadata.score as number;\n const relatedIds = new Set<string>();\n relatedIds.add(id);\n\n // Parent\n const parent = await this.documentStore.findParentChunk(library, version, id);\n if (parent) {\n relatedIds.add(parent.id as string);\n }\n\n // Preceding Siblings\n const precedingSiblings = await this.documentStore.findPrecedingSiblingChunks(\n library,\n version,\n id,\n siblingLimit,\n );\n for (const sib of precedingSiblings) {\n relatedIds.add(sib.id as string);\n }\n\n // Child Chunks\n const childChunks = await this.documentStore.findChildChunks(\n library,\n version,\n id,\n childLimit,\n );\n for (const child of childChunks) {\n relatedIds.add(child.id as string);\n }\n\n // Subsequent Siblings\n const subsequentSiblings = await this.documentStore.findSubsequentSiblingChunks(\n library,\n version,\n id,\n siblingLimit,\n );\n for (const sib of subsequentSiblings) {\n relatedIds.add(sib.id as string);\n }\n\n return { url, hitId: id, relatedIds, score };\n }\n\n /**\n * Groups related chunk info by URL, deduplicates IDs, and finds max score per URL.\n */\n private groupAndPrepareFetch(\n relatedInfos: Array<{\n url: string;\n hitId: string;\n relatedIds: Set<string>;\n score: number;\n }>,\n ): Map<string, { uniqueChunkIds: Set<string>; maxScore: number }> {\n const urlMap = new Map<string, { uniqueChunkIds: Set<string>; maxScore: number }>();\n for (const info of relatedInfos) {\n let entry = urlMap.get(info.url);\n if (!entry) {\n entry = { uniqueChunkIds: new Set(), maxScore: info.score };\n urlMap.set(info.url, entry);\n }\n for (const id of info.relatedIds) {\n entry.uniqueChunkIds.add(id);\n }\n if (info.score > entry.maxScore) {\n entry.maxScore = info.score;\n }\n }\n return urlMap;\n }\n\n /**\n * Finalizes the merged result for a URL group by fetching, sorting, and joining content.\n */\n private async finalizeResult(\n library: string,\n version: string,\n url: string,\n uniqueChunkIds: Set<string>,\n maxScore: number,\n ): Promise<StoreSearchResult> {\n const ids = Array.from(uniqueChunkIds);\n const docs = await this.documentStore.findChunksByIds(library, version, ids);\n // Already sorted by sort_order in findChunksByIds\n const content = docs.map((d) => d.pageContent).join(\"\\n\\n\");\n // TODO: Apply code block merging here if/when implemented\n return {\n url,\n content,\n score: maxScore,\n };\n }\n\n /**\n * Searches for documents and expands the context around the matches.\n * @param library The library name.\n * @param version The library version.\n * @param query The search query.\n * @param version The library version (optional, defaults to searching documents without a version).\n * @param query The search query.\n * @param limit The optional limit for the initial search results.\n * @returns An array of strings representing the aggregated content of the retrieved chunks.\n */\n async search(\n library: string,\n version: string | null | undefined,\n query: string,\n limit?: number,\n ): Promise<StoreSearchResult[]> {\n // Normalize version: null/undefined becomes empty string, then lowercase\n const normalizedVersion = (version ?? \"\").toLowerCase();\n\n const initialResults = await this.documentStore.findByContent(\n library,\n normalizedVersion,\n query,\n limit ?? 10,\n );\n\n // Step 1: Expand context for each initial hit (collect related chunk IDs)\n const relatedInfos = await Promise.all(\n initialResults.map((doc) =>\n this.getRelatedChunkIds(library, normalizedVersion, doc),\n ),\n );\n\n // Step 2: Group by URL, deduplicate, and find max score\n const urlMap = this.groupAndPrepareFetch(relatedInfos);\n\n // Step 3: For each URL group, fetch, sort, and format the merged result\n const results: StoreSearchResult[] = [];\n for (const [url, { uniqueChunkIds, maxScore }] of urlMap.entries()) {\n const result = await this.finalizeResult(\n library,\n normalizedVersion,\n url,\n uniqueChunkIds,\n maxScore,\n );\n results.push(result);\n }\n\n return results;\n }\n}\n","class StoreError extends Error {\n constructor(\n message: string,\n public readonly cause?: unknown,\n ) {\n super(cause ? `${message} caused by ${cause}` : message);\n this.name = this.constructor.name;\n\n const causeError =\n cause instanceof Error ? cause : cause ? new Error(String(cause)) : undefined;\n if (causeError?.stack) {\n this.stack = causeError.stack;\n }\n }\n}\n\nclass DimensionError extends StoreError {\n constructor(\n public readonly modelName: string,\n public readonly modelDimension: number,\n public readonly dbDimension: number,\n ) {\n super(\n `Model \"${modelName}\" produces ${modelDimension}-dimensional vectors, ` +\n `which exceeds the database's fixed dimension of ${dbDimension}. ` +\n `Please use a model with dimension ≤ ${dbDimension}.`,\n );\n }\n}\n\nclass ConnectionError extends StoreError {}\n\nclass DocumentNotFoundError extends StoreError {\n constructor(public readonly id: string) {\n super(`Document ${id} not found`);\n }\n}\n\nexport { StoreError, ConnectionError, DocumentNotFoundError, DimensionError };\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Database } from \"better-sqlite3\";\nimport { MIGRATION_MAX_RETRIES, MIGRATION_RETRY_DELAY_MS } from \"../utils/config\";\nimport { logger } from \"../utils/logger\";\nimport { getProjectRoot } from \"../utils/paths\";\nimport { StoreError } from \"./errors\";\n\n// Construct the absolute path to the migrations directory using the project root\nconst MIGRATIONS_DIR = path.join(getProjectRoot(), \"db\", \"migrations\");\nconst MIGRATIONS_TABLE = \"_schema_migrations\";\n\n/**\n * Ensures the migration tracking table exists in the database.\n * @param db The database instance.\n */\nfunction ensureMigrationsTable(db: Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (\n id TEXT PRIMARY KEY,\n applied_at DATETIME DEFAULT CURRENT_TIMESTAMP\n );\n `);\n}\n\n/**\n * Retrieves the set of already applied migration IDs (filenames) from the tracking table.\n * @param db The database instance.\n * @returns A Set containing the IDs of applied migrations.\n */\nfunction getAppliedMigrations(db: Database): Set<string> {\n const stmt = db.prepare(`SELECT id FROM ${MIGRATIONS_TABLE}`);\n const rows = stmt.all() as Array<{ id: string }>;\n return new Set(rows.map((row) => row.id));\n}\n\n/**\n * Applies pending database migrations found in the migrations directory.\n * Migrations are expected to be .sql files with sequential prefixes (e.g., 001-, 002-).\n * It tracks applied migrations in the _schema_migrations table.\n *\n * @param db The better-sqlite3 database instance.\n * @throws {StoreError} If any migration fails.\n */\nexport async function applyMigrations(db: Database): Promise<void> {\n // Apply performance optimizations for large dataset migrations\n try {\n db.pragma(\"journal_mode = OFF\");\n db.pragma(\"synchronous = OFF\");\n db.pragma(\"mmap_size = 268435456\"); // 256MB memory mapping\n db.pragma(\"cache_size = -64000\"); // 64MB cache (default is ~2MB)\n db.pragma(\"temp_store = MEMORY\"); // Store temporary data in memory\n logger.debug(\"Applied performance optimizations for migration\");\n } catch (_error) {\n logger.warn(\"⚠️ Could not apply all performance optimizations for migration\");\n }\n\n const overallTransaction = db.transaction(() => {\n logger.debug(\"Checking database migrations...\");\n ensureMigrationsTable(db);\n const appliedMigrations = getAppliedMigrations(db);\n\n if (!fs.existsSync(MIGRATIONS_DIR)) {\n throw new StoreError(\"Migrations directory not found\");\n }\n\n const migrationFiles = fs\n .readdirSync(MIGRATIONS_DIR)\n .filter((file) => file.endsWith(\".sql\"))\n .sort(); // Sort alphabetically, relying on naming convention (001-, 002-)\n\n const pendingMigrations = migrationFiles.filter(\n (filename) => !appliedMigrations.has(filename),\n );\n\n if (pendingMigrations.length > 0) {\n logger.info(`🔄 Applying ${pendingMigrations.length} database migration(s)...`);\n }\n\n let appliedCount = 0;\n for (const filename of pendingMigrations) {\n logger.debug(`Applying migration: ${filename}`);\n const filePath = path.join(MIGRATIONS_DIR, filename);\n const sql = fs.readFileSync(filePath, \"utf8\");\n\n // Execute migration and record it directly within the overall transaction\n try {\n db.exec(sql);\n const insertStmt = db.prepare(`INSERT INTO ${MIGRATIONS_TABLE} (id) VALUES (?)`);\n insertStmt.run(filename);\n logger.debug(`✅ Applied migration: ${filename}`);\n appliedCount++;\n } catch (error) {\n logger.error(`❌ Failed to apply migration: ${filename} - ${error}`);\n // Re-throw to ensure the overall transaction rolls back\n throw new StoreError(`Migration failed: ${filename}`, error);\n }\n }\n\n if (appliedCount > 0) {\n logger.info(`✅ Successfully applied ${appliedCount} migration(s)`);\n } else {\n logger.debug(\"Database schema is up to date\");\n }\n\n // Return the count of applied migrations so we know if VACUUM is needed\n return appliedCount;\n });\n\n let retries = 0;\n let appliedMigrationsCount = 0;\n\n while (true) {\n try {\n // Start a single IMMEDIATE transaction for the entire migration process\n appliedMigrationsCount = overallTransaction.immediate(); // Execute the encompassing transaction\n logger.debug(\"Database migrations completed successfully\");\n\n // Only run VACUUM if migrations were actually applied\n if (appliedMigrationsCount > 0) {\n try {\n logger.debug(\n `Running VACUUM after applying ${appliedMigrationsCount} migration(s)...`,\n );\n db.exec(\"VACUUM\");\n logger.debug(\"Database vacuum completed successfully\");\n } catch (error) {\n logger.warn(`⚠️ Could not vacuum database after migrations: ${error}`);\n // Don't fail the migration process if vacuum fails\n }\n } else {\n logger.debug(\"Skipping VACUUM - no migrations were applied\");\n }\n\n break; // Success\n } catch (error) {\n // biome-ignore lint/suspicious/noExplicitAny: error can be any\n if ((error as any)?.code === \"SQLITE_BUSY\" && retries < MIGRATION_MAX_RETRIES) {\n retries++;\n logger.warn(\n `⚠️ Migrations busy (SQLITE_BUSY), retrying attempt ${retries}/${MIGRATION_MAX_RETRIES} in ${MIGRATION_RETRY_DELAY_MS}ms...`,\n );\n await new Promise((resolve) => setTimeout(resolve, MIGRATION_RETRY_DELAY_MS));\n } else {\n // biome-ignore lint/suspicious/noExplicitAny: error can be any\n if ((error as any)?.code === \"SQLITE_BUSY\") {\n logger.error(\n `❌ Migrations still busy after ${MIGRATION_MAX_RETRIES} retries. Giving up: ${error}`,\n );\n }\n // Ensure StoreError is thrown for consistent handling\n if (error instanceof StoreError) {\n throw error;\n }\n throw new StoreError(\"Failed during migration process\", error);\n }\n }\n }\n\n // Configure production-ready settings after migrations\n try {\n // Enable WAL mode for better concurrency (allows readers while writing)\n db.pragma(\"journal_mode = WAL\");\n\n // Configure WAL autocheckpoint to prevent unbounded growth\n db.pragma(\"wal_autocheckpoint = 1000\"); // Checkpoint every 1000 pages (~4MB)\n\n // Set busy timeout for better handling of concurrent access\n db.pragma(\"busy_timeout = 30000\"); // 30 seconds\n\n // Enable foreign key constraints for data integrity\n db.pragma(\"foreign_keys = ON\");\n\n // Set synchronous to NORMAL for good balance of safety and performance\n db.pragma(\"synchronous = NORMAL\");\n\n logger.debug(\n \"Applied production database configuration (WAL mode, autocheckpoint, foreign keys, busy timeout)\",\n );\n } catch (_error) {\n logger.warn(\"⚠️ Could not apply all production database settings\");\n }\n}\n","import type { Document } from \"@langchain/core/documents\";\nimport type { Embeddings } from \"@langchain/core/embeddings\";\nimport Database, { type Database as DatabaseType } from \"better-sqlite3\";\nimport semver from \"semver\";\nimport * as sqliteVec from \"sqlite-vec\";\nimport type { ScraperOptions } from \"../scraper/types\";\nimport type { DocumentMetadata } from \"../types\";\nimport { EMBEDDING_BATCH_SIZE } from \"../utils/config\";\nimport { logger } from \"../utils/logger\";\nimport { applyMigrations } from \"./applyMigrations\";\nimport { ConnectionError, DimensionError, StoreError } from \"./errors\";\nimport {\n type DbDocument,\n type DbQueryResult,\n type DbVersion,\n type DbVersionWithLibrary,\n denormalizeVersionName,\n type LibraryVersionDetails,\n mapDbDocumentToDocument,\n normalizeVersionName,\n VECTOR_DIMENSION,\n type VersionScraperOptions,\n type VersionStatus,\n} from \"./types\";\n\ninterface RawSearchResult extends DbDocument {\n vec_score?: number;\n fts_score?: number;\n}\n\ninterface RankedResult extends RawSearchResult {\n vec_rank?: number;\n fts_rank?: number;\n rrf_score: number;\n}\n\n/**\n * Manages document storage and retrieval using SQLite with vector and full-text search capabilities.\n * Provides direct access to SQLite with prepared statements to store and query document\n * embeddings along with their metadata. Supports versioned storage of documents for different\n * libraries, enabling version-specific document retrieval and searches.\n */\nexport class DocumentStore {\n private readonly db: DatabaseType;\n private embeddings!: Embeddings;\n private readonly dbDimension: number = VECTOR_DIMENSION;\n private modelDimension!: number;\n private statements!: {\n getById: Database.Statement<[bigint]>;\n insertDocument: Database.Statement<\n [bigint, bigint, string, string, string, number, string]\n >;\n insertEmbedding: Database.Statement<[bigint, bigint, bigint, string]>;\n deleteDocuments: Database.Statement<[string, string, string]>;\n deleteDocumentsByUrl: Database.Statement<[string, string, string, string]>;\n queryVersions: Database.Statement<[string]>;\n checkExists: Database.Statement<[string, string]>;\n queryLibraryVersions: Database.Statement<[]>;\n getChildChunks: Database.Statement<\n [string, string, string, number, string, bigint, number]\n >;\n getPrecedingSiblings: Database.Statement<\n [string, string, string, bigint, string, number]\n >;\n getSubsequentSiblings: Database.Statement<\n [string, string, string, bigint, string, number]\n >;\n getParentChunk: Database.Statement<[string, string, string, string, bigint]>;\n insertLibrary: Database.Statement<[string]>;\n getLibraryIdByName: Database.Statement<[string]>;\n // New version-related statements\n insertVersion: Database.Statement<[number, string | null]>;\n resolveVersionId: Database.Statement<[number, string | null]>;\n getVersionById: Database.Statement<[number]>;\n queryVersionsByLibraryId: Database.Statement<[number]>;\n // Status tracking statements\n updateVersionStatus: Database.Statement<[string, string | null, number]>;\n updateVersionProgress: Database.Statement<[number, number, number]>;\n getVersionsByStatus: Database.Statement<string[]>;\n getRunningVersions: Database.Statement<[]>;\n getActiveVersions: Database.Statement<[]>;\n // Scraper options statements\n updateVersionScraperOptions: Database.Statement<[string, string, number]>;\n getVersionWithOptions: Database.Statement<[number]>;\n getVersionsBySourceUrl: Database.Statement<[string]>;\n };\n\n /**\n * Calculates Reciprocal Rank Fusion score for a result\n */\n private calculateRRF(vecRank?: number, ftsRank?: number, k = 60): number {\n let rrf = 0;\n if (vecRank !== undefined) {\n rrf += 1 / (k + vecRank);\n }\n if (ftsRank !== undefined) {\n rrf += 1 / (k + ftsRank);\n }\n return rrf;\n }\n\n /**\n * Assigns ranks to search results based on their scores\n */\n private assignRanks(results: RawSearchResult[]): RankedResult[] {\n // Create maps to store ranks\n const vecRanks = new Map<number, number>();\n const ftsRanks = new Map<number, number>();\n\n // Sort by vector scores and assign ranks\n results\n .filter((r) => r.vec_score !== undefined)\n .sort((a, b) => (b.vec_score ?? 0) - (a.vec_score ?? 0))\n .forEach((result, index) => {\n vecRanks.set(Number(result.id), index + 1);\n });\n\n // Sort by BM25 scores and assign ranks\n results\n .filter((r) => r.fts_score !== undefined)\n .sort((a, b) => (b.fts_score ?? 0) - (a.fts_score ?? 0))\n .forEach((result, index) => {\n ftsRanks.set(Number(result.id), index + 1);\n });\n\n // Combine results with ranks and calculate RRF\n return results.map((result) => ({\n ...result,\n vec_rank: vecRanks.get(Number(result.id)),\n fts_rank: ftsRanks.get(Number(result.id)),\n rrf_score: this.calculateRRF(\n vecRanks.get(Number(result.id)),\n ftsRanks.get(Number(result.id)),\n ),\n }));\n }\n\n constructor(dbPath: string) {\n if (!dbPath) {\n throw new StoreError(\"Missing required database path\");\n }\n\n // Only establish database connection in constructor\n this.db = new Database(dbPath);\n }\n\n /**\n * Sets up prepared statements for database queries\n */\n private prepareStatements(): void {\n const statements = {\n getById: this.db.prepare<[bigint]>(\"SELECT * FROM documents WHERE id = ?\"),\n insertDocument: this.db.prepare<\n [bigint, bigint, string, string, string, number, string]\n >(\n \"INSERT INTO documents (library_id, version_id, url, content, metadata, sort_order, indexed_at) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n ),\n insertEmbedding: this.db.prepare<[bigint, bigint, bigint, string]>(\n \"INSERT INTO documents_vec (rowid, library_id, version_id, embedding) VALUES (?, ?, ?, ?)\",\n ),\n insertLibrary: this.db.prepare<[string]>(\n \"INSERT INTO libraries (name) VALUES (?) ON CONFLICT(name) DO NOTHING\",\n ),\n getLibraryIdByName: this.db.prepare<[string]>(\n \"SELECT id FROM libraries WHERE name = ?\",\n ),\n // New version-related statements\n insertVersion: this.db.prepare<[number, string | null]>(\n \"INSERT INTO versions (library_id, name, status) VALUES (?, ?, 'not_indexed') ON CONFLICT(library_id, name) DO NOTHING\",\n ),\n resolveVersionId: this.db.prepare<[number, string | null]>(\n \"SELECT id FROM versions WHERE library_id = ? AND name IS ?\",\n ),\n getVersionById: this.db.prepare<[number]>(\"SELECT * FROM versions WHERE id = ?\"),\n queryVersionsByLibraryId: this.db.prepare<[number]>(\n \"SELECT * FROM versions WHERE library_id = ? ORDER BY name\",\n ),\n deleteLibraryDocuments: this.db.prepare<[string, string, string]>(\n `DELETE FROM documents\n WHERE library_id = (SELECT id FROM libraries WHERE name = ?)\n AND version_id = (\n SELECT v.id FROM versions v \n WHERE v.library_id = (SELECT id FROM libraries WHERE name = ?)\n AND COALESCE(v.name, '') = COALESCE(?, '')\n )`,\n ),\n deleteDocuments: this.db.prepare<[string, string, string]>(\n `DELETE FROM documents\n WHERE library_id = (SELECT id FROM libraries WHERE name = ?)\n AND version_id = (\n SELECT v.id FROM versions v \n WHERE v.library_id = (SELECT id FROM libraries WHERE name = ?)\n AND COALESCE(v.name, '') = COALESCE(?, '')\n )`,\n ),\n deleteDocumentsByUrl: this.db.prepare<[string, string, string, string]>(\n `DELETE FROM documents\n WHERE url = ?\n AND library_id = (SELECT id FROM libraries WHERE name = ?)\n AND version_id = (\n SELECT v.id FROM versions v \n WHERE v.library_id = (SELECT id FROM libraries WHERE name = ?)\n AND COALESCE(v.name, '') = COALESCE(?, '')\n )`,\n ),\n getDocumentBySort: this.db.prepare<[string, string]>(\n `SELECT d.id\n FROM documents d\n JOIN versions v ON d.version_id = v.id\n JOIN libraries l ON v.library_id = l.id\n WHERE l.name = ?\n AND COALESCE(v.name, '') = COALESCE(?, '')\n LIMIT 1`,\n ),\n queryVersions: this.db.prepare<[string]>(\n `SELECT DISTINCT v.name\n FROM versions v\n JOIN libraries l ON v.library_id = l.id\n WHERE l.name = ?\n ORDER BY v.name`,\n ),\n checkExists: this.db.prepare<[string, string]>(\n `SELECT d.id FROM documents d\n JOIN versions v ON d.version_id = v.id\n JOIN libraries l ON v.library_id = l.id\n WHERE l.name = ?\n AND COALESCE(v.name, '') = COALESCE(?, '')\n LIMIT 1`,\n ),\n queryLibraryVersions: this.db.prepare<[]>(\n `SELECT\n l.name as library,\n v.name as version,\n COUNT(*) as documentCount,\n COUNT(DISTINCT d.url) as uniqueUrlCount,\n MIN(d.indexed_at) as indexedAt\n FROM documents d\n JOIN versions v ON d.version_id = v.id\n JOIN libraries l ON v.library_id = l.id\n GROUP BY l.name, v.name\n ORDER BY l.name, v.name`,\n ),\n getChildChunks: this.db.prepare<\n [string, string, string, number, string, bigint, number]\n >(`\n SELECT d.* FROM documents d\n JOIN versions v ON d.version_id = v.id\n JOIN libraries l ON v.library_id = l.id\n WHERE l.name = ?\n AND COALESCE(v.name, '') = COALESCE(?, '')\n AND d.url = ?\n AND json_array_length(json_extract(d.metadata, '$.path')) = ?\n AND json_extract(d.metadata, '$.path') LIKE ? || '%'\n AND d.sort_order > (SELECT sort_order FROM documents WHERE id = ?)\n ORDER BY d.sort_order\n LIMIT ?\n `),\n getPrecedingSiblings: this.db.prepare<\n [string, string, string, bigint, string, number]\n >(`\n SELECT d.* FROM documents d\n JOIN versions v ON d.version_id = v.id\n JOIN libraries l ON v.library_id = l.id\n WHERE l.name = ?\n AND COALESCE(v.name, '') = COALESCE(?, '')\n AND d.url = ?\n AND d.sort_order < (SELECT sort_order FROM documents WHERE id = ?)\n AND json_extract(d.metadata, '$.path') = ?\n ORDER BY d.sort_order DESC\n LIMIT ?\n `),\n getSubsequentSiblings: this.db.prepare<\n [string, string, string, bigint, string, number]\n >(`\n SELECT d.* FROM documents d\n JOIN versions v ON d.version_id = v.id\n JOIN libraries l ON v.library_id = l.id\n WHERE l.name = ?\n AND COALESCE(v.name, '') = COALESCE(?, '')\n AND d.url = ?\n AND d.sort_order > (SELECT sort_order FROM documents WHERE id = ?)\n AND json_extract(d.metadata, '$.path') = ?\n ORDER BY d.sort_order\n LIMIT ?\n `),\n getParentChunk: this.db.prepare<[string, string, string, string, bigint]>(`\n SELECT d.* FROM documents d\n JOIN versions v ON d.version_id = v.id\n JOIN libraries l ON v.library_id = l.id\n WHERE l.name = ?\n AND COALESCE(v.name, '') = COALESCE(?, '')\n AND d.url = ?\n AND json_extract(d.metadata, '$.path') = ?\n AND d.sort_order < (SELECT sort_order FROM documents WHERE id = ?)\n ORDER BY d.sort_order DESC\n LIMIT 1\n `),\n // Status tracking statements\n updateVersionStatus: this.db.prepare<[string, string | null, number]>(\n \"UPDATE versions SET status = ?, error_message = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?\",\n ),\n updateVersionProgress: this.db.prepare<[number, number, number]>(\n \"UPDATE versions SET progress_pages = ?, progress_max_pages = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?\",\n ),\n getVersionsByStatus: this.db.prepare<[string]>(\n \"SELECT v.*, l.name as library_name FROM versions v JOIN libraries l ON v.library_id = l.id WHERE v.status IN (SELECT value FROM json_each(?))\",\n ),\n getRunningVersions: this.db.prepare<[]>(\n \"SELECT v.*, l.name as library_name FROM versions v JOIN libraries l ON v.library_id = l.id WHERE v.status = 'running' ORDER BY v.started_at\",\n ),\n getActiveVersions: this.db.prepare<[]>(\n \"SELECT v.*, l.name as library_name FROM versions v JOIN libraries l ON v.library_id = l.id WHERE v.status IN ('queued', 'running', 'updating') ORDER BY v.created_at\",\n ),\n // Scraper options statements\n updateVersionScraperOptions: this.db.prepare<[string, string, number]>(\n \"UPDATE versions SET source_url = ?, scraper_options = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?\",\n ),\n getVersionWithOptions: this.db.prepare<[number]>(\n \"SELECT * FROM versions WHERE id = ?\",\n ),\n getVersionsBySourceUrl: this.db.prepare<[string]>(\n \"SELECT v.*, l.name as library_name FROM versions v JOIN libraries l ON v.library_id = l.id WHERE v.source_url = ? ORDER BY v.created_at DESC\",\n ),\n };\n this.statements = statements;\n }\n\n /**\n * Pads a vector to the fixed database dimension by appending zeros.\n * Throws an error if the input vector is longer than the database dimension.\n */\n private padVector(vector: number[]): number[] {\n if (vector.length > this.dbDimension) {\n throw new Error(\n `Vector dimension ${vector.length} exceeds database dimension ${this.dbDimension}`,\n );\n }\n if (vector.length === this.dbDimension) {\n return vector;\n }\n return [...vector, ...new Array(this.dbDimension - vector.length).fill(0)];\n }\n\n /**\n * Initializes embeddings client using environment variables for configuration.\n *\n * The embedding model is configured using DOCS_MCP_EMBEDDING_MODEL environment variable.\n * Format: \"provider:model_name\" (e.g., \"google:text-embedding-004\") or just \"model_name\"\n * for OpenAI (default).\n *\n * Supported providers and their required environment variables:\n * - openai: OPENAI_API_KEY (and optionally OPENAI_API_BASE, OPENAI_ORG_ID)\n * - google: GOOGLE_APPLICATION_CREDENTIALS (path to service account JSON)\n * - aws: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION (or BEDROCK_AWS_REGION)\n * - microsoft: Azure OpenAI credentials (AZURE_OPENAI_API_*)\n */\n private async initializeEmbeddings(): Promise<void> {\n const modelSpec = process.env.DOCS_MCP_EMBEDDING_MODEL || \"text-embedding-3-small\";\n\n // Import dynamically to avoid circular dependencies\n const { createEmbeddingModel } = await import(\"./embeddings/EmbeddingFactory\");\n this.embeddings = createEmbeddingModel(modelSpec);\n\n // Determine the model's actual dimension by embedding a test string\n const testVector = await this.embeddings.embedQuery(\"test\");\n this.modelDimension = testVector.length;\n\n if (this.modelDimension > this.dbDimension) {\n throw new DimensionError(modelSpec, this.modelDimension, this.dbDimension);\n }\n }\n\n /**\n * Escapes a query string for use with SQLite FTS5 MATCH operator.\n * Wraps the query in double quotes and escapes internal double quotes.\n */\n private escapeFtsQuery(query: string): string {\n // Escape internal double quotes by doubling them\n const escapedQuotes = query.replace(/\"/g, '\"\"');\n // Wrap the entire string in double quotes\n return `\"${escapedQuotes}\"`;\n }\n\n /**\n * Initializes database connection and ensures readiness\n */\n async initialize(): Promise<void> {\n try {\n // 1. Load extensions first (moved before migrations)\n sqliteVec.load(this.db);\n\n // 2. Apply migrations (after extensions are loaded)\n applyMigrations(this.db);\n\n // 3. Initialize prepared statements\n this.prepareStatements();\n\n // 4. Initialize embeddings client (await to catch errors)\n await this.initializeEmbeddings();\n } catch (error) {\n // Re-throw StoreError directly, wrap others in ConnectionError\n if (error instanceof StoreError) {\n throw error;\n }\n throw new ConnectionError(\"Failed to initialize database connection\", error);\n }\n }\n\n /**\n * Gracefully closes database connections\n */\n async shutdown(): Promise<void> {\n this.db.close();\n }\n\n /**\n * Resolves a library name and version string to library_id and version_id.\n * Creates library and version records if they don't exist.\n */\n async resolveLibraryAndVersionIds(\n library: string,\n version: string,\n ): Promise<{ libraryId: number; versionId: number }> {\n const normalizedLibrary = library.toLowerCase();\n const normalizedVersion = denormalizeVersionName(version.toLowerCase());\n\n // Insert or get library_id\n this.statements.insertLibrary.run(normalizedLibrary);\n const libraryIdRow = this.statements.getLibraryIdByName.get(normalizedLibrary) as\n | { id: number }\n | undefined;\n if (!libraryIdRow || typeof libraryIdRow.id !== \"number\") {\n throw new StoreError(`Failed to resolve library_id for library: ${library}`);\n }\n const libraryId = libraryIdRow.id;\n\n // Insert or get version_id\n this.statements.insertVersion.run(libraryId, normalizedVersion);\n const versionIdRow = this.statements.resolveVersionId.get(\n libraryId,\n normalizedVersion,\n ) as { id: number } | undefined;\n if (!versionIdRow || typeof versionIdRow.id !== \"number\") {\n throw new StoreError(\n `Failed to resolve version_id for library: ${library}, version: ${version}`,\n );\n }\n\n return { libraryId, versionId: versionIdRow.id };\n }\n\n /**\n * Retrieves all unique versions for a specific library\n */\n async queryUniqueVersions(library: string): Promise<string[]> {\n try {\n const rows = this.statements.queryVersions.all(library.toLowerCase()) as Array<{\n name: string | null;\n }>;\n return rows.map((row) => normalizeVersionName(row.name));\n } catch (error) {\n throw new ConnectionError(\"Failed to query versions\", error);\n }\n }\n\n /**\n * Updates the status of a version record in the database.\n * @param versionId The version ID to update\n * @param status The new status to set\n * @param errorMessage Optional error message for failed statuses\n */\n async updateVersionStatus(\n versionId: number,\n status: VersionStatus,\n errorMessage?: string,\n ): Promise<void> {\n try {\n this.statements.updateVersionStatus.run(status, errorMessage ?? null, versionId);\n } catch (error) {\n throw new StoreError(`Failed to update version status: ${error}`);\n }\n }\n\n /**\n * Updates the progress counters for a version being indexed.\n * @param versionId The version ID to update\n * @param pages Current number of pages processed\n * @param maxPages Total number of pages to process\n */\n async updateVersionProgress(\n versionId: number,\n pages: number,\n maxPages: number,\n ): Promise<void> {\n try {\n this.statements.updateVersionProgress.run(pages, maxPages, versionId);\n } catch (error) {\n throw new StoreError(`Failed to update version progress: ${error}`);\n }\n }\n\n /**\n * Retrieves versions by their status.\n * @param statuses Array of statuses to filter by\n * @returns Array of version records matching the statuses\n */\n async getVersionsByStatus(statuses: VersionStatus[]): Promise<DbVersionWithLibrary[]> {\n try {\n const statusJson = JSON.stringify(statuses);\n const rows = this.statements.getVersionsByStatus.all(\n statusJson,\n ) as DbVersionWithLibrary[];\n return rows;\n } catch (error) {\n throw new StoreError(`Failed to get versions by status: ${error}`);\n }\n }\n\n /**\n * Retrieves all versions currently in RUNNING status.\n * @returns Array of running version records with library names\n */\n async getRunningVersions(): Promise<DbVersionWithLibrary[]> {\n try {\n const rows = this.statements.getRunningVersions.all() as DbVersionWithLibrary[];\n return rows;\n } catch (error) {\n throw new StoreError(`Failed to get running versions: ${error}`);\n }\n }\n\n /**\n * Retrieves all versions in active states (queued, running, updating).\n * @returns Array of active version records with library names\n */\n async getActiveVersions(): Promise<DbVersionWithLibrary[]> {\n try {\n const rows = this.statements.getActiveVersions.all() as DbVersionWithLibrary[];\n return rows;\n } catch (error) {\n throw new StoreError(`Failed to get active versions: ${error}`);\n }\n }\n\n /**\n * Stores scraper options for a version to enable reproducible indexing.\n * @param versionId The version ID to update\n * @param options Complete scraper options used for indexing\n */\n async storeScraperOptions(versionId: number, options: ScraperOptions): Promise<void> {\n try {\n // biome-ignore lint/correctness/noUnusedVariables: Extract source URL and exclude runtime-only fields using destructuring\n const { url: source_url, library, version, signal, ...scraper_options } = options;\n\n const optionsJson = JSON.stringify(scraper_options);\n this.statements.updateVersionScraperOptions.run(source_url, optionsJson, versionId);\n } catch (error) {\n throw new StoreError(`Failed to store scraper options: ${error}`);\n }\n } /**\n * Retrieves stored scraper options for a version.\n * @param versionId The version ID to query\n * @returns Stored scraper options or null if none stored\n */\n async getVersionScraperOptions(\n versionId: number,\n ): Promise<VersionScraperOptions | null> {\n try {\n const row = this.statements.getVersionWithOptions.get(versionId) as\n | DbVersion\n | undefined;\n\n if (!row?.scraper_options) {\n return null;\n }\n\n return JSON.parse(row.scraper_options) as VersionScraperOptions;\n } catch (error) {\n throw new StoreError(`Failed to get version scraper options: ${error}`);\n }\n }\n\n /**\n * Retrieves a version record with all stored options.\n * @param versionId The version ID to query\n * @returns Complete version record or null if not found\n */\n async getVersionWithStoredOptions(versionId: number): Promise<DbVersion | null> {\n try {\n const row = this.statements.getVersionWithOptions.get(versionId) as\n | DbVersion\n | undefined;\n return row || null;\n } catch (error) {\n throw new StoreError(`Failed to get version with stored options: ${error}`);\n }\n }\n\n /**\n * Finds versions that were indexed from the same source URL.\n * Useful for finding similar configurations or detecting duplicates.\n * @param url Source URL to search for\n * @returns Array of versions with the same source URL\n */\n async findVersionsBySourceUrl(url: string): Promise<DbVersionWithLibrary[]> {\n try {\n const rows = this.statements.getVersionsBySourceUrl.all(\n url,\n ) as DbVersionWithLibrary[];\n return rows;\n } catch (error) {\n throw new StoreError(`Failed to find versions by source URL: ${error}`);\n }\n }\n\n /**\n * Verifies existence of documents for a specific library version\n */\n async checkDocumentExists(library: string, version: string): Promise<boolean> {\n try {\n const normalizedVersion = version.toLowerCase();\n const result = this.statements.checkExists.get(\n library.toLowerCase(),\n normalizedVersion,\n );\n return result !== undefined;\n } catch (error) {\n throw new ConnectionError(\"Failed to check document existence\", error);\n }\n }\n\n /**\n * Retrieves a mapping of all libraries to their available versions with details.\n */\n async queryLibraryVersions(): Promise<Map<string, LibraryVersionDetails[]>> {\n try {\n // Define the expected row structure from the GROUP BY query\n interface LibraryVersionRow {\n library: string;\n version: string;\n documentCount: number;\n uniqueUrlCount: number;\n indexedAt: string | null; // SQLite MIN might return string or null\n }\n\n const rows = this.statements.queryLibraryVersions.all() as LibraryVersionRow[];\n const libraryMap = new Map<string, LibraryVersionDetails[]>();\n\n for (const row of rows) {\n // Process all rows, including those where version is \"\" (unversioned)\n const library = row.library;\n if (!libraryMap.has(library)) {\n libraryMap.set(library, []);\n }\n\n // Format indexedAt to ISO string if available\n const indexedAtISO = row.indexedAt ? new Date(row.indexedAt).toISOString() : null;\n\n libraryMap.get(library)?.push({\n version: row.version,\n documentCount: row.documentCount,\n uniqueUrlCount: row.uniqueUrlCount,\n indexedAt: indexedAtISO,\n });\n }\n\n // Sort versions within each library: unversioned first, then semantically\n for (const versions of libraryMap.values()) {\n versions.sort((a, b) => {\n if (a.version === \"\" && b.version !== \"\") {\n return -1; // a (unversioned) comes first\n }\n if (a.version !== \"\" && b.version === \"\") {\n return 1; // b (unversioned) comes first\n }\n if (a.version === \"\" && b.version === \"\") {\n return 0; // Should not happen with GROUP BY, but handle anyway\n }\n // Both are non-empty, use semver compare with fallback to string compare\n try {\n return semver.compare(a.version, b.version);\n } catch (_error) {\n // Fallback to lexicographic comparison if semver parsing fails\n return a.version.localeCompare(b.version);\n }\n });\n }\n\n return libraryMap;\n } catch (error) {\n throw new ConnectionError(\"Failed to query library versions\", error);\n }\n }\n\n /**\n * Stores documents with library and version metadata, generating embeddings\n * for vector similarity search. Automatically removes any existing documents\n * for the same URLs before adding new ones to prevent UNIQUE constraint violations.\n */\n async addDocuments(\n library: string,\n version: string,\n documents: Document[],\n ): Promise<void> {\n try {\n if (documents.length === 0) {\n return;\n }\n\n // Extract unique URLs from the documents being added\n const urls = new Set<string>();\n for (const doc of documents) {\n const url = doc.metadata.url as string;\n if (!url || typeof url !== \"string\" || !url.trim()) {\n throw new StoreError(\"Document metadata must include a valid URL\");\n }\n urls.add(url);\n }\n\n // Generate embeddings in batch\n const texts = documents.map((doc) => {\n const header = `<title>${doc.metadata.title}</title>\\n<url>${doc.metadata.url}</url>\\n<path>${doc.metadata.path.join(\" / \")}</path>\\n`;\n return `${header}${doc.pageContent}`;\n });\n\n // Batch embedding creation to avoid token limit errors\n const rawEmbeddings: number[][] = [];\n for (let i = 0; i < texts.length; i += EMBEDDING_BATCH_SIZE) {\n const batchTexts = texts.slice(i, i + EMBEDDING_BATCH_SIZE);\n const batchEmbeddings = await this.embeddings.embedDocuments(batchTexts);\n rawEmbeddings.push(...batchEmbeddings);\n }\n const paddedEmbeddings = rawEmbeddings.map((vector) => this.padVector(vector));\n\n // Resolve library and version IDs (creates them if they don't exist)\n const { libraryId, versionId } = await this.resolveLibraryAndVersionIds(\n library,\n version,\n );\n\n // Delete existing documents for these URLs to prevent UNIQUE constraint violations\n // This must happen AFTER resolveLibraryAndVersionIds to ensure the library/version exist\n for (const url of urls) {\n const deletedCount = await this.deleteDocumentsByUrl(library, version, url);\n if (deletedCount > 0) {\n logger.debug(`🗑️ Deleted ${deletedCount} existing documents for URL: ${url}`);\n }\n }\n\n // Insert documents in a transaction\n const transaction = this.db.transaction((docs: typeof documents) => {\n for (let i = 0; i < docs.length; i++) {\n const doc = docs[i];\n const url = doc.metadata.url as string;\n\n // Insert into main documents table using foreign key IDs\n const result = this.statements.insertDocument.run(\n BigInt(libraryId),\n BigInt(versionId),\n url,\n doc.pageContent,\n JSON.stringify(doc.metadata),\n i,\n new Date().toISOString(), // Pass current timestamp for indexed_at\n );\n const rowId = result.lastInsertRowid;\n\n // Insert into vector table using foreign key IDs\n this.statements.insertEmbedding.run(\n BigInt(rowId),\n BigInt(libraryId),\n BigInt(versionId),\n JSON.stringify(paddedEmbeddings[i]),\n );\n }\n });\n\n transaction(documents);\n } catch (error) {\n throw new ConnectionError(\"Failed to add documents to store\", error);\n }\n }\n\n /**\n * Removes documents matching specified library and version\n * @returns Number of documents deleted\n */\n async deleteDocuments(library: string, version: string): Promise<number> {\n try {\n const normalizedVersion = version.toLowerCase();\n const result = this.statements.deleteDocuments.run(\n library.toLowerCase(),\n library.toLowerCase(), // library name appears twice in the query\n normalizedVersion,\n );\n return result.changes;\n } catch (error) {\n throw new ConnectionError(\"Failed to delete documents\", error);\n }\n }\n\n /**\n * Removes documents for a specific URL within a library and version\n * @returns Number of documents deleted\n */\n async deleteDocumentsByUrl(\n library: string,\n version: string,\n url: string,\n ): Promise<number> {\n try {\n const normalizedVersion = version.toLowerCase();\n const result = this.statements.deleteDocumentsByUrl.run(\n url,\n library.toLowerCase(),\n library.toLowerCase(), // library name appears twice in the query\n normalizedVersion,\n );\n return result.changes;\n } catch (error) {\n throw new ConnectionError(\"Failed to delete documents by URL\", error);\n }\n }\n\n /**\n * Retrieves a document by its ID.\n * @param id The ID of the document.\n * @returns The document, or null if not found.\n */\n async getById(id: string): Promise<Document | null> {\n try {\n const row = this.statements.getById.get(BigInt(id)) as DbQueryResult<DbDocument>;\n if (!row) {\n return null;\n }\n\n return mapDbDocumentToDocument(row);\n } catch (error) {\n throw new ConnectionError(`Failed to get document by ID ${id}`, error);\n }\n }\n\n /**\n * Finds documents matching a text query using hybrid search.\n * Combines vector similarity search with full-text search using Reciprocal Rank Fusion.\n */\n async findByContent(\n library: string,\n version: string,\n query: string,\n limit: number,\n ): Promise<Document[]> {\n try {\n const rawEmbedding = await this.embeddings.embedQuery(query);\n const embedding = this.padVector(rawEmbedding);\n const ftsQuery = this.escapeFtsQuery(query); // Escape the query for FTS\n const normalizedVersion = version.toLowerCase();\n\n const stmt = this.db.prepare(`\n WITH vec_distances AS (\n SELECT\n dv.rowid as id,\n dv.distance as vec_distance\n FROM documents_vec dv\n JOIN versions v ON dv.version_id = v.id\n JOIN libraries l ON v.library_id = l.id\n WHERE l.name = ?\n AND COALESCE(v.name, '') = COALESCE(?, '')\n AND dv.embedding MATCH ?\n AND dv.k = ?\n ORDER BY dv.distance\n ),\n fts_scores AS (\n SELECT\n f.rowid as id,\n bm25(documents_fts, 10.0, 1.0, 5.0, 1.0) as fts_score\n FROM documents_fts f\n JOIN documents d ON f.rowid = d.id\n JOIN versions v ON d.version_id = v.id\n JOIN libraries l ON v.library_id = l.id\n WHERE l.name = ?\n AND COALESCE(v.name, '') = COALESCE(?, '')\n AND documents_fts MATCH ?\n ORDER BY fts_score\n LIMIT ?\n )\n SELECT\n d.id,\n d.content,\n d.metadata,\n COALESCE(1 / (1 + v.vec_distance), 0) as vec_score,\n COALESCE(-MIN(f.fts_score, 0), 0) as fts_score\n FROM documents d\n LEFT JOIN vec_distances v ON d.id = v.id\n LEFT JOIN fts_scores f ON d.id = f.id\n WHERE v.id IS NOT NULL OR f.id IS NOT NULL\n `);\n\n const rawResults = stmt.all(\n library.toLowerCase(),\n normalizedVersion,\n JSON.stringify(embedding),\n limit,\n library.toLowerCase(),\n normalizedVersion,\n ftsQuery, // Use the escaped query\n limit,\n ) as RawSearchResult[];\n\n // Apply RRF ranking\n const rankedResults = this.assignRanks(rawResults);\n\n // Sort by RRF score and take top results\n const topResults = rankedResults\n .sort((a, b) => b.rrf_score - a.rrf_score)\n .slice(0, limit);\n\n return topResults.map((row) => ({\n ...mapDbDocumentToDocument(row),\n metadata: {\n ...JSON.parse(row.metadata),\n id: row.id,\n score: row.rrf_score,\n vec_rank: row.vec_rank,\n fts_rank: row.fts_rank,\n },\n }));\n } catch (error) {\n throw new ConnectionError(\n `Failed to find documents by content with query \"${query}\"`,\n error,\n );\n }\n }\n\n /**\n * Finds child chunks of a given document based on path hierarchy.\n */\n async findChildChunks(\n library: string,\n version: string,\n id: string,\n limit: number,\n ): Promise<Document[]> {\n try {\n const parent = await this.getById(id);\n if (!parent) {\n return [];\n }\n\n const parentPath = (parent.metadata as DocumentMetadata).path ?? [];\n const parentUrl = (parent.metadata as DocumentMetadata).url;\n const normalizedVersion = version.toLowerCase();\n\n const result = this.statements.getChildChunks.all(\n library.toLowerCase(),\n normalizedVersion,\n parentUrl,\n parentPath.length + 1,\n JSON.stringify(parentPath),\n BigInt(id),\n limit,\n ) as Array<DbDocument>;\n\n return result.map((row) => mapDbDocumentToDocument(row));\n } catch (error) {\n throw new ConnectionError(`Failed to find child chunks for ID ${id}`, error);\n }\n }\n\n /**\n * Finds preceding sibling chunks of a given document.\n */\n async findPrecedingSiblingChunks(\n library: string,\n version: string,\n id: string,\n limit: number,\n ): Promise<Document[]> {\n try {\n const reference = await this.getById(id);\n if (!reference) {\n return [];\n }\n\n const refMetadata = reference.metadata as DocumentMetadata;\n const normalizedVersion = version.toLowerCase();\n\n const result = this.statements.getPrecedingSiblings.all(\n library.toLowerCase(),\n normalizedVersion,\n refMetadata.url,\n BigInt(id),\n JSON.stringify(refMetadata.path),\n limit,\n ) as Array<DbDocument>;\n\n return result.reverse().map((row) => mapDbDocumentToDocument(row));\n } catch (error) {\n throw new ConnectionError(\n `Failed to find preceding sibling chunks for ID ${id}`,\n error,\n );\n }\n }\n\n /**\n * Finds subsequent sibling chunks of a given document.\n */\n async findSubsequentSiblingChunks(\n library: string,\n version: string,\n id: string,\n limit: number,\n ): Promise<Document[]> {\n try {\n const reference = await this.getById(id);\n if (!reference) {\n return [];\n }\n\n const refMetadata = reference.metadata;\n const normalizedVersion = version.toLowerCase();\n\n const result = this.statements.getSubsequentSiblings.all(\n library.toLowerCase(),\n normalizedVersion,\n refMetadata.url,\n BigInt(id),\n JSON.stringify(refMetadata.path),\n limit,\n ) as Array<DbDocument>;\n\n return result.map((row) => mapDbDocumentToDocument(row));\n } catch (error) {\n throw new ConnectionError(\n `Failed to find subsequent sibling chunks for ID ${id}`,\n error,\n );\n }\n }\n\n /**\n * Finds the parent chunk of a given document.\n */\n async findParentChunk(\n library: string,\n version: string,\n id: string,\n ): Promise<Document | null> {\n try {\n const child = await this.getById(id);\n if (!child) {\n return null;\n }\n\n const childMetadata = child.metadata as DocumentMetadata;\n const path = childMetadata.path ?? [];\n const parentPath = path.slice(0, -1);\n\n if (parentPath.length === 0) {\n return null;\n }\n\n const normalizedVersion = version.toLowerCase();\n const result = this.statements.getParentChunk.get(\n library.toLowerCase(),\n normalizedVersion,\n childMetadata.url,\n JSON.stringify(parentPath),\n BigInt(id),\n ) as DbQueryResult<DbDocument>;\n\n if (!result) {\n return null;\n }\n\n return mapDbDocumentToDocument(result);\n } catch (error) {\n throw new ConnectionError(`Failed to find parent chunk for ID ${id}`, error);\n }\n }\n\n /**\n * Fetches multiple documents by their IDs in a single call.\n * Returns an array of Document objects, sorted by their sort_order.\n */\n async findChunksByIds(\n library: string,\n version: string,\n ids: string[],\n ): Promise<Document[]> {\n if (!ids.length) return [];\n try {\n const normalizedVersion = version.toLowerCase();\n // Use parameterized query for variable number of IDs\n const placeholders = ids.map(() => \"?\").join(\",\");\n const stmt = this.db.prepare(\n `SELECT d.* FROM documents d\n JOIN libraries l ON d.library_id = l.id\n JOIN versions v ON d.version_id = v.id\n WHERE l.name = ? \n AND COALESCE(v.name, '') = COALESCE(?, '')\n AND d.id IN (${placeholders}) \n ORDER BY d.sort_order`,\n );\n const rows = stmt.all(\n library.toLowerCase(),\n normalizedVersion,\n ...ids,\n ) as DbDocument[];\n return rows.map((row) => mapDbDocumentToDocument(row));\n } catch (error) {\n throw new ConnectionError(\"Failed to fetch documents by IDs\", error);\n }\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Document } from \"@langchain/core/documents\";\nimport envPaths from \"env-paths\";\nimport Fuse from \"fuse.js\";\nimport semver from \"semver\";\nimport type { ScraperOptions } from \"../scraper/types\";\nimport { GreedySplitter, SemanticMarkdownSplitter } from \"../splitter\";\nimport type { ContentChunk, DocumentSplitter } from \"../splitter/types\";\nimport { LibraryNotFoundError, VersionNotFoundError } from \"../tools\";\nimport {\n SPLITTER_MAX_CHUNK_SIZE,\n SPLITTER_MIN_CHUNK_SIZE,\n SPLITTER_PREFERRED_CHUNK_SIZE,\n} from \"../utils/config\";\nimport { logger } from \"../utils/logger\";\nimport { getProjectRoot } from \"../utils/paths\";\nimport { DocumentRetrieverService } from \"./DocumentRetrieverService\";\nimport { DocumentStore } from \"./DocumentStore\";\nimport { StoreError } from \"./errors\";\nimport type {\n DbVersion,\n DbVersionWithLibrary,\n FindVersionResult,\n LibraryVersionDetails,\n StoreSearchResult,\n VersionScraperOptions,\n} from \"./types\";\n\n/**\n * Provides semantic search capabilities across different versions of library documentation.\n */\nexport class DocumentManagementService {\n private readonly store: DocumentStore;\n private readonly documentRetriever: DocumentRetrieverService;\n private readonly splitter: DocumentSplitter;\n\n /**\n * Normalizes a version string, converting null or undefined to an empty string\n * and converting to lowercase.\n */\n private normalizeVersion(version?: string | null): string {\n return (version ?? \"\").toLowerCase();\n }\n\n constructor() {\n let dbPath: string;\n let dbDir: string;\n\n // 1. Check Environment Variable\n const envStorePath = process.env.DOCS_MCP_STORE_PATH;\n if (envStorePath) {\n dbDir = envStorePath;\n dbPath = path.join(dbDir, \"documents.db\");\n logger.debug(`💾 Using database directory from DOCS_MCP_STORE_PATH: ${dbDir}`);\n } else {\n // 2. Check Old Local Path\n const projectRoot = getProjectRoot();\n const oldDbDir = path.join(projectRoot, \".store\");\n const oldDbPath = path.join(oldDbDir, \"documents.db\");\n const oldDbExists = fs.existsSync(oldDbPath); // Check file existence specifically\n\n if (oldDbExists) {\n dbPath = oldDbPath;\n dbDir = oldDbDir;\n logger.debug(`💾 Using legacy database path: ${dbPath}`);\n } else {\n // 3. Use Standard Path\n const standardPaths = envPaths(\"docs-mcp-server\", { suffix: \"\" });\n dbDir = standardPaths.data;\n dbPath = path.join(dbDir, \"documents.db\");\n logger.debug(`💾 Using standard database directory: ${dbDir}`);\n }\n }\n\n // Ensure the chosen directory exists\n try {\n fs.mkdirSync(dbDir, { recursive: true });\n } catch (error) {\n // Log potential error during directory creation but proceed\n // The DocumentStore constructor might handle DB file creation errors\n logger.error(`⚠️ Failed to create database directory ${dbDir}: ${error}`);\n }\n\n this.store = new DocumentStore(dbPath);\n this.documentRetriever = new DocumentRetrieverService(this.store);\n\n const semanticSplitter = new SemanticMarkdownSplitter(\n SPLITTER_PREFERRED_CHUNK_SIZE,\n SPLITTER_MAX_CHUNK_SIZE,\n );\n const greedySplitter = new GreedySplitter(\n semanticSplitter,\n SPLITTER_MIN_CHUNK_SIZE,\n SPLITTER_PREFERRED_CHUNK_SIZE,\n );\n\n this.splitter = greedySplitter;\n }\n\n /**\n * Initializes the underlying document store.\n */\n async initialize(): Promise<void> {\n await this.store.initialize();\n }\n\n /**\n * Shuts down the underlying document store.\n */\n\n async shutdown(): Promise<void> {\n logger.debug(\"Shutting down store manager\");\n await this.store.shutdown();\n }\n\n // Status tracking methods for pipeline integration\n\n /**\n * Gets versions by their current status.\n */\n async getVersionsByStatus(\n statuses: import(\"./types\").VersionStatus[],\n ): Promise<import(\"./types\").DbVersionWithLibrary[]> {\n return this.store.getVersionsByStatus(statuses);\n }\n\n /**\n * Gets all versions currently in RUNNING status.\n */\n async getRunningVersions(): Promise<import(\"./types\").DbVersionWithLibrary[]> {\n return this.store.getRunningVersions();\n }\n\n /**\n * Updates the status of a version.\n */\n async updateVersionStatus(\n versionId: number,\n status: import(\"./types\").VersionStatus,\n errorMessage?: string,\n ): Promise<void> {\n return this.store.updateVersionStatus(versionId, status, errorMessage);\n }\n\n /**\n * Updates the progress of a version being indexed.\n */\n async updateVersionProgress(\n versionId: number,\n pages: number,\n maxPages: number,\n ): Promise<void> {\n return this.store.updateVersionProgress(versionId, pages, maxPages);\n }\n\n /**\n * Stores scraper options for a version to enable reproducible indexing.\n */\n async storeScraperOptions(versionId: number, options: ScraperOptions): Promise<void> {\n return this.store.storeScraperOptions(versionId, options);\n }\n\n /**\n * Retrieves stored scraper options for a version.\n */\n async getVersionScraperOptions(\n versionId: number,\n ): Promise<VersionScraperOptions | null> {\n return this.store.getVersionScraperOptions(versionId);\n }\n\n /**\n * Retrieves a version record with all stored options.\n */\n async getVersionWithStoredOptions(versionId: number): Promise<DbVersion | null> {\n return this.store.getVersionWithStoredOptions(versionId);\n }\n\n /**\n * Finds versions that were indexed from the same source URL.\n */\n async findVersionsBySourceUrl(url: string): Promise<DbVersionWithLibrary[]> {\n return this.store.findVersionsBySourceUrl(url);\n }\n\n /**\n * Validates if a library exists in the store (either versioned or unversioned).\n * Throws LibraryNotFoundError with suggestions if the library is not found.\n * @param library The name of the library to validate.\n * @throws {LibraryNotFoundError} If the library does not exist.\n */\n async validateLibraryExists(library: string): Promise<void> {\n logger.info(`🔎 Validating existence of library: ${library}`);\n const normalizedLibrary = library.toLowerCase(); // Ensure consistent casing\n\n // Check for both versioned and unversioned documents\n const versions = await this.listVersions(normalizedLibrary);\n const hasUnversioned = await this.exists(normalizedLibrary, \"\"); // Check explicitly for unversioned\n\n if (versions.length === 0 && !hasUnversioned) {\n logger.warn(`⚠️ Library '${library}' not found.`);\n\n // Library doesn't exist, fetch all libraries to provide suggestions\n const allLibraries = await this.listLibraries();\n const libraryNames = allLibraries.map((lib) => lib.library);\n\n let suggestions: string[] = [];\n if (libraryNames.length > 0) {\n const fuse = new Fuse(libraryNames, {\n // Configure fuse.js options if needed (e.g., threshold)\n // isCaseSensitive: false, // Handled by normalizing library names\n // includeScore: true,\n threshold: 0.4, // Adjust threshold for desired fuzziness (0=exact, 1=match anything)\n });\n const results = fuse.search(normalizedLibrary);\n // Take top 3 suggestions\n suggestions = results.slice(0, 3).map((result) => result.item);\n logger.info(`🔍 Found suggestions: ${suggestions.join(\", \")}`);\n }\n\n throw new LibraryNotFoundError(library, suggestions);\n }\n\n logger.info(`✅ Library '${library}' confirmed to exist.`);\n }\n\n /**\n * Returns a list of all available semantic versions for a library.\n */\n async listVersions(library: string): Promise<string[]> {\n const versions = await this.store.queryUniqueVersions(library);\n return versions.filter((v) => semver.valid(v));\n }\n\n /**\n * Checks if documents exist for a given library and optional version.\n * If version is omitted, checks for documents without a specific version.\n */\n async exists(library: string, version?: string | null): Promise<boolean> {\n const normalizedVersion = this.normalizeVersion(version);\n return this.store.checkDocumentExists(library, normalizedVersion);\n }\n\n /**\n * Finds the most appropriate version of documentation based on the requested version.\n * When no target version is specified, returns the latest version.\n *\n * Version matching behavior:\n * - Exact versions (e.g., \"18.0.0\"): Matches that version or any earlier version\n * - X-Range patterns (e.g., \"5.x\", \"5.2.x\"): Matches within the specified range\n * - \"latest\" or no version: Returns the latest available version\n *\n * For documentation, we prefer matching older versions over no match at all,\n * since older docs are often still relevant and useful.\n * Also checks if unversioned documents exist for the library.\n */\n async findBestVersion(\n library: string,\n targetVersion?: string,\n ): Promise<FindVersionResult> {\n const libraryAndVersion = `${library}${targetVersion ? `@${targetVersion}` : \"\"}`;\n logger.info(`🔍 Finding best version for ${libraryAndVersion}`);\n\n // Check if unversioned documents exist *before* filtering for valid semver\n const hasUnversioned = await this.store.checkDocumentExists(library, \"\");\n const versionStrings = await this.listVersions(library);\n\n if (versionStrings.length === 0) {\n if (hasUnversioned) {\n logger.info(`ℹ️ Unversioned documents exist for ${library}`);\n return { bestMatch: null, hasUnversioned: true };\n }\n // Throw error only if NO versions (semver or unversioned) exist\n logger.warn(`⚠️ No valid versions found for ${library}`);\n // Fetch detailed versions to pass to the error constructor\n const allLibraryDetails = await this.store.queryLibraryVersions();\n const libraryDetails = allLibraryDetails.get(library) ?? [];\n throw new VersionNotFoundError(library, targetVersion ?? \"\", libraryDetails);\n }\n\n let bestMatch: string | null = null;\n\n if (!targetVersion || targetVersion === \"latest\") {\n bestMatch = semver.maxSatisfying(versionStrings, \"*\");\n } else {\n const versionRegex = /^(\\d+)(?:\\.(?:x(?:\\.x)?|\\d+(?:\\.(?:x|\\d+))?))?$|^$/;\n if (!versionRegex.test(targetVersion)) {\n logger.warn(`⚠️ Invalid target version format: ${targetVersion}`);\n // Don't throw yet, maybe unversioned exists\n } else {\n // Restore the previous logic with fallback\n let range = targetVersion;\n if (!semver.validRange(targetVersion)) {\n // If it's not a valid range (like '1.2' or '1'), treat it like a tilde range\n range = `~${targetVersion}`;\n } else if (semver.valid(targetVersion)) {\n // If it's an exact version, allow matching it OR any older version\n range = `${range} || <=${targetVersion}`;\n }\n // If it was already a valid range (like '1.x'), use it directly\n bestMatch = semver.maxSatisfying(versionStrings, range);\n }\n }\n\n if (bestMatch) {\n logger.info(`✅ Found best match version ${bestMatch} for ${libraryAndVersion}`);\n } else {\n logger.warn(`⚠️ No matching semver version found for ${libraryAndVersion}`);\n }\n\n // If no semver match found, but unversioned exists, return that info.\n // If a semver match was found, return it along with unversioned status.\n // If no semver match AND no unversioned, throw error.\n if (!bestMatch && !hasUnversioned) {\n // Fetch detailed versions to pass to the error constructor\n const allLibraryDetails = await this.store.queryLibraryVersions();\n const libraryDetails = allLibraryDetails.get(library) ?? [];\n throw new VersionNotFoundError(library, targetVersion ?? \"\", libraryDetails);\n }\n\n return { bestMatch, hasUnversioned };\n }\n\n /**\n * Removes all documents for a specific library and optional version.\n * If version is omitted, removes documents without a specific version.\n */\n async removeAllDocuments(library: string, version?: string | null): Promise<void> {\n const normalizedVersion = this.normalizeVersion(version);\n logger.info(\n `🗑️ Removing all documents from ${library}@${normalizedVersion || \"[no version]\"} store`,\n );\n const count = await this.store.deleteDocuments(library, normalizedVersion);\n logger.info(`📊 Deleted ${count} documents`);\n }\n\n /**\n * Adds a document to the store, splitting it into smaller chunks for better search results.\n * Uses SemanticMarkdownSplitter to maintain markdown structure and content types during splitting.\n * Preserves hierarchical structure of documents and distinguishes between text and code segments.\n * If version is omitted, the document is added without a specific version.\n */\n async addDocument(\n library: string,\n version: string | null | undefined,\n document: Document,\n ): Promise<void> {\n const normalizedVersion = this.normalizeVersion(version);\n const url = document.metadata.url as string;\n if (!url || typeof url !== \"string\" || !url.trim()) {\n throw new StoreError(\"Document metadata must include a valid URL\");\n }\n\n logger.info(`📚 Adding document: ${document.metadata.title}`);\n\n if (!document.pageContent.trim()) {\n throw new Error(\"Document content cannot be empty\");\n }\n\n // Split document into semantic chunks\n const chunks = await this.splitter.splitText(document.pageContent);\n\n // Convert semantic chunks to documents\n const splitDocs = chunks.map((chunk: ContentChunk) => ({\n pageContent: chunk.content,\n metadata: {\n ...document.metadata,\n level: chunk.section.level,\n path: chunk.section.path,\n },\n }));\n logger.info(`✂️ Split document into ${splitDocs.length} chunks`);\n\n // Add split documents to store\n await this.store.addDocuments(library, normalizedVersion, splitDocs);\n }\n\n /**\n * Searches for documentation content across versions.\n * Uses hybrid search (vector + FTS).\n * If version is omitted, searches documents without a specific version.\n */\n async searchStore(\n library: string,\n version: string | null | undefined,\n query: string,\n limit = 5,\n ): Promise<StoreSearchResult[]> {\n const normalizedVersion = this.normalizeVersion(version);\n return this.documentRetriever.search(library, normalizedVersion, query, limit);\n }\n\n async listLibraries(): Promise<\n Array<{ library: string; versions: LibraryVersionDetails[] }>\n > {\n // queryLibraryVersions now returns the detailed map directly\n const libraryMap = await this.store.queryLibraryVersions();\n\n // Transform the map into the desired array structure\n return Array.from(libraryMap.entries()).map(([library, versions]) => ({\n library,\n versions, // The versions array already contains LibraryVersionDetails\n }));\n }\n\n /**\n * Gets all versions in active states (queued, running, updating).\n */\n async getActiveVersions(): Promise<import(\"./types\").DbVersionWithLibrary[]> {\n return this.store.getActiveVersions();\n }\n\n /**\n * Ensures a library and version exist in the database and returns the version ID.\n * Creates the library and version records if they don't exist.\n */\n async ensureLibraryAndVersion(library: string, version: string): Promise<number> {\n // Use the same resolution logic as addDocuments but return the version ID\n const normalizedLibrary = library.toLowerCase();\n const normalizedVersion = this.normalizeVersion(version);\n\n // This will create the library and version if they don't exist\n const { versionId } = await this.store.resolveLibraryAndVersionIds(\n normalizedLibrary,\n normalizedVersion,\n );\n\n return versionId;\n }\n}\n","/**\n * Shared CLI utilities and helper functions.\n */\n\nimport { execSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { chromium } from \"playwright\";\nimport type { AppServerConfig } from \"../app\";\nimport type { IPipeline, PipelineOptions } from \"../pipeline\";\nimport { PipelineFactory } from \"../pipeline\";\nimport { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport {\n DEFAULT_HTTP_PORT,\n DEFAULT_MAX_CONCURRENCY,\n DEFAULT_PROTOCOL,\n DEFAULT_WEB_PORT,\n} from \"../utils/config\";\nimport { LogLevel, logger, setLogLevel } from \"../utils/logger\";\nimport { getProjectRoot } from \"../utils/paths\";\nimport type { GlobalOptions } from \"./types\";\n\n/**\n * Ensures that the Playwright browsers are installed, unless a system Chromium path is set.\n */\nexport function ensurePlaywrightBrowsersInstalled(): void {\n // If PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH is set, skip install\n const chromiumEnvPath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH;\n if (chromiumEnvPath && existsSync(chromiumEnvPath)) {\n logger.debug(\n `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH is set to '${chromiumEnvPath}', skipping Playwright browser install.`,\n );\n return;\n }\n try {\n // Dynamically require Playwright and check for Chromium browser\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const chromiumPath = chromium.executablePath();\n if (!chromiumPath || !existsSync(chromiumPath)) {\n throw new Error(\"Playwright Chromium browser not found\");\n }\n } catch (_err) {\n // Not installed or not found, attempt to install\n logger.debug(\n \"Playwright browsers not found. Installing Chromium browser for dynamic scraping (this may take a minute)...\",\n );\n try {\n logger.debug(\"Installing Playwright Chromium browser...\");\n execSync(\"npm exec -y playwright install --no-shell --with-deps chromium\", {\n stdio: \"ignore\", // Suppress output\n cwd: getProjectRoot(),\n });\n } catch (_installErr) {\n console.error(\n \"❌ Failed to install Playwright browsers automatically. Please run:\\n npx playwright install --no-shell --with-deps chromium\\nand try again.\",\n );\n process.exit(1);\n }\n }\n}\n\n/**\n * Resolves the protocol based on auto-detection or explicit specification.\n * Auto-detection uses TTY status to determine appropriate protocol.\n */\nexport function resolveProtocol(protocol: string): \"stdio\" | \"http\" {\n if (protocol === \"auto\") {\n // VS Code and CI/CD typically run without TTY\n if (!process.stdin.isTTY && !process.stdout.isTTY) {\n return \"stdio\";\n }\n return \"http\";\n }\n\n // Explicit protocol specification\n if (protocol === \"stdio\" || protocol === \"http\") {\n return protocol;\n }\n\n throw new Error(`Invalid protocol: ${protocol}. Must be 'auto', 'stdio', or 'http'`);\n}\n\n/**\n * Validates that --resume flag is only used with in-process workers.\n */\nexport function validateResumeFlag(resume: boolean, serverUrl?: string): void {\n if (resume && serverUrl) {\n throw new Error(\n \"--resume flag is incompatible with --server-url. \" +\n \"External workers handle their own job recovery.\",\n );\n }\n}\n\n/**\n * Formats output for CLI commands\n */\nexport const formatOutput = (data: unknown): string => JSON.stringify(data, null, 2);\n\n/**\n * Sets up logging based on global options\n */\nexport function setupLogging(options: GlobalOptions, protocol?: \"stdio\" | \"http\"): void {\n if (options.silent) {\n setLogLevel(LogLevel.ERROR);\n } else if (options.verbose) {\n setLogLevel(LogLevel.DEBUG);\n }\n\n // Suppress logging in stdio mode (before any logger calls)\n if (protocol === \"stdio\") {\n setLogLevel(LogLevel.ERROR);\n }\n}\n\n/**\n * Validates and parses port number\n */\nexport function validatePort(portString: string): number {\n const port = Number.parseInt(portString, 10);\n if (Number.isNaN(port) || port < 1 || port > 65535) {\n throw new Error(\"❌ Invalid port number\");\n }\n return port;\n}\n\n/**\n * Initializes DocumentManagementService for CLI commands\n */\nexport async function initializeDocumentService(): Promise<DocumentManagementService> {\n const docService = new DocumentManagementService();\n await docService.initialize();\n return docService;\n}\n\n/**\n * Initializes PipelineManager for CLI commands\n */\nexport async function initializePipeline(\n docService: DocumentManagementService,\n options: PipelineOptions = {},\n): Promise<IPipeline> {\n logger.debug(`Initializing PipelineManager with options: ${JSON.stringify(options)}`);\n const manager = await PipelineFactory.createPipeline(docService, options);\n\n // Configure progress callbacks for real-time updates\n manager.setCallbacks({\n onJobProgress: async (job, progress) => {\n logger.debug(\n `📊 Job ${job.id} progress: ${progress.pagesScraped}/${progress.totalPages} pages`,\n );\n },\n onJobStatusChange: async (job) => {\n logger.debug(`🔄 Job ${job.id} status changed to: ${job.status}`);\n },\n onJobError: async (job, error, document) => {\n logger.warn(\n `⚠️ Job ${job.id} error ${document ? `on document ${document.metadata.url}` : \"\"}: ${error.message}`,\n );\n },\n });\n\n return manager;\n}\n\n/**\n * Creates AppServerConfig based on service requirements\n */\nexport function createAppServerConfig(options: {\n enableWebInterface?: boolean;\n enableMcpServer?: boolean;\n enablePipelineApi?: boolean;\n enableWorker?: boolean;\n port: number;\n externalWorkerUrl?: string;\n}): AppServerConfig {\n return {\n enableWebInterface: options.enableWebInterface ?? false,\n enableMcpServer: options.enableMcpServer ?? true,\n enablePipelineApi: options.enablePipelineApi ?? false,\n enableWorker: options.enableWorker ?? true,\n port: options.port,\n externalWorkerUrl: options.externalWorkerUrl,\n };\n}\n\n/**\n * Parses custom headers from CLI options\n */\nexport function parseHeaders(headerOptions: string[]): Record<string, string> {\n const headers: Record<string, string> = {};\n\n if (Array.isArray(headerOptions)) {\n for (const entry of headerOptions) {\n const idx = entry.indexOf(\":\");\n if (idx > 0) {\n const name = entry.slice(0, idx).trim();\n const value = entry.slice(idx + 1).trim();\n if (name) headers[name] = value;\n }\n }\n }\n\n return headers;\n}\n\n/**\n * Default configuration values\n */\nexport const CLI_DEFAULTS = {\n PROTOCOL: DEFAULT_PROTOCOL,\n HTTP_PORT: DEFAULT_HTTP_PORT,\n WEB_PORT: DEFAULT_WEB_PORT,\n MAX_CONCURRENCY: DEFAULT_MAX_CONCURRENCY,\n} as const;\n","/**\n * Default command - Starts unified server when no subcommand is specified.\n */\n\nimport type { Command } from \"commander\";\nimport { startAppServer } from \"../../app\";\nimport { startStdioServer } from \"../../mcp/startStdioServer\";\nimport { initializeTools } from \"../../mcp/tools\";\nimport type { PipelineOptions } from \"../../pipeline\";\nimport { logger } from \"../../utils/logger\";\nimport {\n CLI_DEFAULTS,\n createAppServerConfig,\n ensurePlaywrightBrowsersInstalled,\n initializeDocumentService,\n initializePipeline,\n resolveProtocol,\n setupLogging,\n validatePort,\n} from \"../utils\";\n\nexport function createDefaultAction(program: Command): Command {\n return program\n .option(\n \"--protocol <type>\",\n \"Protocol for MCP server: 'auto' (default), 'stdio', or 'http'\",\n \"auto\",\n )\n .option(\"--port <number>\", \"Port for the server\", CLI_DEFAULTS.HTTP_PORT.toString())\n .option(\"--resume\", \"Resume interrupted jobs on startup\", false)\n .action(\n async (\n options: {\n protocol: string;\n port: string;\n resume: boolean;\n },\n command,\n ) => {\n const globalOptions = command.opts();\n\n // Resolve protocol and validate flags\n const resolvedProtocol = resolveProtocol(options.protocol);\n\n // Setup logging\n setupLogging(globalOptions, resolvedProtocol);\n logger.debug(\"No subcommand specified, starting unified server by default...\");\n const port = validatePort(options.port);\n\n // Ensure browsers are installed\n ensurePlaywrightBrowsersInstalled();\n\n const docService = await initializeDocumentService();\n const pipelineOptions: PipelineOptions = {\n recoverJobs: options.resume || false, // Use --resume flag for job recovery\n concurrency: 3,\n };\n const pipeline = await initializePipeline(docService, pipelineOptions);\n\n if (resolvedProtocol === \"stdio\") {\n // Direct stdio mode - bypass AppServer entirely\n logger.debug(`🔍 Auto-detected stdio protocol (no TTY)`);\n\n await pipeline.start(); // Start pipeline for stdio mode\n const mcpTools = await initializeTools(docService, pipeline);\n await startStdioServer(mcpTools);\n\n await new Promise(() => {}); // Keep running forever\n } else {\n // HTTP mode - use AppServer\n logger.debug(`🔍 Auto-detected http protocol (TTY available)`);\n\n // Configure services based on resolved protocol\n const config = createAppServerConfig({\n enableWebInterface: true, // Enable web interface in http mode\n enableMcpServer: true, // Always enable MCP server\n enablePipelineApi: true, // Enable pipeline API in http mode\n enableWorker: true, // Always enable in-process worker for unified server\n port,\n });\n\n await startAppServer(docService, pipeline, config);\n\n await new Promise(() => {}); // Keep running forever\n }\n },\n );\n}\n","/**\n * Fetch URL command - Fetches a URL and converts its content to Markdown.\n */\n\nimport type { Command } from \"commander\";\nimport { FileFetcher, HttpFetcher } from \"../../scraper/fetcher\";\nimport { ScrapeMode } from \"../../scraper/types\";\nimport { FetchUrlTool } from \"../../tools\";\nimport { parseHeaders, setupLogging } from \"../utils\";\n\nexport function createFetchUrlCommand(program: Command): Command {\n return program\n .command(\"fetch-url <url>\")\n .description(\"Fetch a URL and convert its content to Markdown\")\n .option(\n \"--no-follow-redirects\",\n \"Disable following HTTP redirects (default: follow redirects)\",\n )\n .option(\n \"--scrape-mode <mode>\",\n `HTML processing strategy: '${ScrapeMode.Fetch}', '${ScrapeMode.Playwright}', '${ScrapeMode.Auto}' (default)`,\n (value: string): ScrapeMode => {\n const validModes = Object.values(ScrapeMode);\n if (!validModes.includes(value as ScrapeMode)) {\n console.warn(\n `Warning: Invalid scrape mode '${value}'. Using default '${ScrapeMode.Auto}'.`,\n );\n return ScrapeMode.Auto;\n }\n return value as ScrapeMode;\n },\n ScrapeMode.Auto,\n )\n .option(\n \"--header <name:value>\",\n \"Custom HTTP header to send with the request (can be specified multiple times)\",\n (val: string, prev: string[] = []) => prev.concat([val]),\n [] as string[],\n )\n .action(\n async (\n url: string,\n options: {\n followRedirects: boolean;\n scrapeMode: ScrapeMode;\n header: string[];\n },\n command,\n ) => {\n const globalOptions = command.parent?.opts() || {};\n setupLogging(globalOptions);\n\n const headers = parseHeaders(options.header);\n\n // FetchUrlTool does not require DocumentManagementService or PipelineManager\n const fetchUrlTool = new FetchUrlTool(new HttpFetcher(), new FileFetcher());\n const content = await fetchUrlTool.execute({\n url,\n followRedirects: options.followRedirects,\n scrapeMode: options.scrapeMode,\n headers: Object.keys(headers).length > 0 ? headers : undefined,\n });\n console.log(content);\n },\n );\n}\n","/**\n * Find version command - Finds the best matching version for a library.\n */\n\nimport type { Command } from \"commander\";\nimport { FindVersionTool } from \"../../tools\";\nimport { initializeDocumentService, setupLogging } from \"../utils\";\n\nexport function createFindVersionCommand(program: Command): Command {\n return program\n .command(\"find-version <library>\")\n .description(\"Find the best matching version for a library\")\n .option(\"-v, --version <string>\", \"Pattern to match (optional, supports ranges)\")\n .action(async (library: string, options: { version?: string }, command) => {\n const globalOptions = command.parent?.opts() || {};\n setupLogging(globalOptions);\n\n const docService = await initializeDocumentService();\n try {\n const findVersionTool = new FindVersionTool(docService);\n const versionInfo = await findVersionTool.execute({\n library,\n targetVersion: options.version,\n });\n if (!versionInfo) throw new Error(\"Failed to get version information\");\n console.log(versionInfo);\n } finally {\n await docService.shutdown();\n }\n });\n}\n","/**\n * List command - Lists all available libraries and their versions.\n */\n\nimport type { Command } from \"commander\";\nimport { ListLibrariesTool } from \"../../tools\";\nimport { formatOutput, initializeDocumentService, setupLogging } from \"../utils\";\n\nexport function createListCommand(program: Command): Command {\n return program\n .command(\"list\")\n .description(\"List all available libraries and their versions\")\n .action(async (command) => {\n const globalOptions = command.opts() || {};\n setupLogging(globalOptions);\n\n const docService = await initializeDocumentService();\n try {\n const listLibrariesTool = new ListLibrariesTool(docService);\n const result = await listLibrariesTool.execute();\n console.log(formatOutput(result.libraries));\n } finally {\n await docService.shutdown();\n }\n });\n}\n","/**\n * MCP command - Starts MCP server only.\n */\n\nimport type { Command } from \"commander\";\nimport { startAppServer } from \"../../app\";\nimport { startStdioServer } from \"../../mcp/startStdioServer\";\nimport { initializeTools } from \"../../mcp/tools\";\nimport type { PipelineOptions } from \"../../pipeline\";\nimport { logger } from \"../../utils/logger\";\nimport {\n CLI_DEFAULTS,\n createAppServerConfig,\n initializeDocumentService,\n initializePipeline,\n resolveProtocol,\n setupLogging,\n validatePort,\n} from \"../utils\";\n\nexport function createMcpCommand(program: Command): Command {\n return program\n .command(\"mcp\")\n .description(\"Start MCP server only\")\n .option(\n \"--protocol <type>\",\n \"Protocol for MCP server: 'auto' (default), 'stdio', or 'http'\",\n CLI_DEFAULTS.PROTOCOL,\n )\n .option(\n \"--port <number>\",\n \"Port for the MCP server\",\n CLI_DEFAULTS.HTTP_PORT.toString(),\n )\n .option(\n \"--server-url <url>\",\n \"URL of external pipeline worker API (e.g., http://localhost:6280/api)\",\n )\n .action(\n async (\n cmdOptions: {\n protocol: string;\n port: string;\n serverUrl?: string;\n },\n command,\n ) => {\n const globalOptions = command.parent?.opts() || {};\n const port = validatePort(cmdOptions.port);\n const serverUrl = cmdOptions.serverUrl;\n\n // Resolve protocol using same logic as default action\n const resolvedProtocol = resolveProtocol(cmdOptions.protocol);\n setupLogging(globalOptions, resolvedProtocol);\n\n try {\n const docService = await initializeDocumentService();\n const pipelineOptions: PipelineOptions = {\n recoverJobs: false, // MCP command doesn't support job recovery\n serverUrl,\n concurrency: 3,\n };\n const pipeline = await initializePipeline(docService, pipelineOptions);\n\n if (resolvedProtocol === \"stdio\") {\n // Direct stdio mode - bypass AppServer entirely\n logger.debug(`🔍 Auto-detected stdio protocol (no TTY)`);\n logger.info(\"🚀 Starting MCP server (stdio mode)\");\n\n await pipeline.start(); // Start pipeline for stdio mode\n const mcpTools = await initializeTools(docService, pipeline);\n await startStdioServer(mcpTools);\n\n await new Promise(() => {}); // Keep running forever\n } else {\n // HTTP mode - use AppServer\n logger.debug(`🔍 Auto-detected http protocol (TTY available)`);\n logger.info(\"🚀 Starting MCP server (http mode)\");\n\n // Configure MCP-only server\n const config = createAppServerConfig({\n enableWebInterface: false, // Never enable web interface in mcp command\n enableMcpServer: true,\n enablePipelineApi: false, // Never enable pipeline API in mcp command\n enableWorker: !serverUrl,\n port,\n externalWorkerUrl: serverUrl,\n });\n\n await startAppServer(docService, pipeline, config);\n await new Promise(() => {}); // Keep running forever\n }\n } catch (error) {\n logger.error(`❌ Failed to start MCP server: ${error}`);\n process.exit(1);\n }\n },\n );\n}\n","/**\n * Remove command - Removes documents for a specific library and version.\n */\n\nimport type { Command } from \"commander\";\nimport { initializeDocumentService, setupLogging } from \"../utils\";\n\nexport function createRemoveCommand(program: Command): Command {\n return program\n .command(\"remove <library>\")\n .description(\"Remove documents for a specific library and version\")\n .option(\n \"-v, --version <string>\",\n \"Version to remove (optional, removes unversioned if omitted)\",\n )\n .action(async (library: string, options: { version?: string }, command) => {\n const globalOptions = command.parent?.opts() || {};\n setupLogging(globalOptions);\n\n const docService = await initializeDocumentService();\n const { version } = options;\n try {\n await docService.removeAllDocuments(library, version);\n console.log(\n `✅ Successfully removed documents for ${library}${version ? `@${version}` : \" (unversioned)\"}.`,\n );\n } catch (error) {\n console.error(\n `❌ Failed to remove documents for ${library}${version ? `@${version}` : \" (unversioned)\"}:`,\n error instanceof Error ? error.message : String(error),\n );\n throw error;\n } finally {\n await docService.shutdown();\n }\n });\n}\n","/**\n * Scrape command - Scrapes and indexes documentation from a URL or local folder.\n */\n\nimport type { Command } from \"commander\";\nimport type { IPipeline, PipelineOptions } from \"../../pipeline\";\nimport { PipelineFactory } from \"../../pipeline\";\nimport { ScrapeMode } from \"../../scraper/types\";\nimport { DocumentManagementService } from \"../../store/DocumentManagementService\";\nimport { ScrapeTool } from \"../../tools\";\nimport {\n DEFAULT_MAX_CONCURRENCY,\n DEFAULT_MAX_DEPTH,\n DEFAULT_MAX_PAGES,\n} from \"../../utils/config\";\nimport { parseHeaders, setupLogging } from \"../utils\";\n\nexport function createScrapeCommand(program: Command): Command {\n return program\n .command(\"scrape <library> <url>\")\n .description(\n \"Scrape and index documentation from a URL or local folder.\\n\\n\" +\n \"To scrape local files or folders, use a file:// URL.\\n\" +\n \"Examples:\\n\" +\n \" scrape mylib https://react.dev/reference/react\\n\" +\n \" scrape mylib file:///Users/me/docs/index.html\\n\" +\n \" scrape mylib file:///Users/me/docs/my-library\\n\" +\n \"\\nNote: For local files/folders, you must use the file:// prefix. If running in Docker, mount the folder and use the container path. See README for details.\",\n )\n .option(\"-v, --version <string>\", \"Version of the library (optional)\")\n .option(\n \"-p, --max-pages <number>\",\n \"Maximum pages to scrape\",\n DEFAULT_MAX_PAGES.toString(),\n )\n .option(\n \"-d, --max-depth <number>\",\n \"Maximum navigation depth\",\n DEFAULT_MAX_DEPTH.toString(),\n )\n .option(\n \"-c, --max-concurrency <number>\",\n \"Maximum concurrent page requests\",\n DEFAULT_MAX_CONCURRENCY.toString(),\n )\n .option(\"--ignore-errors\", \"Ignore errors during scraping\", true)\n .option(\n \"--scope <scope>\",\n \"Crawling boundary: 'subpages' (default), 'hostname', or 'domain'\",\n (value) => {\n const validScopes = [\"subpages\", \"hostname\", \"domain\"];\n if (!validScopes.includes(value)) {\n console.warn(`Warning: Invalid scope '${value}'. Using default 'subpages'.`);\n return \"subpages\";\n }\n return value;\n },\n \"subpages\",\n )\n .option(\n \"--no-follow-redirects\",\n \"Disable following HTTP redirects (default: follow redirects)\",\n )\n .option(\n \"--scrape-mode <mode>\",\n `HTML processing strategy: '${ScrapeMode.Fetch}', '${ScrapeMode.Playwright}', '${ScrapeMode.Auto}' (default)`,\n (value: string): ScrapeMode => {\n const validModes = Object.values(ScrapeMode);\n if (!validModes.includes(value as ScrapeMode)) {\n console.warn(\n `Warning: Invalid scrape mode '${value}'. Using default '${ScrapeMode.Auto}'.`,\n );\n return ScrapeMode.Auto;\n }\n return value as ScrapeMode;\n },\n ScrapeMode.Auto,\n )\n .option(\n \"--include-pattern <pattern>\",\n \"Glob or regex pattern for URLs to include (can be specified multiple times). Regex patterns must be wrapped in slashes, e.g. /pattern/.\",\n (val: string, prev: string[] = []) => prev.concat([val]),\n [] as string[],\n )\n .option(\n \"--exclude-pattern <pattern>\",\n \"Glob or regex pattern for URLs to exclude (can be specified multiple times, takes precedence over include). Regex patterns must be wrapped in slashes, e.g. /pattern/.\",\n (val: string, prev: string[] = []) => prev.concat([val]),\n [] as string[],\n )\n .option(\n \"--header <name:value>\",\n \"Custom HTTP header to send with each request (can be specified multiple times)\",\n (val: string, prev: string[] = []) => prev.concat([val]),\n [] as string[],\n )\n .option(\n \"--server-url <url>\",\n \"URL of external pipeline worker API (e.g., http://localhost:6280/api)\",\n )\n .action(\n async (\n library: string,\n url: string,\n options: {\n version?: string;\n maxPages: string;\n maxDepth: string;\n maxConcurrency: string;\n ignoreErrors: boolean;\n scope: string;\n followRedirects: boolean;\n scrapeMode: ScrapeMode;\n includePattern: string[];\n excludePattern: string[];\n header: string[];\n serverUrl?: string;\n },\n command,\n ) => {\n const globalOptions = command.parent?.opts() || {};\n setupLogging(globalOptions);\n\n const docService = new DocumentManagementService();\n let pipeline: IPipeline | null = null;\n\n try {\n await docService.initialize();\n\n // Use server-url if provided, otherwise run locally\n const pipelineOptions: PipelineOptions = {\n recoverJobs: false, // CLI: no job recovery (immediate execution)\n concurrency: 1, // CLI: single job at a time\n serverUrl: options.serverUrl, // Use external worker if specified\n };\n\n pipeline = await PipelineFactory.createPipeline(docService, pipelineOptions);\n await pipeline.start();\n const scrapeTool = new ScrapeTool(pipeline);\n\n // Parse headers from CLI options\n const headers = parseHeaders(options.header);\n\n const result = await scrapeTool.execute({\n url,\n library,\n version: options.version,\n options: {\n maxPages: Number.parseInt(options.maxPages),\n maxDepth: Number.parseInt(options.maxDepth),\n maxConcurrency: Number.parseInt(options.maxConcurrency),\n ignoreErrors: options.ignoreErrors,\n scope: options.scope as \"subpages\" | \"hostname\" | \"domain\",\n followRedirects: options.followRedirects,\n scrapeMode: options.scrapeMode,\n includePatterns:\n Array.isArray(options.includePattern) && options.includePattern.length > 0\n ? options.includePattern\n : undefined,\n excludePatterns:\n Array.isArray(options.excludePattern) && options.excludePattern.length > 0\n ? options.excludePattern\n : undefined,\n headers: Object.keys(headers).length > 0 ? headers : undefined,\n },\n });\n\n if (\"pagesScraped\" in result) {\n console.log(`✅ Successfully scraped ${result.pagesScraped} pages`);\n } else {\n console.log(`🚀 Scraping job started with ID: ${result.jobId}`);\n }\n } finally {\n if (pipeline) await pipeline.stop();\n await docService.shutdown();\n }\n },\n );\n}\n","/**\n * Search command - Searches documents in a library.\n */\n\nimport type { Command } from \"commander\";\nimport { SearchTool } from \"../../tools\";\nimport { formatOutput, initializeDocumentService, setupLogging } from \"../utils\";\n\nexport function createSearchCommand(program: Command): Command {\n return program\n .command(\"search <library> <query>\")\n .description(\n \"Search documents in a library. Version matching examples:\\n\" +\n \" - search react --version 18.0.0 'hooks' -> matches docs for React 18.0.0 or earlier versions\\n\" +\n \" - search react --version 18.0.0 'hooks' --exact-match -> only matches React 18.0.0\\n\" +\n \" - search typescript --version 5.x 'types' -> matches any TypeScript 5.x.x version\\n\" +\n \" - search typescript --version 5.2.x 'types' -> matches any TypeScript 5.2.x version\",\n )\n .option(\n \"-v, --version <string>\",\n \"Version of the library (optional, supports ranges)\",\n )\n .option(\"-l, --limit <number>\", \"Maximum number of results\", \"5\")\n .option(\"-e, --exact-match\", \"Only use exact version match (default: false)\", false)\n .action(\n async (\n library: string,\n query: string,\n options: {\n version?: string;\n limit: string;\n exactMatch: boolean;\n },\n command,\n ) => {\n const globalOptions = command.parent?.opts() || {};\n setupLogging(globalOptions);\n\n const docService = await initializeDocumentService();\n try {\n const searchTool = new SearchTool(docService);\n const result = await searchTool.execute({\n library,\n version: options.version,\n query,\n limit: Number.parseInt(options.limit),\n exactMatch: options.exactMatch,\n });\n console.log(formatOutput(result.results));\n } finally {\n await docService.shutdown();\n }\n },\n );\n}\n","/**\n * Web command - Starts web interface only.\n */\n\nimport type { Command } from \"commander\";\nimport { startAppServer } from \"../../app\";\nimport type { PipelineOptions } from \"../../pipeline\";\nimport { logger } from \"../../utils/logger\";\nimport {\n CLI_DEFAULTS,\n createAppServerConfig,\n initializeDocumentService,\n initializePipeline,\n setupLogging,\n validatePort,\n} from \"../utils\";\n\nexport function createWebCommand(program: Command): Command {\n return program\n .command(\"web\")\n .description(\"Start web interface only\")\n .option(\n \"--port <number>\",\n \"Port for the web interface\",\n CLI_DEFAULTS.WEB_PORT.toString(),\n )\n .option(\n \"--server-url <url>\",\n \"URL of external pipeline worker API (e.g., http://localhost:6280/api)\",\n )\n .action(\n async (\n cmdOptions: {\n port: string;\n serverUrl?: string;\n },\n command,\n ) => {\n const globalOptions = command.parent?.opts() || {};\n const port = validatePort(cmdOptions.port);\n const serverUrl = cmdOptions.serverUrl;\n\n setupLogging(globalOptions);\n\n try {\n const docService = await initializeDocumentService();\n const pipelineOptions: PipelineOptions = {\n recoverJobs: false, // Web command doesn't support job recovery\n serverUrl,\n concurrency: 3,\n };\n const pipeline = await initializePipeline(docService, pipelineOptions);\n\n // Configure web-only server\n const config = createAppServerConfig({\n enableWebInterface: true,\n enableMcpServer: false,\n enablePipelineApi: false,\n enableWorker: !serverUrl,\n port,\n externalWorkerUrl: serverUrl,\n });\n\n logger.info(\n `🚀 Starting web interface${serverUrl ? ` connecting to worker at ${serverUrl}` : \"\"}`,\n );\n await startAppServer(docService, pipeline, config);\n\n await new Promise(() => {}); // Keep running forever\n } catch (error) {\n logger.error(`❌ Failed to start web interface: ${error}`);\n process.exit(1);\n }\n },\n );\n}\n","/**\n * Worker command - Starts external pipeline worker (HTTP API).\n */\n\nimport type { Command } from \"commander\";\nimport { startAppServer } from \"../../app\";\nimport type { PipelineOptions } from \"../../pipeline\";\nimport { logger } from \"../../utils/logger\";\nimport {\n CLI_DEFAULTS,\n createAppServerConfig,\n ensurePlaywrightBrowsersInstalled,\n initializeDocumentService,\n initializePipeline,\n setupLogging,\n validatePort,\n} from \"../utils\";\n\nexport function createWorkerCommand(program: Command): Command {\n return program\n .command(\"worker\")\n .description(\"Start external pipeline worker (HTTP API)\")\n .option(\"--port <number>\", \"Port for worker API\", \"8080\")\n .option(\"--resume\", \"Resume interrupted jobs on startup\", true)\n .action(async (cmdOptions: { port: string; resume: boolean }, command) => {\n const globalOptions = command.parent?.opts() || {};\n const port = validatePort(cmdOptions.port);\n\n setupLogging(globalOptions);\n\n try {\n logger.info(`🚀 Starting external pipeline worker on port ${port}`);\n\n // Ensure browsers are installed for scraping\n ensurePlaywrightBrowsersInstalled();\n\n // Initialize services\n const docService = await initializeDocumentService();\n const pipelineOptions: PipelineOptions = {\n recoverJobs: cmdOptions.resume, // Use the resume option\n concurrency: CLI_DEFAULTS.MAX_CONCURRENCY,\n };\n const pipeline = await initializePipeline(docService, pipelineOptions);\n\n // Configure worker-only server\n const config = createAppServerConfig({\n enableWebInterface: false,\n enableMcpServer: false,\n enablePipelineApi: true,\n enableWorker: true,\n port,\n });\n\n logger.info(`🚀 Starting external pipeline worker with HTTP API`);\n await startAppServer(docService, pipeline, config);\n\n await new Promise(() => {}); // Keep running forever\n } catch (error) {\n logger.error(`❌ Failed to start external pipeline worker: ${error}`);\n process.exit(1);\n }\n });\n}\n","/**\n * Main CLI setup and command registration.\n */\n\nimport { Command } from \"commander\";\nimport packageJson from \"../../package.json\";\nimport { LogLevel, setLogLevel } from \"../utils/logger\";\nimport { createDefaultAction } from \"./commands/default\";\nimport { createFetchUrlCommand } from \"./commands/fetchUrl\";\nimport { createFindVersionCommand } from \"./commands/findVersion\";\nimport { createListCommand } from \"./commands/list\";\nimport { createMcpCommand } from \"./commands/mcp\";\nimport { createRemoveCommand } from \"./commands/remove\";\nimport { createScrapeCommand } from \"./commands/scrape\";\nimport { createSearchCommand } from \"./commands/search\";\nimport { createWebCommand } from \"./commands/web\";\nimport { createWorkerCommand } from \"./commands/worker\";\nimport type { GlobalOptions } from \"./types\";\n\n/**\n * Creates and configures the main CLI program with all commands.\n */\nexport function createCliProgram(): Command {\n const program = new Command();\n\n // Configure main program\n program\n .name(\"docs-mcp-server\")\n .description(\"Unified CLI, MCP Server, and Web Interface for Docs MCP Server.\")\n .version(packageJson.version)\n .option(\"--verbose\", \"Enable verbose (debug) logging\", false)\n .option(\"--silent\", \"Disable all logging except errors\", false)\n .enablePositionalOptions()\n .showHelpAfterError(true);\n\n // Set up global options handling\n program.hook(\"preAction\", (thisCommand, _actionCommand) => {\n const globalOptions: GlobalOptions = thisCommand.opts();\n if (globalOptions.silent) setLogLevel(LogLevel.ERROR);\n else if (globalOptions.verbose) setLogLevel(LogLevel.DEBUG);\n });\n\n // Register all commands\n createMcpCommand(program);\n createWebCommand(program);\n createWorkerCommand(program);\n createScrapeCommand(program);\n createSearchCommand(program);\n createListCommand(program);\n createFindVersionCommand(program);\n createRemoveCommand(program);\n createFetchUrlCommand(program);\n\n // Set default action for when no subcommand is specified\n createDefaultAction(program);\n\n return program;\n}\n","/**\n * CLI main entry point with global shutdown and error handling.\n */\n\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { IPipeline } from \"../pipeline\";\nimport type { DocumentManagementService } from \"../store/DocumentManagementService\";\nimport { logger } from \"../utils/logger\";\nimport { createCliProgram } from \"./index\";\n\n// Module-level variables for active services and shutdown state\nlet activeAppServer: Promise<{ stop: () => Promise<void> }> | null = null;\nlet activeMcpStdioServer: McpServer | null = null;\nlet activeDocService: DocumentManagementService | null = null;\nlet activePipelineManager: IPipeline | null = null;\nlet isShuttingDown = false;\n\n/**\n * Graceful shutdown handler for SIGINT\n */\nconst sigintHandler = async (): Promise<void> => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n\n logger.debug(\"Received SIGINT. Shutting down gracefully...\");\n try {\n if (activeAppServer) {\n logger.debug(\"SIGINT: Stopping AppServer...\");\n const appServer = await activeAppServer;\n await appServer.stop();\n activeAppServer = null;\n logger.debug(\"SIGINT: AppServer stopped.\");\n }\n\n if (activeMcpStdioServer) {\n logger.debug(\"SIGINT: Stopping MCP server...\");\n await activeMcpStdioServer.close();\n activeMcpStdioServer = null;\n logger.debug(\"SIGINT: MCP server stopped.\");\n }\n\n // Shutdown active services\n logger.debug(\"SIGINT: Shutting down active services...\");\n if (activePipelineManager) {\n await activePipelineManager.stop();\n activePipelineManager = null;\n logger.debug(\"SIGINT: PipelineManager stopped.\");\n }\n\n if (activeDocService) {\n await activeDocService.shutdown();\n activeDocService = null;\n logger.debug(\"SIGINT: DocumentManagementService shut down.\");\n }\n\n logger.info(\"✅ Graceful shutdown completed\");\n process.exit(0);\n } catch (error) {\n logger.error(`❌ Error during graceful shutdown: ${error}`);\n process.exit(1);\n }\n};\n\n/**\n * Registers global services for shutdown handling\n */\nexport function registerGlobalServices(services: {\n appServer?: Promise<{ stop: () => Promise<void> }>;\n mcpStdioServer?: McpServer;\n docService?: DocumentManagementService;\n pipelineManager?: IPipeline;\n}): void {\n if (services.appServer) activeAppServer = services.appServer;\n if (services.mcpStdioServer) activeMcpStdioServer = services.mcpStdioServer;\n if (services.docService) activeDocService = services.docService;\n if (services.pipelineManager) activePipelineManager = services.pipelineManager;\n}\n\n/**\n * Main CLI execution function\n */\nexport async function runCli(): Promise<void> {\n let commandExecuted = false;\n\n // Reset shutdown state for new execution\n isShuttingDown = false;\n\n // Ensure only one SIGINT handler is active\n process.removeListener(\"SIGINT\", sigintHandler);\n process.on(\"SIGINT\", sigintHandler);\n\n try {\n const program = createCliProgram();\n\n // Track if a command was executed\n program.hook(\"preAction\", () => {\n commandExecuted = true;\n });\n\n await program.parseAsync(process.argv);\n } catch (error) {\n logger.error(`❌ Error in CLI: ${error}`);\n if (!isShuttingDown) {\n isShuttingDown = true;\n\n // Shutdown active services on error\n const shutdownPromises: Promise<void>[] = [];\n\n if (activeAppServer) {\n shutdownPromises.push(\n activeAppServer\n .then(async (appServer) => {\n await appServer.stop();\n activeAppServer = null;\n })\n .catch((e) => logger.error(`❌ Error stopping AppServer: ${e}`)),\n );\n }\n\n if (activeMcpStdioServer) {\n shutdownPromises.push(\n activeMcpStdioServer\n .close()\n .then(() => {\n activeMcpStdioServer = null;\n })\n .catch((e) => logger.error(`❌ Error stopping MCP server: ${e}`)),\n );\n }\n\n if (activePipelineManager) {\n shutdownPromises.push(\n activePipelineManager\n .stop()\n .then(() => {\n activePipelineManager = null;\n })\n .catch((e) => logger.error(`❌ Error stopping pipeline: ${e}`)),\n );\n }\n\n if (activeDocService) {\n shutdownPromises.push(\n activeDocService\n .shutdown()\n .then(() => {\n activeDocService = null;\n })\n .catch((e) => logger.error(`❌ Error shutting down doc service: ${e}`)),\n );\n }\n\n await Promise.allSettled(shutdownPromises);\n }\n process.exit(1);\n }\n\n // This block handles cleanup for CLI commands that completed successfully\n // and were not long-running servers.\n if (commandExecuted && !activeAppServer) {\n if (!isShuttingDown) {\n logger.debug(\n \"CLI command executed. No global services to shut down from this path.\",\n );\n }\n }\n}\n\n// Handle HMR for vite-node --watch\nif (import.meta.hot) {\n import.meta.hot.on(\"vite:beforeFullReload\", async () => {\n logger.info(\"🔥 Hot reload detected\");\n process.removeListener(\"SIGINT\", sigintHandler);\n\n const wasAlreadyShuttingDown = isShuttingDown;\n isShuttingDown = true;\n\n try {\n const shutdownPromises: Promise<void>[] = [];\n\n if (activeAppServer) {\n logger.debug(\"Shutting down AppServer...\");\n shutdownPromises.push(\n activeAppServer.then(async (appServer) => {\n await appServer.stop();\n logger.debug(\"AppServer shut down.\");\n }),\n );\n }\n\n if (activePipelineManager) {\n shutdownPromises.push(\n activePipelineManager.stop().then(() => {\n activePipelineManager = null;\n logger.debug(\"PipelineManager stopped.\");\n }),\n );\n }\n\n if (activeDocService) {\n shutdownPromises.push(\n activeDocService.shutdown().then(() => {\n activeDocService = null;\n logger.debug(\"DocumentManagementService shut down.\");\n }),\n );\n }\n\n await Promise.allSettled(shutdownPromises);\n logger.debug(\"Active services shut down.\");\n } catch (hmrError) {\n logger.error(`❌ Error during HMR cleanup: ${hmrError}`);\n } finally {\n // Reset state for the next module instantiation\n activeAppServer = null;\n if (!wasAlreadyShuttingDown) {\n isShuttingDown = false;\n }\n }\n });\n}\n","import \"dotenv/config\";\nimport { runCli } from \"./cli/main\";\nimport { ensurePlaywrightBrowsersInstalled } from \"./cli/utils\";\n\n// Ensure Playwright browsers are installed\nensurePlaywrightBrowsersInstalled();\n\n// Run the CLI\nrunCli().catch((error) => {\n console.error(`🔥 Fatal error in main execution: ${error}`);\n process.exit(1);\n});\n"],"names":["PipelineJobStatus","semver","DEFAULT_MAX_DEPTH","ScrapeMode","version","packageJson","VersionStatus","parseHeaders","fs","AppServer","path","URL","item","uuidv4","job","error","PipelineFactory","chunks","projectRoot"],"mappings":"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"}
|