@arabold/docs-mcp-server 1.25.2 → 1.26.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.
Files changed (114) hide show
  1. package/README.md +6 -1
  2. package/dist/android-icon-144x144.png +0 -0
  3. package/dist/android-icon-192x192.png +0 -0
  4. package/dist/android-icon-36x36.png +0 -0
  5. package/dist/android-icon-48x48.png +0 -0
  6. package/dist/android-icon-72x72.png +0 -0
  7. package/dist/android-icon-96x96.png +0 -0
  8. package/dist/apple-icon-114x114.png +0 -0
  9. package/dist/apple-icon-120x120.png +0 -0
  10. package/dist/apple-icon-144x144.png +0 -0
  11. package/dist/apple-icon-152x152.png +0 -0
  12. package/dist/apple-icon-180x180.png +0 -0
  13. package/dist/apple-icon-57x57.png +0 -0
  14. package/dist/apple-icon-60x60.png +0 -0
  15. package/dist/apple-icon-72x72.png +0 -0
  16. package/dist/apple-icon-76x76.png +0 -0
  17. package/dist/apple-icon-precomposed.png +0 -0
  18. package/dist/apple-icon.png +0 -0
  19. package/dist/assets/android-icon-144x144.png +0 -0
  20. package/dist/assets/android-icon-192x192.png +0 -0
  21. package/dist/assets/android-icon-36x36.png +0 -0
  22. package/dist/assets/android-icon-48x48.png +0 -0
  23. package/dist/assets/android-icon-72x72.png +0 -0
  24. package/dist/assets/android-icon-96x96.png +0 -0
  25. package/dist/assets/apple-icon-114x114.png +0 -0
  26. package/dist/assets/apple-icon-120x120.png +0 -0
  27. package/dist/assets/apple-icon-144x144.png +0 -0
  28. package/dist/assets/apple-icon-152x152.png +0 -0
  29. package/dist/assets/apple-icon-180x180.png +0 -0
  30. package/dist/assets/apple-icon-57x57.png +0 -0
  31. package/dist/assets/apple-icon-60x60.png +0 -0
  32. package/dist/assets/apple-icon-72x72.png +0 -0
  33. package/dist/assets/apple-icon-76x76.png +0 -0
  34. package/dist/assets/apple-icon-precomposed.png +0 -0
  35. package/dist/assets/apple-icon.png +0 -0
  36. package/dist/assets/favicon-16x16.png +0 -0
  37. package/dist/assets/favicon-32x32.png +0 -0
  38. package/dist/assets/favicon-96x96.png +0 -0
  39. package/dist/assets/favicon.ico +0 -0
  40. package/dist/assets/main.css +1 -1
  41. package/dist/assets/main.js +167 -81
  42. package/dist/assets/main.js.map +1 -1
  43. package/dist/assets/manifest.json +47 -0
  44. package/dist/assets/ms-icon-144x144.png +0 -0
  45. package/dist/assets/ms-icon-150x150.png +0 -0
  46. package/dist/assets/ms-icon-310x310.png +0 -0
  47. package/dist/assets/ms-icon-70x70.png +0 -0
  48. package/dist/favicon-16x16.png +0 -0
  49. package/dist/favicon-32x32.png +0 -0
  50. package/dist/favicon-96x96.png +0 -0
  51. package/dist/favicon.ico +0 -0
  52. package/dist/index.js +930 -224
  53. package/dist/index.js.map +1 -1
  54. package/dist/manifest.json +47 -0
  55. package/dist/ms-icon-144x144.png +0 -0
  56. package/dist/ms-icon-150x150.png +0 -0
  57. package/dist/ms-icon-310x310.png +0 -0
  58. package/dist/ms-icon-70x70.png +0 -0
  59. package/package.json +3 -7
  60. package/public/android-icon-144x144.png +0 -0
  61. package/public/android-icon-192x192.png +0 -0
  62. package/public/android-icon-36x36.png +0 -0
  63. package/public/android-icon-48x48.png +0 -0
  64. package/public/android-icon-72x72.png +0 -0
  65. package/public/android-icon-96x96.png +0 -0
  66. package/public/apple-icon-114x114.png +0 -0
  67. package/public/apple-icon-120x120.png +0 -0
  68. package/public/apple-icon-144x144.png +0 -0
  69. package/public/apple-icon-152x152.png +0 -0
  70. package/public/apple-icon-180x180.png +0 -0
  71. package/public/apple-icon-57x57.png +0 -0
  72. package/public/apple-icon-60x60.png +0 -0
  73. package/public/apple-icon-72x72.png +0 -0
  74. package/public/apple-icon-76x76.png +0 -0
  75. package/public/apple-icon-precomposed.png +0 -0
  76. package/public/apple-icon.png +0 -0
  77. package/public/assets/android-icon-144x144.png +0 -0
  78. package/public/assets/android-icon-192x192.png +0 -0
  79. package/public/assets/android-icon-36x36.png +0 -0
  80. package/public/assets/android-icon-48x48.png +0 -0
  81. package/public/assets/android-icon-72x72.png +0 -0
  82. package/public/assets/android-icon-96x96.png +0 -0
  83. package/public/assets/apple-icon-114x114.png +0 -0
  84. package/public/assets/apple-icon-120x120.png +0 -0
  85. package/public/assets/apple-icon-144x144.png +0 -0
  86. package/public/assets/apple-icon-152x152.png +0 -0
  87. package/public/assets/apple-icon-180x180.png +0 -0
  88. package/public/assets/apple-icon-57x57.png +0 -0
  89. package/public/assets/apple-icon-60x60.png +0 -0
  90. package/public/assets/apple-icon-72x72.png +0 -0
  91. package/public/assets/apple-icon-76x76.png +0 -0
  92. package/public/assets/apple-icon-precomposed.png +0 -0
  93. package/public/assets/apple-icon.png +0 -0
  94. package/public/assets/favicon-16x16.png +0 -0
  95. package/public/assets/favicon-32x32.png +0 -0
  96. package/public/assets/favicon-96x96.png +0 -0
  97. package/public/assets/favicon.ico +0 -0
  98. package/public/assets/main.css +1 -1
  99. package/public/assets/main.js +167 -81
  100. package/public/assets/main.js.map +1 -1
  101. package/public/assets/manifest.json +47 -0
  102. package/public/assets/ms-icon-144x144.png +0 -0
  103. package/public/assets/ms-icon-150x150.png +0 -0
  104. package/public/assets/ms-icon-310x310.png +0 -0
  105. package/public/assets/ms-icon-70x70.png +0 -0
  106. package/public/favicon-16x16.png +0 -0
  107. package/public/favicon-32x32.png +0 -0
  108. package/public/favicon-96x96.png +0 -0
  109. package/public/favicon.ico +0 -0
  110. package/public/manifest.json +47 -0
  111. package/public/ms-icon-144x144.png +0 -0
  112. package/public/ms-icon-150x150.png +0 -0
  113. package/public/ms-icon-310x310.png +0 -0
  114. package/public/ms-icon-70x70.png +0 -0
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { PostHog } from "posthog-node";
9
9
  import { randomUUID } from "node:crypto";
10
10
  import fs, { existsSync, readFileSync } from "node:fs";
11
11
  import path from "node:path";
12
+ import { fileURLToPath, URL as URL$1 } from "node:url";
12
13
  import envPaths from "env-paths";
13
14
  import { Option, Command } from "commander";
14
15
  import formBody from "@fastify/formbody";
@@ -22,11 +23,10 @@ import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
22
23
  import { v4 } from "uuid";
23
24
  import { VirtualConsole, JSDOM } from "jsdom";
24
25
  import mime from "mime";
25
- import { fileURLToPath, URL as URL$1 } from "node:url";
26
26
  import psl from "psl";
27
+ import { HeaderGenerator } from "header-generator";
27
28
  import fs$1 from "node:fs/promises";
28
29
  import axios from "axios";
29
- import { HeaderGenerator } from "header-generator";
30
30
  import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
31
31
  import remarkGfm from "remark-gfm";
32
32
  import remarkHtml from "remark-html";
@@ -539,6 +539,49 @@ class PostHogClient {
539
539
  return this.enabled && !!this.client;
540
540
  }
541
541
  }
542
+ let projectRoot = null;
543
+ function getProjectRoot() {
544
+ if (projectRoot) {
545
+ return projectRoot;
546
+ }
547
+ const currentFilePath = fileURLToPath(import.meta.url);
548
+ let currentDir = path.dirname(currentFilePath);
549
+ while (true) {
550
+ const packageJsonPath = path.join(currentDir, "package.json");
551
+ if (fs.existsSync(packageJsonPath)) {
552
+ projectRoot = currentDir;
553
+ return currentDir;
554
+ }
555
+ const parentDir = path.dirname(currentDir);
556
+ if (parentDir === currentDir) {
557
+ throw new Error("Could not find project root containing package.json.");
558
+ }
559
+ currentDir = parentDir;
560
+ }
561
+ }
562
+ function resolveStorePath(storePath) {
563
+ let dbDir;
564
+ if (storePath) {
565
+ dbDir = storePath;
566
+ } else {
567
+ const projectRoot2 = getProjectRoot();
568
+ const oldDbDir = path.join(projectRoot2, ".store");
569
+ const oldDbPath = path.join(oldDbDir, "documents.db");
570
+ const oldDbExists = fs.existsSync(oldDbPath);
571
+ if (oldDbExists) {
572
+ dbDir = oldDbDir;
573
+ } else {
574
+ const standardPaths = envPaths("docs-mcp-server", { suffix: "" });
575
+ dbDir = standardPaths.data;
576
+ }
577
+ }
578
+ try {
579
+ fs.mkdirSync(dbDir, { recursive: true });
580
+ } catch (error) {
581
+ console.warn(`⚠️ Failed to create database directory ${dbDir}: ${error}`);
582
+ }
583
+ return dbDir;
584
+ }
542
585
  class TelemetryConfig {
543
586
  static instance;
544
587
  enabled = true;
@@ -560,7 +603,7 @@ class TelemetryConfig {
560
603
  }
561
604
  function generateInstallationId(storePath) {
562
605
  try {
563
- const dataDir = storePath || envPaths("docs-mcp-server", { suffix: "" }).data;
606
+ const dataDir = resolveStorePath(storePath);
564
607
  const installationIdPath = path.join(dataDir, "installation.id");
565
608
  if (fs.existsSync(installationIdPath)) {
566
609
  const existingId = fs.readFileSync(installationIdPath, "utf8").trim();
@@ -709,14 +752,14 @@ function extractProtocol(urlOrPath) {
709
752
  }
710
753
  }
711
754
  const name = "@arabold/docs-mcp-server";
712
- const version = "1.25.1";
755
+ const version = "1.25.3";
713
756
  const description = "MCP server for fetching and searching documentation";
714
757
  const type = "module";
715
758
  const bin = { "docs-mcp-server": "dist/index.js" };
716
759
  const license = "MIT";
717
760
  const repository = { "type": "git", "url": "git+https://github.com/arabold/docs-mcp-server.git" };
718
761
  const files = ["dist", "public", "db", "README.md", "LICENSE", "package.json"];
719
- const scripts = { "prepare": "husky || true", "build": "vite build --config vite.config.web.ts && vite build", "start": "node --enable-source-maps dist/index.js", "cli": "node --enable-source-maps dist/index.js", "server": "node --enable-source-maps dist/index.ts", "web": "node --enable-source-maps dist/index.ts web", "dev": "vite-node --watch src/index.ts", "dev:server": "vite-node --watch src/index.ts", "dev:server:stdio": "vite-node --watch src/index.ts -- --protocol stdio", "dev:server:http": "vite-node --watch src/index.ts -- --protocol http", "dev:web": "npm-run-all --parallel dev:web:assets dev:web:bin", "dev:web:bin": "vite-node --watch src/index.ts web", "dev:web:assets": "vite build --config vite.config.web.ts --watch", "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", "test:e2e": "vitest run --config test/vitest.config.ts", "test:e2e:watch": "vitest --config test/vitest.config.ts", "lint": "biome check .", "lint:fix": "biome check . --fix", "format": "biome format . --write", "postinstall": "echo 'Skipping Playwright browser install. See README.md for details.'" };
762
+ const scripts = { "prepare": "husky || true", "build": "vite build --config vite.config.web.ts && vite build", "start": "node --enable-source-maps dist/index.js", "cli": "node --enable-source-maps dist/index.js", "server": "node --enable-source-maps dist/index.ts", "web": "node --enable-source-maps dist/index.ts web", "dev": "npm-run-all --parallel dev:server dev:web", "dev:server": "vite-node --watch src/index.ts", "dev:web": "vite build --config vite.config.web.ts --watch", "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", "test:e2e": "vitest run --config test/vitest.config.ts", "test:e2e:watch": "vitest --config test/vitest.config.ts", "lint": "biome check .", "lint:fix": "biome check . --fix", "format": "biome format . --write", "postinstall": "echo 'Skipping Playwright browser install. See README.md for details.'" };
720
763
  const dependencies = { "@fastify/formbody": "^8.0.2", "@fastify/static": "^8.2.0", "@joplin/turndown-plugin-gfm": "^1.0.62", "@kitajs/html": "^4.2.9", "@kitajs/ts-html-plugin": "^4.1.1", "@langchain/aws": "^0.1.13", "@langchain/google-genai": "^0.2.16", "@langchain/google-vertexai": "^0.2.16", "@langchain/openai": "^0.6.3", "@modelcontextprotocol/sdk": "^1.17.1", "@trpc/client": "^11.4.4", "@trpc/server": "^11.4.4", "alpinejs": "^3.14.9", "axios": "^1.11.0", "axios-retry": "^4.5.0", "better-sqlite3": "^12.2.0", "cheerio": "^1.1.2", "commander": "^14.0.0", "dompurify": "^3.2.6", "dotenv": "^17.2.1", "env-paths": "^3.0.0", "fastify": "^5.4.0", "flowbite": "^3.1.2", "fuse.js": "^7.1.0", "header-generator": "^2.1.69", "htmx.org": "^2.0.6", "iconv-lite": "^0.6.3", "jose": "^6.0.12", "jsdom": "^26.1.0", "langchain": "^0.3.30", "mime": "^4.0.7", "minimatch": "^10.0.1", "playwright": "^1.52.0", "posthog-node": "^5.7.0", "psl": "^1.15.0", "remark": "^15.0.1", "remark-gfm": "^4.0.1", "remark-html": "^16.0.1", "semver": "^7.7.2", "sqlite-vec": "^0.1.7-alpha.2", "tree-sitter": "^0.21.1", "tree-sitter-javascript": "^0.23.1", "tree-sitter-python": "^0.21.0", "tree-sitter-typescript": "^0.23.2", "turndown": "^7.2.0", "zod": "^4.0.14" };
721
764
  const devDependencies = { "@biomejs/biome": "^2.1.3", "@commitlint/cli": "^19.8.1", "@commitlint/config-conventional": "^19.8.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^11.0.3", "@semantic-release/npm": "^12.0.2", "@tailwindcss/postcss": "^4.1.11", "@tailwindcss/vite": "^4.1.11", "@types/alpinejs": "^3.13.11", "@types/better-sqlite3": "^7.6.13", "@types/jsdom": "~21.1.7", "@types/lint-staged": "~13.3.0", "@types/node": "^24.1.0", "@types/node-fetch": "^2.6.13", "@types/psl": "^1.1.3", "@types/semver": "^7.7.0", "@types/turndown": "^5.0.5", "autoprefixer": "^10.4.21", "flowbite-typography": "^1.0.5", "husky": "^9.1.7", "lint-staged": "^16.1.2", "memfs": "^4.34.0", "npm-run-all": "^4.1.5", "postcss": "^8.5.6", "semantic-release": "^24.2.7", "tailwindcss": "^4.1.4", "typescript": "^5.9.2", "vite": "^6.3.5", "vite-node": "^3.1.2", "vite-plugin-dts": "^4.5.4", "vitest": "^3.2.4" };
722
765
  const engines = { "node": ">=20.0.0" };
@@ -1364,6 +1407,17 @@ class RedirectError extends ScraperError {
1364
1407
  this.statusCode = statusCode;
1365
1408
  }
1366
1409
  }
1410
+ class ChallengeError extends ScraperError {
1411
+ constructor(url, statusCode, challengeType) {
1412
+ super(
1413
+ `Challenge detected for ${url} (status: ${statusCode}, type: ${challengeType})`,
1414
+ false
1415
+ );
1416
+ this.url = url;
1417
+ this.statusCode = statusCode;
1418
+ this.challengeType = challengeType;
1419
+ }
1420
+ }
1367
1421
  class MimeTypeUtils {
1368
1422
  /**
1369
1423
  * Parses a Content-Type header string into its MIME type and charset.
@@ -1582,26 +1636,6 @@ class MimeTypeUtils {
1582
1636
  return mimeToLanguage[mimeType] || "";
1583
1637
  }
1584
1638
  }
1585
- let projectRoot = null;
1586
- function getProjectRoot() {
1587
- if (projectRoot) {
1588
- return projectRoot;
1589
- }
1590
- const currentFilePath = fileURLToPath(import.meta.url);
1591
- let currentDir = path.dirname(currentFilePath);
1592
- while (true) {
1593
- const packageJsonPath = path.join(currentDir, "package.json");
1594
- if (fs.existsSync(packageJsonPath)) {
1595
- projectRoot = currentDir;
1596
- return projectRoot;
1597
- }
1598
- const parentDir = path.dirname(currentDir);
1599
- if (parentDir === currentDir) {
1600
- throw new Error("Could not find project root containing package.json.");
1601
- }
1602
- currentDir = parentDir;
1603
- }
1604
- }
1605
1639
  const fullTrim = (str) => {
1606
1640
  return str.replace(/^[\s\r\n\t]+|[\s\r\n\t]+$/g, "");
1607
1641
  };
@@ -1660,6 +1694,132 @@ function extractPrimaryDomain(hostname) {
1660
1694
  const domain = psl.get(hostname.toLowerCase());
1661
1695
  return domain || hostname;
1662
1696
  }
1697
+ class FingerprintGenerator {
1698
+ headerGenerator;
1699
+ /**
1700
+ * Creates an instance of FingerprintGenerator.
1701
+ * @param options Optional configuration for the header generator.
1702
+ */
1703
+ constructor(options) {
1704
+ const defaultOptions = {
1705
+ browsers: [{ name: "chrome", minVersion: 100 }, "firefox", "safari"],
1706
+ devices: ["desktop", "mobile"],
1707
+ operatingSystems: ["windows", "linux", "macos", "android", "ios"],
1708
+ locales: ["en-US", "en"],
1709
+ httpVersion: "2"
1710
+ };
1711
+ this.headerGenerator = new HeaderGenerator({
1712
+ ...defaultOptions,
1713
+ ...options
1714
+ });
1715
+ }
1716
+ /**
1717
+ * Generates a set of realistic HTTP headers.
1718
+ * @returns A set of realistic HTTP headers.
1719
+ */
1720
+ generateHeaders() {
1721
+ return this.headerGenerator.getHeaders();
1722
+ }
1723
+ }
1724
+ class BrowserFetcher {
1725
+ browser = null;
1726
+ page = null;
1727
+ fingerprintGenerator;
1728
+ constructor() {
1729
+ this.fingerprintGenerator = new FingerprintGenerator();
1730
+ }
1731
+ canFetch(source) {
1732
+ return source.startsWith("http://") || source.startsWith("https://");
1733
+ }
1734
+ async fetch(source, options) {
1735
+ try {
1736
+ await this.ensureBrowserReady();
1737
+ if (!this.page) {
1738
+ throw new ScraperError("Failed to create browser page", false);
1739
+ }
1740
+ if (options?.headers) {
1741
+ await this.page.setExtraHTTPHeaders(options.headers);
1742
+ }
1743
+ const timeout = options?.timeout || 3e4;
1744
+ logger.debug(`🌐 Navigating to ${source} with browser...`);
1745
+ const response = await this.page.goto(source, {
1746
+ waitUntil: "networkidle",
1747
+ timeout
1748
+ });
1749
+ if (!response) {
1750
+ throw new ScraperError(`Failed to navigate to ${source}`, false);
1751
+ }
1752
+ if (options?.followRedirects === false && response.status() >= 300 && response.status() < 400) {
1753
+ const location = response.headers().location;
1754
+ if (location) {
1755
+ throw new ScraperError(`Redirect blocked: ${source} -> ${location}`, false);
1756
+ }
1757
+ }
1758
+ const finalUrl = this.page.url();
1759
+ const content = await this.page.content();
1760
+ const contentBuffer = Buffer.from(content, "utf-8");
1761
+ const contentType = response.headers()["content-type"] || "text/html";
1762
+ const { mimeType, charset } = MimeTypeUtils.parseContentType(contentType);
1763
+ return {
1764
+ content: contentBuffer,
1765
+ mimeType,
1766
+ charset,
1767
+ encoding: void 0,
1768
+ // Browser handles encoding automatically
1769
+ source: finalUrl
1770
+ };
1771
+ } catch (error) {
1772
+ if (options?.signal?.aborted) {
1773
+ throw new ScraperError("Browser fetch cancelled", false);
1774
+ }
1775
+ logger.error(`❌ Browser fetch failed for ${source}: ${error}`);
1776
+ throw new ScraperError(
1777
+ `Browser fetch failed for ${source}: ${error instanceof Error ? error.message : String(error)}`,
1778
+ false,
1779
+ error instanceof Error ? error : void 0
1780
+ );
1781
+ }
1782
+ }
1783
+ async ensureBrowserReady() {
1784
+ if (!this.browser) {
1785
+ logger.debug("🚀 Launching browser...");
1786
+ this.browser = await chromium.launch({
1787
+ headless: true,
1788
+ args: [
1789
+ "--no-sandbox",
1790
+ "--disable-setuid-sandbox",
1791
+ "--disable-dev-shm-usage",
1792
+ "--disable-web-security",
1793
+ "--disable-features=site-per-process"
1794
+ ]
1795
+ });
1796
+ }
1797
+ if (!this.page) {
1798
+ this.page = await this.browser.newPage();
1799
+ const dynamicHeaders = this.fingerprintGenerator.generateHeaders();
1800
+ await this.page.setExtraHTTPHeaders(dynamicHeaders);
1801
+ await this.page.setViewportSize({ width: 1920, height: 1080 });
1802
+ }
1803
+ }
1804
+ /**
1805
+ * Close the browser and clean up resources
1806
+ */
1807
+ async close() {
1808
+ try {
1809
+ if (this.page) {
1810
+ await this.page.close();
1811
+ this.page = null;
1812
+ }
1813
+ if (this.browser) {
1814
+ await this.browser.close();
1815
+ this.browser = null;
1816
+ }
1817
+ logger.debug("🔒 Browser closed successfully");
1818
+ } catch (error) {
1819
+ logger.warn(`⚠️ Error closing browser: ${error}`);
1820
+ }
1821
+ }
1822
+ }
1663
1823
  class FileFetcher {
1664
1824
  canFetch(source) {
1665
1825
  return source.startsWith("file://");
@@ -1693,33 +1853,6 @@ class FileFetcher {
1693
1853
  }
1694
1854
  }
1695
1855
  }
1696
- class FingerprintGenerator {
1697
- headerGenerator;
1698
- /**
1699
- * Creates an instance of FingerprintGenerator.
1700
- * @param options Optional configuration for the header generator.
1701
- */
1702
- constructor(options) {
1703
- const defaultOptions = {
1704
- browsers: [{ name: "chrome", minVersion: 100 }, "firefox", "safari"],
1705
- devices: ["desktop", "mobile"],
1706
- operatingSystems: ["windows", "linux", "macos", "android", "ios"],
1707
- locales: ["en-US", "en"],
1708
- httpVersion: "2"
1709
- };
1710
- this.headerGenerator = new HeaderGenerator({
1711
- ...defaultOptions,
1712
- ...options
1713
- });
1714
- }
1715
- /**
1716
- * Generates a set of realistic HTTP headers.
1717
- * @returns A set of realistic HTTP headers.
1718
- */
1719
- generateHeaders() {
1720
- return this.headerGenerator.getHeaders();
1721
- }
1722
- }
1723
1856
  class HttpFetcher {
1724
1857
  retryableStatusCodes = [
1725
1858
  408,
@@ -1854,6 +1987,27 @@ class HttpFetcher {
1854
1987
  throw new RedirectError(source, location, status);
1855
1988
  }
1856
1989
  }
1990
+ if (status === 403) {
1991
+ const cfMitigated = axiosError.response?.headers?.["cf-mitigated"];
1992
+ const server = axiosError.response?.headers?.server;
1993
+ let responseBody = "";
1994
+ if (axiosError.response?.data) {
1995
+ try {
1996
+ if (typeof axiosError.response.data === "string") {
1997
+ responseBody = axiosError.response.data;
1998
+ } else if (Buffer.isBuffer(axiosError.response.data)) {
1999
+ responseBody = axiosError.response.data.toString("utf-8");
2000
+ } else if (axiosError.response.data instanceof ArrayBuffer) {
2001
+ responseBody = Buffer.from(axiosError.response.data).toString("utf-8");
2002
+ }
2003
+ } catch {
2004
+ }
2005
+ }
2006
+ const isCloudflareChallenge = cfMitigated === "challenge" || server === "cloudflare" || responseBody.includes("Enable JavaScript and cookies to continue") || responseBody.includes("Just a moment...") || responseBody.includes("cf_chl_opt");
2007
+ if (isCloudflareChallenge) {
2008
+ throw new ChallengeError(source, status, "cloudflare");
2009
+ }
2010
+ }
1857
2011
  if (attempt < maxRetries && (status === void 0 || this.retryableStatusCodes.includes(status))) {
1858
2012
  const delay = baseDelay * 2 ** attempt;
1859
2013
  logger.warn(
@@ -1875,6 +2029,52 @@ class HttpFetcher {
1875
2029
  );
1876
2030
  }
1877
2031
  }
2032
+ class AutoDetectFetcher {
2033
+ httpFetcher = new HttpFetcher();
2034
+ browserFetcher = new BrowserFetcher();
2035
+ fileFetcher = new FileFetcher();
2036
+ /**
2037
+ * Check if this fetcher can handle the given source.
2038
+ * Returns true for any URL that any of the underlying fetchers can handle.
2039
+ */
2040
+ canFetch(source) {
2041
+ return this.httpFetcher.canFetch(source) || this.browserFetcher.canFetch(source) || this.fileFetcher.canFetch(source);
2042
+ }
2043
+ /**
2044
+ * Fetch content from the source, automatically selecting the appropriate fetcher
2045
+ * and handling fallbacks when challenges are detected.
2046
+ */
2047
+ async fetch(source, options) {
2048
+ if (this.fileFetcher.canFetch(source)) {
2049
+ logger.debug(`Using FileFetcher for: ${source}`);
2050
+ return this.fileFetcher.fetch(source, options);
2051
+ }
2052
+ if (this.httpFetcher.canFetch(source)) {
2053
+ try {
2054
+ logger.debug(`Using HttpFetcher for: ${source}`);
2055
+ return await this.httpFetcher.fetch(source, options);
2056
+ } catch (error) {
2057
+ if (error instanceof ChallengeError) {
2058
+ logger.info(
2059
+ `🔄 Challenge detected for ${source}, falling back to browser fetcher...`
2060
+ );
2061
+ return this.browserFetcher.fetch(source, options);
2062
+ }
2063
+ throw error;
2064
+ }
2065
+ }
2066
+ throw new Error(`No suitable fetcher found for URL: ${source}`);
2067
+ }
2068
+ /**
2069
+ * Close all underlying fetchers to prevent resource leaks.
2070
+ */
2071
+ async close() {
2072
+ await Promise.allSettled([
2073
+ this.browserFetcher.close()
2074
+ // HttpFetcher and FileFetcher don't need explicit cleanup
2075
+ ]);
2076
+ }
2077
+ }
1878
2078
  class SplitterError extends Error {
1879
2079
  }
1880
2080
  class MinimumChunkSizeError extends SplitterError {
@@ -5742,7 +5942,7 @@ class BaseScraperStrategy {
5742
5942
  async cleanup() {
5743
5943
  }
5744
5944
  }
5745
- class GitHubScraperStrategy extends BaseScraperStrategy {
5945
+ class GitHubRepoScraperStrategy extends BaseScraperStrategy {
5746
5946
  httpFetcher = new HttpFetcher();
5747
5947
  pipelines;
5748
5948
  resolvedBranch;
@@ -5776,9 +5976,18 @@ class GitHubScraperStrategy extends BaseScraperStrategy {
5776
5976
  throw new Error(`Invalid GitHub repository URL: ${url}`);
5777
5977
  }
5778
5978
  const [, owner, repo] = match;
5779
- const branchMatch = parsedUrl.pathname.match(/\/tree\/([^/]+)/);
5780
- const branch = branchMatch?.[1];
5781
- return { owner, repo, branch };
5979
+ const segments = parsedUrl.pathname.split("/").filter(Boolean);
5980
+ if (segments.length >= 4 && segments[2] === "blob") {
5981
+ const branch2 = segments[3];
5982
+ const filePath = segments.length > 4 ? segments.slice(4).join("/") : void 0;
5983
+ return { owner, repo, branch: branch2, filePath, isBlob: true };
5984
+ }
5985
+ if (segments.length < 4 || segments[2] !== "tree") {
5986
+ return { owner, repo };
5987
+ }
5988
+ const branch = segments[3];
5989
+ const subPath = segments.length > 4 ? segments.slice(4).join("/") : void 0;
5990
+ return { owner, repo, branch, subPath };
5782
5991
  }
5783
5992
  /**
5784
5993
  * Fetches the repository tree structure from GitHub API.
@@ -5976,13 +6185,24 @@ class GitHubScraperStrategy extends BaseScraperStrategy {
5976
6185
  async processItem(item, options, _progressCallback, signal) {
5977
6186
  const repoInfo = this.parseGitHubUrl(options.url);
5978
6187
  if (item.depth === 0) {
6188
+ if ("isBlob" in repoInfo && repoInfo.isBlob) {
6189
+ if (repoInfo.filePath) {
6190
+ logger.info(
6191
+ `📄 Processing single file: ${repoInfo.owner}/${repoInfo.repo}/${repoInfo.filePath}`
6192
+ );
6193
+ return { links: [`github-file://${repoInfo.filePath}`] };
6194
+ } else {
6195
+ logger.warn(
6196
+ `⚠️ Blob URL without file path: ${options.url}. No files to process.`
6197
+ );
6198
+ return { links: [] };
6199
+ }
6200
+ }
5979
6201
  logger.info(
5980
6202
  `🗂️ Discovering repository structure for ${repoInfo.owner}/${repoInfo.repo}`
5981
6203
  );
5982
6204
  const { tree, resolvedBranch } = await this.fetchRepositoryTree(repoInfo, signal);
5983
- const fileItems = tree.tree.filter(
5984
- (treeItem) => this.shouldProcessFile(treeItem, options)
5985
- );
6205
+ const fileItems = tree.tree.filter((treeItem) => this.isWithinSubPath(treeItem.path, repoInfo.subPath)).filter((treeItem) => this.shouldProcessFile(treeItem, options));
5986
6206
  logger.info(
5987
6207
  `📁 Found ${fileItems.length} processable files in repository (branch: ${resolvedBranch})`
5988
6208
  );
@@ -6016,12 +6236,15 @@ class GitHubScraperStrategy extends BaseScraperStrategy {
6016
6236
  logger.warn(`⚠️ Processing error for ${filePath}: ${err.message}`);
6017
6237
  }
6018
6238
  const githubUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/blob/${this.resolvedBranch || repoInfo.branch || "main"}/${filePath}`;
6239
+ const processedTitle = processed.metadata.title;
6240
+ const hasValidTitle = typeof processedTitle === "string" && processedTitle.trim() !== "";
6241
+ const fallbackTitle = filePath.split("/").pop() || "Untitled";
6019
6242
  return {
6020
6243
  document: {
6021
6244
  content: typeof processed.textContent === "string" ? processed.textContent : "",
6022
6245
  metadata: {
6023
6246
  url: githubUrl,
6024
- title: typeof processed.metadata.title === "string" ? processed.metadata.title : filePath.split("/").pop() || "Untitled",
6247
+ title: hasValidTitle ? processedTitle : fallbackTitle,
6025
6248
  library: options.library,
6026
6249
  version: options.version
6027
6250
  },
@@ -6034,6 +6257,26 @@ class GitHubScraperStrategy extends BaseScraperStrategy {
6034
6257
  }
6035
6258
  return { document: void 0, links: [] };
6036
6259
  }
6260
+ /**
6261
+ * Normalize a path by removing leading and trailing slashes.
6262
+ */
6263
+ normalizePath(path2) {
6264
+ return path2.replace(/^\/+/, "").replace(/\/+$/, "");
6265
+ }
6266
+ isWithinSubPath(path2, subPath) {
6267
+ if (!subPath) {
6268
+ return true;
6269
+ }
6270
+ const trimmedSubPath = this.normalizePath(subPath);
6271
+ if (trimmedSubPath.length === 0) {
6272
+ return true;
6273
+ }
6274
+ const normalizedPath = this.normalizePath(path2);
6275
+ if (normalizedPath === trimmedSubPath) {
6276
+ return true;
6277
+ }
6278
+ return normalizedPath.startsWith(`${trimmedSubPath}/`);
6279
+ }
6037
6280
  async scrape(options, progressCallback, signal) {
6038
6281
  const url = new URL(options.url);
6039
6282
  if (!url.hostname.includes("github.com")) {
@@ -6048,6 +6291,228 @@ class GitHubScraperStrategy extends BaseScraperStrategy {
6048
6291
  await Promise.allSettled(this.pipelines.map((pipeline) => pipeline.close()));
6049
6292
  }
6050
6293
  }
6294
+ class GitHubWikiScraperStrategy extends BaseScraperStrategy {
6295
+ httpFetcher = new HttpFetcher();
6296
+ pipelines;
6297
+ constructor() {
6298
+ super();
6299
+ this.pipelines = PipelineFactory$1.createStandardPipelines();
6300
+ }
6301
+ canHandle(url) {
6302
+ try {
6303
+ const parsedUrl = new URL(url);
6304
+ const { hostname, pathname } = parsedUrl;
6305
+ return ["github.com", "www.github.com"].includes(hostname) && pathname.includes("/wiki") && pathname.match(/^\/([^/]+)\/([^/]+)\/wiki/) !== null;
6306
+ } catch {
6307
+ return false;
6308
+ }
6309
+ }
6310
+ /**
6311
+ * Parses a GitHub wiki URL to extract repository information.
6312
+ */
6313
+ parseGitHubWikiUrl(url) {
6314
+ const parsedUrl = new URL(url);
6315
+ const match = parsedUrl.pathname.match(/^\/([^/]+)\/([^/]+)\/wiki/);
6316
+ if (!match) {
6317
+ throw new Error(`Invalid GitHub wiki URL: ${url}`);
6318
+ }
6319
+ const [, owner, repo] = match;
6320
+ return { owner, repo };
6321
+ }
6322
+ /**
6323
+ * Override shouldProcessUrl to only process URLs within the wiki scope.
6324
+ */
6325
+ shouldProcessUrl(url, options) {
6326
+ try {
6327
+ const parsedUrl = new URL(url);
6328
+ const wikiInfo = this.parseGitHubWikiUrl(options.url);
6329
+ const expectedWikiPath = `/${wikiInfo.owner}/${wikiInfo.repo}/wiki`;
6330
+ if (!parsedUrl.pathname.startsWith(expectedWikiPath)) {
6331
+ return false;
6332
+ }
6333
+ const wikiPagePath = parsedUrl.pathname.replace(expectedWikiPath, "").replace(/^\//, "");
6334
+ return shouldIncludeUrl(
6335
+ wikiPagePath || "Home",
6336
+ options.includePatterns,
6337
+ options.excludePatterns
6338
+ );
6339
+ } catch {
6340
+ return false;
6341
+ }
6342
+ }
6343
+ async processItem(item, options, _progressCallback, signal) {
6344
+ const currentUrl = item.url;
6345
+ logger.info(
6346
+ `📖 Processing wiki page ${this.pageCount}/${options.maxPages}: ${currentUrl}`
6347
+ );
6348
+ try {
6349
+ const rawContent = await this.httpFetcher.fetch(currentUrl, { signal });
6350
+ let processed;
6351
+ for (const pipeline of this.pipelines) {
6352
+ if (pipeline.canProcess(rawContent)) {
6353
+ logger.debug(
6354
+ `Selected ${pipeline.constructor.name} for content type "${rawContent.mimeType}" (${currentUrl})`
6355
+ );
6356
+ const wikiOptions = { ...options, scrapeMode: ScrapeMode.Fetch };
6357
+ processed = await pipeline.process(rawContent, wikiOptions, this.httpFetcher);
6358
+ break;
6359
+ }
6360
+ }
6361
+ if (!processed) {
6362
+ logger.warn(
6363
+ `⚠️ Unsupported content type "${rawContent.mimeType}" for wiki page ${currentUrl}. Skipping processing.`
6364
+ );
6365
+ return { document: void 0, links: [] };
6366
+ }
6367
+ for (const err of processed.errors) {
6368
+ logger.warn(`⚠️ Processing error for ${currentUrl}: ${err.message}`);
6369
+ }
6370
+ const parsedUrl = new URL(currentUrl);
6371
+ const wikiInfo = this.parseGitHubWikiUrl(currentUrl);
6372
+ const wikiPagePath = parsedUrl.pathname.replace(`/${wikiInfo.owner}/${wikiInfo.repo}/wiki`, "").replace(/^\//, "");
6373
+ const pageTitle = wikiPagePath || "Home";
6374
+ const document2 = {
6375
+ content: typeof processed.textContent === "string" ? processed.textContent : "",
6376
+ metadata: {
6377
+ url: currentUrl,
6378
+ title: typeof processed.metadata.title === "string" && processed.metadata.title.trim() !== "" ? processed.metadata.title : pageTitle,
6379
+ library: options.library,
6380
+ version: options.version
6381
+ },
6382
+ contentType: rawContent.mimeType
6383
+ };
6384
+ const links = processed.links || [];
6385
+ const wikiLinks = links.filter((link) => {
6386
+ if (!link || link.trim() === "" || link === "invalid-url" || link === "not-a-url-at-all") {
6387
+ return false;
6388
+ }
6389
+ return true;
6390
+ }).map((link) => {
6391
+ try {
6392
+ return new URL(link, currentUrl).href;
6393
+ } catch {
6394
+ return null;
6395
+ }
6396
+ }).filter((link) => link !== null).filter((link) => {
6397
+ try {
6398
+ const linkUrl = new URL(link);
6399
+ return linkUrl.hostname === parsedUrl.hostname && linkUrl.pathname.startsWith(`/${wikiInfo.owner}/${wikiInfo.repo}/wiki`);
6400
+ } catch {
6401
+ return false;
6402
+ }
6403
+ });
6404
+ return { document: document2, links: wikiLinks };
6405
+ } catch (error) {
6406
+ logger.warn(`⚠️ Failed to process wiki page ${currentUrl}: ${error}`);
6407
+ return { document: void 0, links: [] };
6408
+ }
6409
+ }
6410
+ async scrape(options, progressCallback, signal) {
6411
+ const url = new URL(options.url);
6412
+ if (!url.hostname.includes("github.com") || !url.pathname.includes("/wiki")) {
6413
+ throw new Error("URL must be a GitHub wiki URL");
6414
+ }
6415
+ let startUrl = options.url;
6416
+ if (url.pathname.endsWith("/wiki") || url.pathname.endsWith("/wiki/")) {
6417
+ startUrl = url.pathname.endsWith("/") ? `${options.url}Home` : `${options.url}/Home`;
6418
+ }
6419
+ const wikiOptions = { ...options, url: startUrl };
6420
+ return super.scrape(wikiOptions, progressCallback, signal);
6421
+ }
6422
+ /**
6423
+ * Cleanup resources used by this strategy.
6424
+ */
6425
+ async cleanup() {
6426
+ await Promise.allSettled(this.pipelines.map((pipeline) => pipeline.close()));
6427
+ }
6428
+ }
6429
+ class GitHubScraperStrategy {
6430
+ repoStrategy = new GitHubRepoScraperStrategy();
6431
+ wikiStrategy = new GitHubWikiScraperStrategy();
6432
+ canHandle(url) {
6433
+ try {
6434
+ const parsedUrl = new URL(url);
6435
+ const { hostname, pathname } = parsedUrl;
6436
+ if (!["github.com", "www.github.com"].includes(hostname)) {
6437
+ return false;
6438
+ }
6439
+ const pathMatch = pathname.match(/^\/([^/]+)\/([^/]+)\/?$/);
6440
+ return pathMatch !== null;
6441
+ } catch {
6442
+ return false;
6443
+ }
6444
+ }
6445
+ async scrape(options, progressCallback, signal) {
6446
+ const url = new URL(options.url);
6447
+ if (!url.hostname.includes("github.com")) {
6448
+ throw new Error("URL must be a GitHub URL");
6449
+ }
6450
+ const pathMatch = url.pathname.match(/^\/([^/]+)\/([^/]+)\/?$/);
6451
+ if (!pathMatch) {
6452
+ throw new Error("URL must be a base GitHub repository URL");
6453
+ }
6454
+ const [, owner, repo] = pathMatch;
6455
+ logger.info(`🚀 Starting comprehensive GitHub scraping for ${owner}/${repo}`);
6456
+ let totalPagesDiscovered = 0;
6457
+ let wikiPagesScraped = 0;
6458
+ let wikiCompleted = false;
6459
+ let repoCompleted = false;
6460
+ const mergedProgressCallback = async (progress) => {
6461
+ if (!wikiCompleted) {
6462
+ totalPagesDiscovered = progress.totalDiscovered;
6463
+ wikiPagesScraped = progress.pagesScraped;
6464
+ } else if (!repoCompleted) {
6465
+ progress = {
6466
+ ...progress,
6467
+ pagesScraped: wikiPagesScraped + progress.pagesScraped,
6468
+ totalPages: wikiPagesScraped + progress.totalPages,
6469
+ totalDiscovered: totalPagesDiscovered + progress.totalDiscovered
6470
+ };
6471
+ }
6472
+ await progressCallback(progress);
6473
+ };
6474
+ try {
6475
+ const wikiUrl = `${options.url.replace(/\/$/, "")}/wiki`;
6476
+ const wikiOptions = { ...options, url: wikiUrl };
6477
+ logger.info(`📖 Attempting to scrape wiki for ${owner}/${repo}`);
6478
+ try {
6479
+ await this.wikiStrategy.scrape(wikiOptions, mergedProgressCallback, signal);
6480
+ wikiCompleted = true;
6481
+ logger.info(
6482
+ `✅ Completed wiki scraping for ${owner}/${repo} (${wikiPagesScraped} pages)`
6483
+ );
6484
+ } catch (error) {
6485
+ wikiCompleted = true;
6486
+ logger.info(`ℹ️ Wiki not available or accessible for ${owner}/${repo}: ${error}`);
6487
+ }
6488
+ const maxPages = options.maxPages || 1e3;
6489
+ const remainingPages = Math.max(0, maxPages - wikiPagesScraped);
6490
+ if (remainingPages > 0) {
6491
+ logger.info(
6492
+ `📂 Scraping repository code for ${owner}/${repo} (${remainingPages} pages remaining)`
6493
+ );
6494
+ const repoOptions = { ...options, maxPages: remainingPages };
6495
+ await this.repoStrategy.scrape(repoOptions, mergedProgressCallback, signal);
6496
+ repoCompleted = true;
6497
+ logger.info(`✅ Completed repository code scraping for ${owner}/${repo}`);
6498
+ } else {
6499
+ logger.info(
6500
+ `ℹ️ Skipping repository code scraping - page limit reached with wiki content`
6501
+ );
6502
+ }
6503
+ logger.info(`🎉 Comprehensive GitHub scraping completed for ${owner}/${repo}`);
6504
+ } catch (error) {
6505
+ logger.error(`❌ GitHub scraping failed for ${owner}/${repo}: ${error}`);
6506
+ throw error;
6507
+ }
6508
+ }
6509
+ /**
6510
+ * Cleanup resources used by both underlying strategies.
6511
+ */
6512
+ async cleanup() {
6513
+ await Promise.allSettled([this.repoStrategy.cleanup(), this.wikiStrategy.cleanup()]);
6514
+ }
6515
+ }
6051
6516
  class LocalFileStrategy extends BaseScraperStrategy {
6052
6517
  fileFetcher = new FileFetcher();
6053
6518
  pipelines;
@@ -6112,7 +6577,7 @@ class LocalFileStrategy extends BaseScraperStrategy {
6112
6577
  }
6113
6578
  }
6114
6579
  class WebScraperStrategy extends BaseScraperStrategy {
6115
- httpFetcher = new HttpFetcher();
6580
+ fetcher = new AutoDetectFetcher();
6116
6581
  shouldFollowLinkFn;
6117
6582
  pipelines;
6118
6583
  constructor(options = {}) {
@@ -6146,14 +6611,14 @@ class WebScraperStrategy extends BaseScraperStrategy {
6146
6611
  headers: options.headers
6147
6612
  // Forward custom headers
6148
6613
  };
6149
- const rawContent = await this.httpFetcher.fetch(url, fetchOptions);
6614
+ const rawContent = await this.fetcher.fetch(url, fetchOptions);
6150
6615
  let processed;
6151
6616
  for (const pipeline of this.pipelines) {
6152
6617
  if (pipeline.canProcess(rawContent)) {
6153
6618
  logger.debug(
6154
6619
  `Selected ${pipeline.constructor.name} for content type "${rawContent.mimeType}" (${url})`
6155
6620
  );
6156
- processed = await pipeline.process(rawContent, options, this.httpFetcher);
6621
+ processed = await pipeline.process(rawContent, options, this.fetcher);
6157
6622
  break;
6158
6623
  }
6159
6624
  }
@@ -6202,10 +6667,13 @@ class WebScraperStrategy extends BaseScraperStrategy {
6202
6667
  }
6203
6668
  }
6204
6669
  /**
6205
- * Cleanup resources used by this strategy, specifically the pipeline browser instances.
6670
+ * Cleanup resources used by this strategy, specifically the pipeline browser instances and fetcher.
6206
6671
  */
6207
6672
  async cleanup() {
6208
- await Promise.allSettled(this.pipelines.map((pipeline) => pipeline.close()));
6673
+ await Promise.allSettled([
6674
+ ...this.pipelines.map((pipeline) => pipeline.close()),
6675
+ this.fetcher.close()
6676
+ ]);
6209
6677
  }
6210
6678
  }
6211
6679
  class NpmScraperStrategy {
@@ -6268,6 +6736,7 @@ class ScraperRegistry {
6268
6736
  this.strategies = [
6269
6737
  new NpmScraperStrategy(),
6270
6738
  new PyPiScraperStrategy(),
6739
+ new GitHubWikiScraperStrategy(),
6271
6740
  new GitHubScraperStrategy(),
6272
6741
  new WebScraperStrategy(),
6273
6742
  new LocalFileStrategy()
@@ -7390,14 +7859,6 @@ function parseHeaders(headerOptions) {
7390
7859
  }
7391
7860
  return headers;
7392
7861
  }
7393
- const CLI_DEFAULTS = {
7394
- PROTOCOL: DEFAULT_PROTOCOL,
7395
- HTTP_PORT: DEFAULT_HTTP_PORT,
7396
- WEB_PORT: DEFAULT_WEB_PORT,
7397
- HOST: DEFAULT_HOST,
7398
- MAX_CONCURRENCY: DEFAULT_MAX_CONCURRENCY,
7399
- TELEMETRY: true
7400
- };
7401
7862
  function parseAuthConfig(options) {
7402
7863
  if (!options.authEnabled) {
7403
7864
  return void 0;
@@ -7614,17 +8075,17 @@ class LibraryNotFoundError extends ToolError {
7614
8075
  }
7615
8076
  class FetchUrlTool {
7616
8077
  /**
7617
- * Collection of fetchers that will be tried in order for a given URL.
8078
+ * AutoDetectFetcher handles all URL types and fallback logic automatically.
7618
8079
  */
7619
- fetchers;
8080
+ fetcher;
7620
8081
  /**
7621
8082
  * Collection of pipelines that will be tried in order for processing content.
7622
8083
  * The first pipeline that can process the content type will be used.
7623
8084
  * Currently includes HtmlPipeline, MarkdownPipeline, and TextPipeline (as fallback).
7624
8085
  */
7625
8086
  pipelines;
7626
- constructor(httpFetcher, fileFetcher) {
7627
- this.fetchers = [httpFetcher, fileFetcher];
8087
+ constructor(fetcher) {
8088
+ this.fetcher = fetcher;
7628
8089
  const htmlPipeline = new HtmlPipeline();
7629
8090
  const markdownPipeline = new MarkdownPipeline();
7630
8091
  const textPipeline = new TextPipeline();
@@ -7638,24 +8099,21 @@ class FetchUrlTool {
7638
8099
  */
7639
8100
  async execute(options) {
7640
8101
  const { url, scrapeMode = ScrapeMode.Auto, headers } = options;
7641
- const canFetchResults = this.fetchers.map((f) => f.canFetch(url));
7642
- const fetcherIndex = canFetchResults.indexOf(true);
7643
- if (fetcherIndex === -1) {
8102
+ if (!this.fetcher.canFetch(url)) {
7644
8103
  throw new ToolError(
7645
8104
  `Invalid URL: ${url}. Must be an HTTP/HTTPS URL or a file:// URL.`,
7646
8105
  this.constructor.name
7647
8106
  );
7648
8107
  }
7649
- const fetcher = this.fetchers[fetcherIndex];
7650
- logger.debug(`Using fetcher "${fetcher.constructor.name}" for URL: ${url}`);
7651
8108
  try {
7652
8109
  logger.info(`📡 Fetching ${url}...`);
7653
- const rawContent = await fetcher.fetch(url, {
8110
+ const fetchOptions = {
7654
8111
  followRedirects: options.followRedirects ?? true,
7655
8112
  maxRetries: 3,
7656
8113
  headers
7657
8114
  // propagate custom headers
7658
- });
8115
+ };
8116
+ const rawContent = await this.fetcher.fetch(url, fetchOptions);
7659
8117
  logger.info("🔄 Processing content...");
7660
8118
  let processed;
7661
8119
  for (const pipeline of this.pipelines) {
@@ -7677,7 +8135,7 @@ class FetchUrlTool {
7677
8135
  headers
7678
8136
  // propagate custom headers
7679
8137
  },
7680
- fetcher
8138
+ this.fetcher
7681
8139
  );
7682
8140
  break;
7683
8141
  }
@@ -7717,7 +8175,10 @@ class FetchUrlTool {
7717
8175
  this.constructor.name
7718
8176
  );
7719
8177
  } finally {
7720
- await Promise.allSettled(this.pipelines.map((pipeline) => pipeline.close()));
8178
+ await Promise.allSettled([
8179
+ ...this.pipelines.map((pipeline) => pipeline.close()),
8180
+ this.fetcher.close()
8181
+ ]);
7721
8182
  }
7722
8183
  }
7723
8184
  }
@@ -8551,7 +9012,7 @@ async function initializeTools(docService, pipeline) {
8551
9012
  cancelJob: new CancelJobTool(pipeline),
8552
9013
  // clearCompletedJobs: new ClearCompletedJobsTool(pipeline),
8553
9014
  remove: new RemoveTool(docService, pipeline),
8554
- fetchUrl: new FetchUrlTool(new HttpFetcher(), new FileFetcher())
9015
+ fetchUrl: new FetchUrlTool(new AutoDetectFetcher())
8555
9016
  };
8556
9017
  return tools;
8557
9018
  }
@@ -8909,15 +9370,130 @@ const Layout = ({ title, version: version2, children }) => {
8909
9370
  try {
8910
9371
  const packageJson2 = JSON.parse(readFileSync("package.json", "utf-8"));
8911
9372
  versionString = packageJson2.version;
9373
+ logger.debug(`Resolved version from package.json: ${versionString}`);
8912
9374
  } catch (error) {
8913
9375
  logger.error(`Error reading package.json: ${error}`);
8914
9376
  }
8915
9377
  }
9378
+ const versionInitializer = `versionUpdate({ currentVersion: ${versionString ? `'${versionString}'` : "null"} })`;
8916
9379
  return /* @__PURE__ */ jsxs("html", { lang: "en", children: [
8917
9380
  /* @__PURE__ */ jsxs("head", { children: [
8918
9381
  /* @__PURE__ */ jsx("meta", { charset: "UTF-8" }),
8919
9382
  /* @__PURE__ */ jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
8920
9383
  /* @__PURE__ */ jsx("title", { safe: true, children: title }),
9384
+ /* @__PURE__ */ jsx(
9385
+ "link",
9386
+ {
9387
+ rel: "apple-touch-icon",
9388
+ sizes: "57x57",
9389
+ href: "/apple-icon-57x57.png"
9390
+ }
9391
+ ),
9392
+ /* @__PURE__ */ jsx(
9393
+ "link",
9394
+ {
9395
+ rel: "apple-touch-icon",
9396
+ sizes: "60x60",
9397
+ href: "/apple-icon-60x60.png"
9398
+ }
9399
+ ),
9400
+ /* @__PURE__ */ jsx(
9401
+ "link",
9402
+ {
9403
+ rel: "apple-touch-icon",
9404
+ sizes: "72x72",
9405
+ href: "/apple-icon-72x72.png"
9406
+ }
9407
+ ),
9408
+ /* @__PURE__ */ jsx(
9409
+ "link",
9410
+ {
9411
+ rel: "apple-touch-icon",
9412
+ sizes: "76x76",
9413
+ href: "/apple-icon-76x76.png"
9414
+ }
9415
+ ),
9416
+ /* @__PURE__ */ jsx(
9417
+ "link",
9418
+ {
9419
+ rel: "apple-touch-icon",
9420
+ sizes: "114x114",
9421
+ href: "/apple-icon-114x114.png"
9422
+ }
9423
+ ),
9424
+ /* @__PURE__ */ jsx(
9425
+ "link",
9426
+ {
9427
+ rel: "apple-touch-icon",
9428
+ sizes: "120x120",
9429
+ href: "/apple-icon-120x120.png"
9430
+ }
9431
+ ),
9432
+ /* @__PURE__ */ jsx(
9433
+ "link",
9434
+ {
9435
+ rel: "apple-touch-icon",
9436
+ sizes: "144x144",
9437
+ href: "/apple-icon-144x144.png"
9438
+ }
9439
+ ),
9440
+ /* @__PURE__ */ jsx(
9441
+ "link",
9442
+ {
9443
+ rel: "apple-touch-icon",
9444
+ sizes: "152x152",
9445
+ href: "/apple-icon-152x152.png"
9446
+ }
9447
+ ),
9448
+ /* @__PURE__ */ jsx(
9449
+ "link",
9450
+ {
9451
+ rel: "apple-touch-icon",
9452
+ sizes: "180x180",
9453
+ href: "/apple-icon-180x180.png"
9454
+ }
9455
+ ),
9456
+ /* @__PURE__ */ jsx(
9457
+ "link",
9458
+ {
9459
+ rel: "icon",
9460
+ type: "image/png",
9461
+ sizes: "192x192",
9462
+ href: "/android-icon-192x192.png"
9463
+ }
9464
+ ),
9465
+ /* @__PURE__ */ jsx(
9466
+ "link",
9467
+ {
9468
+ rel: "icon",
9469
+ type: "image/png",
9470
+ sizes: "32x32",
9471
+ href: "/favicon-32x32.png"
9472
+ }
9473
+ ),
9474
+ /* @__PURE__ */ jsx(
9475
+ "link",
9476
+ {
9477
+ rel: "icon",
9478
+ type: "image/png",
9479
+ sizes: "96x96",
9480
+ href: "/favicon-96x96.png"
9481
+ }
9482
+ ),
9483
+ /* @__PURE__ */ jsx(
9484
+ "link",
9485
+ {
9486
+ rel: "icon",
9487
+ type: "image/png",
9488
+ sizes: "16x16",
9489
+ href: "/favicon-16x16.png"
9490
+ }
9491
+ ),
9492
+ /* @__PURE__ */ jsx("link", { rel: "shortcut icon", href: "/favicon.ico" }),
9493
+ /* @__PURE__ */ jsx("link", { rel: "manifest", href: "/manifest.json" }),
9494
+ /* @__PURE__ */ jsx("meta", { name: "msapplication-TileColor", content: "#ffffff" }),
9495
+ /* @__PURE__ */ jsx("meta", { name: "msapplication-TileImage", content: "/ms-icon-144x144.png" }),
9496
+ /* @__PURE__ */ jsx("meta", { name: "theme-color", content: "#ffffff" }),
8921
9497
  /* @__PURE__ */ jsx("link", { rel: "stylesheet", href: "/assets/main.css" }),
8922
9498
  /* @__PURE__ */ jsx("style", { children: `
8923
9499
  .htmx-indicator {
@@ -8941,27 +9517,143 @@ const Layout = ({ title, version: version2, children }) => {
8941
9517
  form .htmx-indicator .spinner { display: flex; }
8942
9518
  form .htmx-indicator .search-text { display: none; }
8943
9519
  form .spinner { display: none; }
8944
- ` })
9520
+ ` })
8945
9521
  ] }),
8946
9522
  /* @__PURE__ */ jsxs("body", { class: "bg-gray-50 dark:bg-gray-900", children: [
8947
- /* @__PURE__ */ jsxs("div", { class: "container max-w-2xl mx-auto px-4 py-4", children: [
8948
- /* @__PURE__ */ jsx("header", { class: "mb-4", children: /* @__PURE__ */ jsxs("h1", { class: "text-3xl font-bold text-gray-900 dark:text-white", children: [
8949
- /* @__PURE__ */ jsx("a", { href: "/", children: "MCP Docs" }),
8950
- versionString ? /* @__PURE__ */ jsxs(
8951
- "span",
8952
- {
8953
- safe: true,
8954
- class: "ml-2 text-base font-normal text-gray-500 dark:text-gray-400 align-baseline",
8955
- title: `Version ${versionString}`,
8956
- children: [
8957
- "v",
8958
- versionString
8959
- ]
8960
- }
8961
- ) : null
8962
- ] }) }),
8963
- /* @__PURE__ */ jsx("main", { children })
8964
- ] }),
9523
+ /* @__PURE__ */ jsx(
9524
+ "header",
9525
+ {
9526
+ class: "bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700",
9527
+ "x-data": versionInitializer,
9528
+ "x-init": "queueCheck()",
9529
+ children: /* @__PURE__ */ jsxs("div", { class: "container max-w-2xl mx-auto px-4 py-4", children: [
9530
+ /* @__PURE__ */ jsxs("div", { class: "hidden sm:flex items-center justify-between", children: [
9531
+ /* @__PURE__ */ jsxs("div", { class: "flex items-center gap-3", children: [
9532
+ /* @__PURE__ */ jsxs(
9533
+ "a",
9534
+ {
9535
+ href: "https://grounded.tools",
9536
+ target: "_blank",
9537
+ rel: "noopener noreferrer",
9538
+ class: "text-xl font-medium text-gray-900 dark:text-white hover:text-primary-500 dark:hover:text-primary-400 transition-colors font-brand",
9539
+ children: [
9540
+ /* @__PURE__ */ jsx("span", { class: "text-primary-600 dark:text-primary-300", children: "grounded" }),
9541
+ /* @__PURE__ */ jsx("span", { class: "text-accent-500", children: "." }),
9542
+ /* @__PURE__ */ jsx("span", { class: "text-gray-900 dark:text-gray-100", children: "tools" })
9543
+ ]
9544
+ }
9545
+ ),
9546
+ /* @__PURE__ */ jsx("span", { class: "text-gray-400 dark:text-gray-400", children: "|" }),
9547
+ /* @__PURE__ */ jsx(
9548
+ "a",
9549
+ {
9550
+ href: "/",
9551
+ class: "text-lg font-semibold text-gray-900 dark:text-white hover:text-primary-500 dark:hover:text-primary-400 transition-colors font-brand",
9552
+ children: "Docs MCP Server"
9553
+ }
9554
+ ),
9555
+ versionString ? /* @__PURE__ */ jsxs(
9556
+ "span",
9557
+ {
9558
+ safe: true,
9559
+ class: "text-sm font-normal text-gray-500 dark:text-slate-400",
9560
+ title: `Version ${versionString}`,
9561
+ children: [
9562
+ "v",
9563
+ versionString
9564
+ ]
9565
+ }
9566
+ ) : null
9567
+ ] }),
9568
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
9569
+ "span",
9570
+ {
9571
+ "x-show": "hasUpdate",
9572
+ "x-cloak": true,
9573
+ class: "inline-flex items-center gap-2 rounded-full bg-amber-100 dark:bg-amber-500/20 px-3 py-1.5 text-sm font-medium text-amber-700 dark:text-amber-300 border border-amber-200 dark:border-amber-500/30",
9574
+ role: "status",
9575
+ "aria-live": "polite",
9576
+ children: [
9577
+ /* @__PURE__ */ jsx("span", { class: "flex h-4 w-4 items-center justify-center rounded-full bg-amber-500 text-amber-800 dark:text-amber-900 text-xs font-bold", children: "!" }),
9578
+ /* @__PURE__ */ jsx(
9579
+ "a",
9580
+ {
9581
+ "x-bind:href": "latestReleaseUrl",
9582
+ target: "_blank",
9583
+ rel: "noopener noreferrer",
9584
+ class: "hover:text-amber-800 dark:hover:text-amber-200 transition-colors",
9585
+ children: /* @__PURE__ */ jsx("span", { class: "mr-1", children: "Update available" })
9586
+ }
9587
+ )
9588
+ ]
9589
+ }
9590
+ ) })
9591
+ ] }),
9592
+ /* @__PURE__ */ jsxs("div", { class: "sm:hidden space-y-2", children: [
9593
+ /* @__PURE__ */ jsx("div", { class: "flex justify-center", children: /* @__PURE__ */ jsxs(
9594
+ "a",
9595
+ {
9596
+ href: "https://grounded.tools",
9597
+ target: "_blank",
9598
+ rel: "noopener noreferrer",
9599
+ class: "text-xl font-medium text-gray-900 dark:text-white hover:text-primary-500 dark:hover:text-primary-400 transition-colors font-brand",
9600
+ children: [
9601
+ /* @__PURE__ */ jsx("span", { class: "text-primary-600 dark:text-primary-300", children: "grounded" }),
9602
+ /* @__PURE__ */ jsx("span", { class: "text-accent-500", children: "." }),
9603
+ /* @__PURE__ */ jsx("span", { class: "text-gray-900 dark:text-gray-100", children: "tools" })
9604
+ ]
9605
+ }
9606
+ ) }),
9607
+ /* @__PURE__ */ jsxs("div", { class: "flex items-center justify-center gap-2", children: [
9608
+ /* @__PURE__ */ jsx(
9609
+ "a",
9610
+ {
9611
+ href: "/",
9612
+ class: "text-lg font-semibold text-gray-900 dark:text-white hover:text-primary-500 dark:hover:text-primary-400 transition-colors font-brand",
9613
+ children: "Docs MCP Server"
9614
+ }
9615
+ ),
9616
+ versionString ? /* @__PURE__ */ jsxs(
9617
+ "span",
9618
+ {
9619
+ safe: true,
9620
+ class: "text-sm font-normal text-gray-500 dark:text-slate-400",
9621
+ title: `Version ${versionString}`,
9622
+ children: [
9623
+ "v",
9624
+ versionString
9625
+ ]
9626
+ }
9627
+ ) : null
9628
+ ] }),
9629
+ /* @__PURE__ */ jsx("div", { class: "flex justify-center", children: /* @__PURE__ */ jsxs(
9630
+ "span",
9631
+ {
9632
+ "x-show": "hasUpdate",
9633
+ "x-cloak": true,
9634
+ class: "inline-flex items-center gap-2 rounded-full bg-amber-100 dark:bg-amber-500/20 px-3 py-1.5 text-sm font-medium text-amber-700 dark:text-amber-300 border border-amber-200 dark:border-amber-500/30",
9635
+ role: "status",
9636
+ "aria-live": "polite",
9637
+ children: [
9638
+ /* @__PURE__ */ jsx("span", { class: "flex h-4 w-4 items-center justify-center rounded-full bg-amber-500 text-amber-800 dark:text-amber-900 text-xs font-bold", children: "!" }),
9639
+ /* @__PURE__ */ jsx(
9640
+ "a",
9641
+ {
9642
+ "x-bind:href": "latestReleaseUrl",
9643
+ target: "_blank",
9644
+ rel: "noopener noreferrer",
9645
+ class: "hover:text-amber-800 dark:hover:text-amber-200 transition-colors",
9646
+ children: /* @__PURE__ */ jsx("span", { class: "mr-1", children: "Update available" })
9647
+ }
9648
+ )
9649
+ ]
9650
+ }
9651
+ ) })
9652
+ ] })
9653
+ ] })
9654
+ }
9655
+ ),
9656
+ /* @__PURE__ */ jsx("div", { class: "container max-w-2xl mx-auto px-4 py-6", children: /* @__PURE__ */ jsx("main", { children }) }),
8965
9657
  /* @__PURE__ */ jsx("script", { type: "module", src: "/assets/main.js" })
8966
9658
  ] })
8967
9659
  ] });
@@ -9054,7 +9746,7 @@ const VersionBadge = ({ version: version2 }) => {
9054
9746
  if (!version2) {
9055
9747
  return null;
9056
9748
  }
9057
- return /* @__PURE__ */ jsx("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", children: /* @__PURE__ */ jsx("span", { safe: true, children: version2 }) });
9749
+ return /* @__PURE__ */ jsx("span", { class: "bg-primary-100 text-primary-800 text-xs font-medium me-2 px-1.5 py-0.5 rounded dark:bg-primary-900 dark:text-primary-300", children: /* @__PURE__ */ jsx("span", { safe: true, children: version2 }) });
9058
9750
  };
9059
9751
  function getStatusClasses(status) {
9060
9752
  const baseClasses = "px-1.5 py-0.5 text-xs font-medium rounded";
@@ -9463,7 +10155,7 @@ const ScrapeFormContent = ({
9463
10155
  "x-model": "url",
9464
10156
  "x-on:input": "checkUrlPath",
9465
10157
  "x-on:paste": "$nextTick(() => checkUrlPath())",
9466
- 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"
10158
+ 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-primary-500 focus:border-primary-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
9467
10159
  }
9468
10160
  ),
9469
10161
  /* @__PURE__ */ jsx(
@@ -9504,7 +10196,7 @@ const ScrapeFormContent = ({
9504
10196
  name: "library",
9505
10197
  id: "library",
9506
10198
  required: true,
9507
- 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"
10199
+ 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-primary-500 focus:border-primary-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
9508
10200
  }
9509
10201
  )
9510
10202
  ] }),
@@ -9526,7 +10218,7 @@ const ScrapeFormContent = ({
9526
10218
  type: "text",
9527
10219
  name: "version",
9528
10220
  id: "version",
9529
- 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"
10221
+ 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-primary-500 focus:border-primary-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
9530
10222
  }
9531
10223
  )
9532
10224
  ] }),
@@ -9553,7 +10245,7 @@ const ScrapeFormContent = ({
9553
10245
  id: "maxPages",
9554
10246
  min: "1",
9555
10247
  placeholder: "1000",
9556
- 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"
10248
+ 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-primary-500 focus:border-primary-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
9557
10249
  }
9558
10250
  )
9559
10251
  ] }),
@@ -9577,7 +10269,7 @@ const ScrapeFormContent = ({
9577
10269
  id: "maxDepth",
9578
10270
  min: "0",
9579
10271
  placeholder: "3",
9580
- 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"
10272
+ 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-primary-500 focus:border-primary-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
9581
10273
  }
9582
10274
  )
9583
10275
  ] }),
@@ -9610,7 +10302,7 @@ const ScrapeFormContent = ({
9610
10302
  {
9611
10303
  name: "scope",
9612
10304
  id: "scope",
9613
- 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",
10305
+ 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-primary-500 focus:border-primary-500 sm:text-sm rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",
9614
10306
  children: [
9615
10307
  /* @__PURE__ */ jsx("option", { value: "subpages", selected: true, children: "Subpages (Default)" }),
9616
10308
  /* @__PURE__ */ jsx("option", { value: "hostname", children: "Hostname" }),
@@ -9638,7 +10330,7 @@ const ScrapeFormContent = ({
9638
10330
  id: "includePatterns",
9639
10331
  rows: "2",
9640
10332
  placeholder: "e.g. docs/* or /api\\/v1.*/",
9641
- 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"
10333
+ 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-primary-500 focus:border-primary-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
9642
10334
  }
9643
10335
  )
9644
10336
  ] }),
@@ -9661,7 +10353,7 @@ const ScrapeFormContent = ({
9661
10353
  id: "excludePatterns",
9662
10354
  rows: "5",
9663
10355
  safe: true,
9664
- 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 font-mono text-xs",
10356
+ 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-primary-500 focus:border-primary-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-xs",
9665
10357
  children: defaultExcludePatternsText
9666
10358
  }
9667
10359
  ),
@@ -9693,7 +10385,7 @@ const ScrapeFormContent = ({
9693
10385
  {
9694
10386
  name: "scrapeMode",
9695
10387
  id: "scrapeMode",
9696
- 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",
10388
+ 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-primary-500 focus:border-primary-500 sm:text-sm rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",
9697
10389
  children: [
9698
10390
  /* @__PURE__ */ jsx("option", { value: ScrapeMode.Auto, selected: true, children: "Auto (Default)" }),
9699
10391
  /* @__PURE__ */ jsx("option", { value: ScrapeMode.Fetch, children: "Fetch" }),
@@ -9752,7 +10444,7 @@ const ScrapeFormContent = ({
9752
10444
  "button",
9753
10445
  {
9754
10446
  type: "button",
9755
- 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",
10447
+ class: "mt-1 px-2 py-0.5 bg-primary-100 dark:bg-primary-900 text-primary-700 dark:text-primary-200 rounded text-xs",
9756
10448
  "x-on:click": "headers.push({ name: '', value: '' })",
9757
10449
  children: "+ Add Header"
9758
10450
  }
@@ -9767,7 +10459,7 @@ const ScrapeFormContent = ({
9767
10459
  name: "followRedirects",
9768
10460
  type: "checkbox",
9769
10461
  checked: true,
9770
- 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"
10462
+ class: "h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700"
9771
10463
  }
9772
10464
  ),
9773
10465
  /* @__PURE__ */ jsx(
@@ -9787,7 +10479,7 @@ const ScrapeFormContent = ({
9787
10479
  name: "ignoreErrors",
9788
10480
  type: "checkbox",
9789
10481
  checked: true,
9790
- 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"
10482
+ class: "h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700"
9791
10483
  }
9792
10484
  ),
9793
10485
  /* @__PURE__ */ jsx(
@@ -9805,7 +10497,7 @@ const ScrapeFormContent = ({
9805
10497
  "button",
9806
10498
  {
9807
10499
  type: "submit",
9808
- 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",
10500
+ 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-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500",
9809
10501
  children: "Queue Job"
9810
10502
  }
9811
10503
  ) })
@@ -10022,6 +10714,7 @@ const VersionDetailsRow = ({
10022
10714
  "span",
10023
10715
  {
10024
10716
  "x-show": `$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && !$store.confirmingAction.isDeleting`,
10717
+ class: "mx-1",
10025
10718
  children: [
10026
10719
  "Confirm?",
10027
10720
  /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Confirm delete" })
@@ -10046,34 +10739,48 @@ const VersionDetailsRow = ({
10046
10739
  )
10047
10740
  );
10048
10741
  };
10049
- const LibraryDetailCard = ({ library }) => (
10050
- // Use Flowbite Card structure with updated padding and border, and white background
10051
- /* @__PURE__ */ jsxs("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", children: [
10052
- /* @__PURE__ */ jsx("h3", { class: "text-lg font-medium text-gray-900 dark:text-white mb-1", children: /* @__PURE__ */ jsx("span", { safe: true, children: library.name }) }),
10053
- /* @__PURE__ */ jsx("div", { class: "mt-1", children: library.versions.length > 0 ? library.versions.map((v) => {
10054
- const adapted = {
10055
- id: -1,
10056
- ref: { library: library.name, version: v.version },
10057
- status: v.status,
10058
- progress: v.progress,
10059
- counts: {
10060
- documents: v.documentCount,
10061
- uniqueUrls: v.uniqueUrlCount
10062
- },
10063
- indexedAt: v.indexedAt,
10064
- sourceUrl: v.sourceUrl ?? void 0
10065
- };
10066
- return /* @__PURE__ */ jsx(
10067
- VersionDetailsRow,
10742
+ const LibraryDetailCard = ({ library }) => {
10743
+ const versions = library.versions?.reverse() || [];
10744
+ const latestVersion = versions[0];
10745
+ return (
10746
+ // Use Flowbite Card structure with updated padding and border, and white background
10747
+ /* @__PURE__ */ jsxs("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", children: [
10748
+ /* @__PURE__ */ jsx("h3", { class: "text-lg font-medium text-gray-900 dark:text-white mb-1", children: /* @__PURE__ */ jsx("span", { safe: true, children: library.name }) }),
10749
+ latestVersion?.sourceUrl ? /* @__PURE__ */ jsx("div", { class: "text-sm text-gray-500 dark:text-gray-400", children: /* @__PURE__ */ jsx(
10750
+ "a",
10068
10751
  {
10069
- libraryName: library.name,
10070
- version: adapted,
10071
- showDelete: false
10752
+ href: latestVersion.sourceUrl,
10753
+ target: "_blank",
10754
+ class: "hover:underline",
10755
+ safe: true,
10756
+ children: latestVersion.sourceUrl
10072
10757
  }
10073
- );
10074
- }) : /* @__PURE__ */ jsx("p", { class: "text-sm text-gray-500 dark:text-gray-400 italic", children: "No versions indexed." }) })
10075
- ] })
10076
- );
10758
+ ) }) : null,
10759
+ /* @__PURE__ */ jsx("div", { class: "mt-2", children: versions.length > 0 ? versions.map((v) => {
10760
+ const adapted = {
10761
+ id: -1,
10762
+ ref: { library: library.name, version: v.version },
10763
+ status: v.status,
10764
+ progress: v.progress,
10765
+ counts: {
10766
+ documents: v.documentCount,
10767
+ uniqueUrls: v.uniqueUrlCount
10768
+ },
10769
+ indexedAt: v.indexedAt,
10770
+ sourceUrl: v.sourceUrl ?? void 0
10771
+ };
10772
+ return /* @__PURE__ */ jsx(
10773
+ VersionDetailsRow,
10774
+ {
10775
+ libraryName: library.name,
10776
+ version: adapted,
10777
+ showDelete: false
10778
+ }
10779
+ );
10780
+ }) : /* @__PURE__ */ jsx("p", { class: "text-sm text-gray-500 dark:text-gray-400 italic", children: "No versions indexed." }) })
10781
+ ] })
10782
+ );
10783
+ };
10077
10784
  const LibrarySearchCard = ({ library }) => {
10078
10785
  return /* @__PURE__ */ jsxs("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", children: [
10079
10786
  /* @__PURE__ */ jsxs("h2", { class: "text-xl font-semibold mb-2 text-gray-900 dark:text-white", safe: true, children: [
@@ -10236,37 +10943,51 @@ function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool) {
10236
10943
  }
10237
10944
  );
10238
10945
  }
10239
- const LibraryItem = ({ library }) => (
10240
- // Use Flowbite Card structure with updated padding and border, and white background
10241
- /* @__PURE__ */ jsxs("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", children: [
10242
- /* @__PURE__ */ jsx("h3", { class: "text-lg font-medium text-gray-900 dark:text-white mb-1", children: /* @__PURE__ */ jsx(
10243
- "a",
10244
- {
10245
- href: `/libraries/${encodeURIComponent(library.name)}`,
10246
- class: "hover:underline",
10247
- children: /* @__PURE__ */ jsx("span", { safe: true, children: library.name })
10248
- }
10249
- ) }),
10250
- /* @__PURE__ */ jsx("div", { class: "mt-1", children: library.versions.length > 0 ? library.versions.map((v) => {
10251
- const adapted = {
10252
- id: -1,
10253
- ref: { library: library.name, version: v.version },
10254
- status: v.status,
10255
- progress: v.progress,
10256
- counts: {
10257
- documents: v.documentCount,
10258
- uniqueUrls: v.uniqueUrlCount
10259
- },
10260
- indexedAt: v.indexedAt,
10261
- sourceUrl: v.sourceUrl ?? void 0
10262
- };
10263
- return /* @__PURE__ */ jsx(VersionDetailsRow, { libraryName: library.name, version: adapted });
10264
- }) : (
10265
- // Display message if no versions are indexed
10266
- /* @__PURE__ */ jsx("p", { class: "text-sm text-gray-500 dark:text-gray-400 italic", children: "No versions indexed." })
10267
- ) })
10268
- ] })
10269
- );
10946
+ const LibraryItem = ({ library }) => {
10947
+ const versions = library.versions?.reverse() || [];
10948
+ const latestVersion = versions[0];
10949
+ return (
10950
+ // Use Flowbite Card structure with updated padding and border, and white background
10951
+ /* @__PURE__ */ jsxs("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", children: [
10952
+ /* @__PURE__ */ jsx("h3", { class: "text-lg font-medium text-gray-900 dark:text-white", children: /* @__PURE__ */ jsx(
10953
+ "a",
10954
+ {
10955
+ href: `/libraries/${encodeURIComponent(library.name)}`,
10956
+ class: "hover:underline",
10957
+ children: /* @__PURE__ */ jsx("span", { safe: true, children: library.name })
10958
+ }
10959
+ ) }),
10960
+ latestVersion?.sourceUrl ? /* @__PURE__ */ jsx("div", { class: "text-sm text-gray-500 dark:text-gray-400", children: /* @__PURE__ */ jsx(
10961
+ "a",
10962
+ {
10963
+ href: latestVersion.sourceUrl,
10964
+ target: "_blank",
10965
+ class: "hover:underline",
10966
+ safe: true,
10967
+ children: latestVersion.sourceUrl
10968
+ }
10969
+ ) }) : null,
10970
+ /* @__PURE__ */ jsx("div", { class: "mt-2", children: versions.length > 0 ? versions.map((v) => {
10971
+ const adapted = {
10972
+ id: -1,
10973
+ ref: { library: library.name, version: v.version },
10974
+ status: v.status,
10975
+ progress: v.progress,
10976
+ counts: {
10977
+ documents: v.documentCount,
10978
+ uniqueUrls: v.uniqueUrlCount
10979
+ },
10980
+ indexedAt: v.indexedAt,
10981
+ sourceUrl: v.sourceUrl ?? void 0
10982
+ };
10983
+ return /* @__PURE__ */ jsx(VersionDetailsRow, { libraryName: library.name, version: adapted });
10984
+ }) : (
10985
+ // Display message if no versions are indexed
10986
+ /* @__PURE__ */ jsx("p", { class: "text-sm text-gray-500 dark:text-gray-400 italic", children: "No versions indexed." })
10987
+ ) })
10988
+ ] })
10989
+ );
10990
+ };
10270
10991
  const LibraryList = ({ libraries }) => {
10271
10992
  return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", { class: "space-y-2", children: libraries.map((library) => /* @__PURE__ */ jsx(LibraryItem, { library })) }) });
10272
10993
  };
@@ -12540,34 +13261,10 @@ class DocumentManagementService {
12540
13261
  normalizeVersion(version2) {
12541
13262
  return (version2 ?? "").toLowerCase();
12542
13263
  }
12543
- constructor(embeddingConfig, pipelineConfig, storePath) {
12544
- let dbPath;
12545
- let dbDir;
12546
- if (storePath) {
12547
- dbDir = storePath;
12548
- dbPath = path.join(dbDir, "documents.db");
12549
- logger.debug(`Using database directory from storePath parameter: ${dbDir}`);
12550
- } else {
12551
- const projectRoot2 = getProjectRoot();
12552
- const oldDbDir = path.join(projectRoot2, ".store");
12553
- const oldDbPath = path.join(oldDbDir, "documents.db");
12554
- const oldDbExists = fs.existsSync(oldDbPath);
12555
- if (oldDbExists) {
12556
- dbPath = oldDbPath;
12557
- dbDir = oldDbDir;
12558
- logger.debug(`Using legacy database path: ${dbPath}`);
12559
- } else {
12560
- const standardPaths = envPaths("docs-mcp-server", { suffix: "" });
12561
- dbDir = standardPaths.data;
12562
- dbPath = path.join(dbDir, "documents.db");
12563
- logger.debug(`Using standard database directory: ${dbDir}`);
12564
- }
12565
- }
12566
- try {
12567
- fs.mkdirSync(dbDir, { recursive: true });
12568
- } catch (error) {
12569
- logger.error(`⚠️ Failed to create database directory ${dbDir}: ${error}`);
12570
- }
13264
+ constructor(storePath, embeddingConfig, pipelineConfig) {
13265
+ const dbDir = storePath;
13266
+ const dbPath = path.join(dbDir, "documents.db");
13267
+ logger.debug(`Using database directory: ${dbDir}`);
12571
13268
  this.store = new DocumentStore(dbPath, embeddingConfig);
12572
13269
  this.documentRetriever = new DocumentRetrieverService(this.store);
12573
13270
  this.pipelines = PipelineFactory$1.createStandardPipelines(pipelineConfig);
@@ -12922,16 +13619,19 @@ async function createDocumentManagement(options = {}) {
12922
13619
  await client.initialize();
12923
13620
  return client;
12924
13621
  }
13622
+ if (!options.storePath) {
13623
+ throw new Error("storePath is required when not using a remote server");
13624
+ }
12925
13625
  const service = new DocumentManagementService(
13626
+ options.storePath,
12926
13627
  options.embeddingConfig,
12927
- void 0,
12928
- options.storePath
13628
+ void 0
12929
13629
  );
12930
13630
  await service.initialize();
12931
13631
  return service;
12932
13632
  }
12933
- async function createLocalDocumentManagement(embeddingConfig, storePath) {
12934
- const service = new DocumentManagementService(embeddingConfig, void 0, storePath);
13633
+ async function createLocalDocumentManagement(storePath, embeddingConfig) {
13634
+ const service = new DocumentManagementService(storePath, embeddingConfig, void 0);
12935
13635
  await service.initialize();
12936
13636
  return service;
12937
13637
  }
@@ -12939,7 +13639,7 @@ function createDefaultAction(program) {
12939
13639
  return program.addOption(
12940
13640
  new Option("--protocol <protocol>", "Protocol for MCP server").env("DOCS_MCP_PROTOCOL").default("auto").choices(["auto", "stdio", "http"])
12941
13641
  ).addOption(
12942
- new Option("--port <number>", "Port for the server").env("DOCS_MCP_PORT").env("PORT").default(CLI_DEFAULTS.HTTP_PORT.toString()).argParser((v) => {
13642
+ new Option("--port <number>", "Port for the server").env("DOCS_MCP_PORT").env("PORT").default(DEFAULT_HTTP_PORT.toString()).argParser((v) => {
12943
13643
  const n = Number(v);
12944
13644
  if (!Number.isInteger(n) || n < 1 || n > 65535) {
12945
13645
  throw new Error("Port must be an integer between 1 and 65535");
@@ -12947,7 +13647,7 @@ function createDefaultAction(program) {
12947
13647
  return String(n);
12948
13648
  })
12949
13649
  ).addOption(
12950
- new Option("--host <host>", "Host to bind the server to").env("DOCS_MCP_HOST").env("HOST").default(CLI_DEFAULTS.HOST).argParser(validateHost)
13650
+ new Option("--host <host>", "Host to bind the server to").env("DOCS_MCP_HOST").env("HOST").default(DEFAULT_HOST).argParser(validateHost)
12951
13651
  ).addOption(
12952
13652
  new Option(
12953
13653
  "--embedding-model <model>",
@@ -13004,12 +13704,12 @@ function createDefaultAction(program) {
13004
13704
  validateAuthConfig(authConfig);
13005
13705
  warnHttpUsage(authConfig, port);
13006
13706
  }
13007
- const globalOptions = program.parent?.opts() || {};
13707
+ const globalOptions = program.opts();
13008
13708
  ensurePlaywrightBrowsersInstalled();
13009
13709
  const embeddingConfig = resolveEmbeddingContext(options.embeddingModel);
13010
13710
  const docService = await createLocalDocumentManagement(
13011
- embeddingConfig,
13012
- globalOptions.storePath
13711
+ globalOptions.storePath,
13712
+ embeddingConfig
13013
13713
  );
13014
13714
  const pipelineOptions = {
13015
13715
  recoverJobs: options.resume || false,
@@ -13070,7 +13770,7 @@ async function fetchUrlAction(url, options) {
13070
13770
  hasHeaders: options.header.length > 0
13071
13771
  });
13072
13772
  const headers = parseHeaders(options.header);
13073
- const fetchUrlTool = new FetchUrlTool(new HttpFetcher(), new FileFetcher());
13773
+ const fetchUrlTool = new FetchUrlTool(new AutoDetectFetcher());
13074
13774
  const content = await fetchUrlTool.execute({
13075
13775
  url,
13076
13776
  followRedirects: options.followRedirects,
@@ -13160,9 +13860,9 @@ function createListCommand(program) {
13160
13860
  }
13161
13861
  function createMcpCommand(program) {
13162
13862
  return program.command("mcp").description("Start MCP server only").addOption(
13163
- new Option("--protocol <protocol>", "Protocol for MCP server").env("DOCS_MCP_PROTOCOL").default(CLI_DEFAULTS.PROTOCOL).choices(["auto", "stdio", "http"])
13863
+ new Option("--protocol <protocol>", "Protocol for MCP server").env("DOCS_MCP_PROTOCOL").default(DEFAULT_PROTOCOL).choices(["auto", "stdio", "http"])
13164
13864
  ).addOption(
13165
- new Option("--port <number>", "Port for the MCP server").env("DOCS_MCP_PORT").env("PORT").default(CLI_DEFAULTS.HTTP_PORT.toString()).argParser((v) => {
13865
+ new Option("--port <number>", "Port for the MCP server").env("DOCS_MCP_PORT").env("PORT").default(DEFAULT_HTTP_PORT.toString()).argParser((v) => {
13166
13866
  const n = Number(v);
13167
13867
  if (!Number.isInteger(n) || n < 1 || n > 65535) {
13168
13868
  throw new Error("Port must be an integer between 1 and 65535");
@@ -13170,7 +13870,7 @@ function createMcpCommand(program) {
13170
13870
  return String(n);
13171
13871
  })
13172
13872
  ).addOption(
13173
- new Option("--host <host>", "Host to bind the MCP server to").env("DOCS_MCP_HOST").env("HOST").default(CLI_DEFAULTS.HOST).argParser(validateHost)
13873
+ new Option("--host <host>", "Host to bind the MCP server to").env("DOCS_MCP_HOST").env("HOST").default(DEFAULT_HOST).argParser(validateHost)
13174
13874
  ).addOption(
13175
13875
  new Option(
13176
13876
  "--embedding-model <model>",
@@ -13530,7 +14230,7 @@ function createSearchCommand(program) {
13530
14230
  }
13531
14231
  function createWebCommand(program) {
13532
14232
  return program.command("web").description("Start web interface only").addOption(
13533
- new Option("--port <number>", "Port for the web interface").env("DOCS_MCP_WEB_PORT").env("DOCS_MCP_PORT").env("PORT").default(CLI_DEFAULTS.WEB_PORT.toString()).argParser((v) => {
14233
+ new Option("--port <number>", "Port for the web interface").env("DOCS_MCP_WEB_PORT").env("DOCS_MCP_PORT").env("PORT").default(DEFAULT_WEB_PORT.toString()).argParser((v) => {
13534
14234
  const n = Number(v);
13535
14235
  if (!Number.isInteger(n) || n < 1 || n > 65535) {
13536
14236
  throw new Error("Port must be an integer between 1 and 65535");
@@ -13538,7 +14238,7 @@ function createWebCommand(program) {
13538
14238
  return String(n);
13539
14239
  })
13540
14240
  ).addOption(
13541
- new Option("--host <host>", "Host to bind the web interface to").env("DOCS_MCP_HOST").env("HOST").default(CLI_DEFAULTS.HOST).argParser(validateHost)
14241
+ new Option("--host <host>", "Host to bind the web interface to").env("DOCS_MCP_HOST").env("HOST").default(DEFAULT_HOST).argParser(validateHost)
13542
14242
  ).addOption(
13543
14243
  new Option(
13544
14244
  "--embedding-model <model>",
@@ -13620,7 +14320,7 @@ function createWorkerCommand(program) {
13620
14320
  return String(n);
13621
14321
  })
13622
14322
  ).addOption(
13623
- new Option("--host <host>", "Host to bind the worker API to").env("DOCS_MCP_HOST").env("HOST").default(CLI_DEFAULTS.HOST).argParser(validateHost)
14323
+ new Option("--host <host>", "Host to bind the worker API to").env("DOCS_MCP_HOST").env("HOST").default(DEFAULT_HOST).argParser(validateHost)
13624
14324
  ).addOption(
13625
14325
  new Option(
13626
14326
  "--embedding-model <model>",
@@ -13640,11 +14340,15 @@ function createWorkerCommand(program) {
13640
14340
  logger.info(`🚀 Starting external pipeline worker on port ${port}`);
13641
14341
  ensurePlaywrightBrowsersInstalled();
13642
14342
  const embeddingConfig = resolveEmbeddingContext(cmdOptions.embeddingModel);
13643
- const docService = await createLocalDocumentManagement(embeddingConfig);
14343
+ const globalOptions = program.parent?.opts() || {};
14344
+ const docService = await createLocalDocumentManagement(
14345
+ globalOptions.storePath,
14346
+ embeddingConfig
14347
+ );
13644
14348
  const pipelineOptions = {
13645
14349
  recoverJobs: cmdOptions.resume,
13646
14350
  // Use the resume option
13647
- concurrency: CLI_DEFAULTS.MAX_CONCURRENCY
14351
+ concurrency: DEFAULT_MAX_CONCURRENCY
13648
14352
  };
13649
14353
  const pipeline = await createPipelineWithCallbacks(docService, pipelineOptions);
13650
14354
  const config = createAppServerConfig({
@@ -13692,10 +14396,12 @@ function createCliProgram() {
13692
14396
  ).enablePositionalOptions().allowExcessArguments(false).showHelpAfterError(true);
13693
14397
  program.hook("preAction", async (thisCommand, actionCommand) => {
13694
14398
  const globalOptions = thisCommand.opts();
14399
+ const resolvedStorePath = resolveStorePath(globalOptions.storePath);
14400
+ globalOptions.storePath = resolvedStorePath;
13695
14401
  setupLogging(globalOptions);
13696
14402
  initTelemetry({
13697
14403
  enabled: globalOptions.telemetry ?? true,
13698
- storePath: globalOptions.storePath
14404
+ storePath: resolvedStorePath
13699
14405
  });
13700
14406
  if (shouldEnableTelemetry()) {
13701
14407
  if (analytics.isEnabled()) {