@arabold/docs-mcp-server 1.30.0 → 1.31.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/dist/index.js CHANGED
@@ -1579,7 +1579,7 @@ function createMcpServerInstance(tools, readOnly = false) {
1579
1579
  "Re-scrape a previously indexed library version, updating only changed pages.",
1580
1580
  {
1581
1581
  library: z.string().trim().describe("Library name."),
1582
- version: z.string().trim().optional().describe("Library version (optional, refreshes unversioned if omitted).")
1582
+ version: z.string().trim().optional().describe("Library version (optional, refreshes latest if omitted).")
1583
1583
  },
1584
1584
  {
1585
1585
  title: "Refresh Library Version",
@@ -1833,7 +1833,7 @@ ${formattedJob}`);
1833
1833
  "Remove indexed documentation for a library version. Use only if explicitly instructed.",
1834
1834
  {
1835
1835
  library: z.string().trim().describe("Library name."),
1836
- version: z.string().trim().optional().describe("Library version (optional, removes unversioned if omitted).")
1836
+ version: z.string().trim().optional().describe("Library version (optional, removes latest if omitted).")
1837
1837
  },
1838
1838
  {
1839
1839
  title: "Remove Library Documentation",
@@ -7180,6 +7180,24 @@ let PipelineFactory$1 = class PipelineFactory {
7180
7180
  ];
7181
7181
  }
7182
7182
  };
7183
+ function compareVersionsDescending(a, b) {
7184
+ const aIsUnversioned = a === "" || a === null || a === void 0;
7185
+ const bIsUnversioned = b === "" || b === null || b === void 0;
7186
+ if (aIsUnversioned && bIsUnversioned) return 0;
7187
+ if (aIsUnversioned) return -1;
7188
+ if (bIsUnversioned) return 1;
7189
+ const aSemver = semver__default.valid(a) ?? semver__default.valid(semver__default.coerce(a));
7190
+ const bSemver = semver__default.valid(b) ?? semver__default.valid(semver__default.coerce(b));
7191
+ if (aSemver && bSemver) {
7192
+ return semver__default.rcompare(aSemver, bSemver);
7193
+ }
7194
+ const aLower = a.toLowerCase();
7195
+ const bLower = b.toLowerCase();
7196
+ return bLower.localeCompare(aLower);
7197
+ }
7198
+ function sortVersionsDescending(versions) {
7199
+ return [...versions].sort(compareVersionsDescending);
7200
+ }
7183
7201
  class HierarchicalAssemblyStrategy {
7184
7202
  /**
7185
7203
  * Determines if this strategy can handle the given content type.
@@ -8887,22 +8905,7 @@ class DocumentStore {
8887
8905
  });
8888
8906
  }
8889
8907
  for (const versions of libraryMap.values()) {
8890
- versions.sort((a, b) => {
8891
- if (a.version === "" && b.version !== "") {
8892
- return -1;
8893
- }
8894
- if (a.version !== "" && b.version === "") {
8895
- return 1;
8896
- }
8897
- if (a.version === "" && b.version === "") {
8898
- return 0;
8899
- }
8900
- try {
8901
- return semver__default.compare(a.version, b.version);
8902
- } catch (_error) {
8903
- return a.version.localeCompare(b.version);
8904
- }
8905
- });
8908
+ versions.sort((a, b) => compareVersionsDescending(a.version, b.version));
8906
8909
  }
8907
8910
  return libraryMap;
8908
8911
  } catch (error) {
@@ -9680,10 +9683,12 @@ class DocumentManagementService {
9680
9683
  }
9681
9684
  /**
9682
9685
  * Returns a list of all available semantic versions for a library.
9686
+ * Sorted in descending order (latest first).
9683
9687
  */
9684
9688
  async listVersions(library) {
9685
9689
  const versions = await this.store.queryUniqueVersions(library);
9686
- return versions.filter((v) => semver__default.valid(v));
9690
+ const validVersions = versions.filter((v) => semver__default.valid(v));
9691
+ return sortVersionsDescending(validVersions);
9687
9692
  }
9688
9693
  /**
9689
9694
  * Checks if documents exist for a given library and optional version.
@@ -9761,7 +9766,7 @@ class DocumentManagementService {
9761
9766
  async removeAllDocuments(library, version) {
9762
9767
  const normalizedVersion = this.normalizeVersion(version);
9763
9768
  logger.info(
9764
- `🗑️ Removing all documents from ${library}@${normalizedVersion || "[no version]"} store`
9769
+ `🗑️ Removing all documents from ${library}@${normalizedVersion || "latest"} store`
9765
9770
  );
9766
9771
  const count = await this.store.deletePages(library, normalizedVersion);
9767
9772
  logger.info(`🗑️ Deleted ${count} documents`);
@@ -9792,17 +9797,15 @@ class DocumentManagementService {
9792
9797
  */
9793
9798
  async removeVersion(library, version) {
9794
9799
  const normalizedVersion = this.normalizeVersion(version);
9795
- logger.debug(`Removing version: ${library}@${normalizedVersion || "[no version]"}`);
9800
+ logger.debug(`Removing version: ${library}@${normalizedVersion || "latest"}`);
9796
9801
  const result = await this.store.removeVersion(library, normalizedVersion, true);
9797
9802
  logger.info(`🗑️ Removed ${result.documentsDeleted} documents`);
9798
9803
  if (result.versionDeleted && result.libraryDeleted) {
9799
9804
  logger.info(`🗑️ Completely removed library ${library} (was last version)`);
9800
9805
  } else if (result.versionDeleted) {
9801
- logger.info(`🗑️ Removed version ${library}@${normalizedVersion || "[no version]"}`);
9806
+ logger.info(`🗑️ Removed version ${library}@${normalizedVersion || "latest"}`);
9802
9807
  } else {
9803
- logger.warn(
9804
- `⚠️ Version ${library}@${normalizedVersion || "[no version]"} not found`
9805
- );
9808
+ logger.warn(`⚠️ Version ${library}@${normalizedVersion || "latest"} not found`);
9806
9809
  const libraryRecord = await this.store.getLibrary(library);
9807
9810
  if (libraryRecord) {
9808
9811
  const versions = await this.store.queryUniqueVersions(library);
@@ -10538,6 +10541,29 @@ function registerEventsRoute(server, eventBus) {
10538
10541
  });
10539
10542
  });
10540
10543
  }
10544
+ const PrimaryButton = ({
10545
+ children,
10546
+ type = "button",
10547
+ class: className = "",
10548
+ disabled = false,
10549
+ ...rest
10550
+ }) => {
10551
+ const baseClasses = "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 transition-colors duration-150";
10552
+ const disabledClasses = disabled ? "opacity-50 cursor-not-allowed" : "";
10553
+ const combinedClasses = `${baseClasses} ${disabledClasses} ${className}`.trim();
10554
+ return /* @__PURE__ */ jsx("button", { type, class: combinedClasses, disabled, ...rest, children });
10555
+ };
10556
+ const AddJobButton = () => {
10557
+ return /* @__PURE__ */ jsx(
10558
+ PrimaryButton,
10559
+ {
10560
+ "hx-get": "/web/jobs/new",
10561
+ "hx-target": "#addJobForm",
10562
+ "hx-swap": "innerHTML",
10563
+ children: "Add New Documentation"
10564
+ }
10565
+ );
10566
+ };
10541
10567
  const Toast = () => {
10542
10568
  return /* @__PURE__ */ jsx(
10543
10569
  "div",
@@ -10666,7 +10692,7 @@ const Layout = ({
10666
10692
  children,
10667
10693
  eventClientConfig
10668
10694
  }) => {
10669
- const versionString = version || "1.30.0";
10695
+ const versionString = version || "1.31.0";
10670
10696
  const versionInitializer = `versionUpdate({ currentVersion: ${`'${versionString}'`} })`;
10671
10697
  return /* @__PURE__ */ jsxs("html", { lang: "en", children: [
10672
10698
  /* @__PURE__ */ jsxs("head", { children: [
@@ -11014,17 +11040,7 @@ function registerIndexRoute(server, config) {
11014
11040
  }
11015
11041
  )
11016
11042
  ] }),
11017
- /* @__PURE__ */ jsx("section", { class: "mb-8", children: /* @__PURE__ */ jsx("div", { id: "addJobForm", children: /* @__PURE__ */ jsx(
11018
- "button",
11019
- {
11020
- type: "button",
11021
- "hx-get": "/web/jobs/new",
11022
- "hx-target": "#addJobForm",
11023
- "hx-swap": "innerHTML",
11024
- 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 transition-colors duration-150",
11025
- children: "Add New Documentation"
11026
- }
11027
- ) }) }),
11043
+ /* @__PURE__ */ jsx("section", { class: "mb-8", children: /* @__PURE__ */ jsx("div", { id: "addJobForm", children: /* @__PURE__ */ jsx(AddJobButton, {}) }) }),
11028
11044
  /* @__PURE__ */ jsxs("div", { children: [
11029
11045
  /* @__PURE__ */ jsx("h2", { class: "text-xl font-semibold mb-2 text-gray-900 dark:text-white", children: "Indexed Documentation" }),
11030
11046
  /* @__PURE__ */ jsx(
@@ -11155,10 +11171,12 @@ const ProgressBar = ({ progress, showText = true }) => {
11155
11171
  ) })
11156
11172
  ] });
11157
11173
  };
11158
- const LoadingSpinner = () => /* @__PURE__ */ jsxs(
11174
+ const LoadingSpinner = ({
11175
+ class: className = "text-white"
11176
+ }) => /* @__PURE__ */ jsxs(
11159
11177
  "svg",
11160
11178
  {
11161
- class: "animate-spin h-4 w-4 text-white",
11179
+ class: `animate-spin h-4 w-4 ${className}`,
11162
11180
  xmlns: "http://www.w3.org/2000/svg",
11163
11181
  fill: "none",
11164
11182
  viewBox: "0 0 24 24",
@@ -11293,6 +11311,17 @@ function registerJobListRoutes(server, listJobsTool) {
11293
11311
  return /* @__PURE__ */ jsx(JobList, { jobs: result.jobs });
11294
11312
  });
11295
11313
  }
11314
+ const AddVersionButton = ({ libraryName }) => {
11315
+ return /* @__PURE__ */ jsx(
11316
+ PrimaryButton,
11317
+ {
11318
+ "hx-get": `/web/libraries/${encodeURIComponent(libraryName)}/add-version-form`,
11319
+ "hx-target": "#add-version-form-container",
11320
+ "hx-swap": "innerHTML",
11321
+ children: "Add New Version"
11322
+ }
11323
+ );
11324
+ };
11296
11325
  const Alert = ({ type, title, message }) => {
11297
11326
  let iconSvg;
11298
11327
  let colorClasses;
@@ -11437,17 +11466,39 @@ const Tooltip = ({ text, position = "top" }) => {
11437
11466
  );
11438
11467
  };
11439
11468
  const ScrapeFormContent = ({
11440
- defaultExcludePatterns
11469
+ defaultExcludePatterns,
11470
+ initialValues,
11471
+ mode = "new"
11441
11472
  }) => {
11442
- const defaultExcludePatternsText = defaultExcludePatterns?.join("\n") || "";
11473
+ const isAddVersionMode = mode === "add-version";
11474
+ const urlValue = initialValues?.url || "";
11475
+ const libraryValue = initialValues?.library || "";
11476
+ const maxPagesValue = initialValues?.maxPages?.toString() || "";
11477
+ const maxDepthValue = initialValues?.maxDepth?.toString() || "";
11478
+ const scopeValue = initialValues?.scope || "subpages";
11479
+ const includePatternsValue = initialValues?.includePatterns || "";
11480
+ const scrapeModeValue = initialValues?.scrapeMode || ScrapeMode.Auto;
11481
+ const followRedirectsValue = initialValues?.followRedirects ?? true;
11482
+ const ignoreErrorsValue = initialValues?.ignoreErrors ?? true;
11483
+ const excludePatternsText = initialValues?.excludePatterns !== void 0 ? initialValues.excludePatterns : defaultExcludePatterns?.join("\n") || "";
11484
+ const headersJson = JSON.stringify(initialValues?.headers || []);
11485
+ const closeButtonAttrs = isAddVersionMode ? {
11486
+ "hx-get": `/web/libraries/${encodeURIComponent(libraryValue)}/add-version-button`,
11487
+ "hx-target": "#add-version-form-container",
11488
+ "hx-swap": "innerHTML"
11489
+ } : {
11490
+ "hx-get": "/web/jobs/new-button",
11491
+ "hx-target": "#addJobForm",
11492
+ "hx-swap": "innerHTML"
11493
+ };
11494
+ const formTarget = isAddVersionMode ? "#add-version-form-container" : "#addJobForm";
11495
+ const title = isAddVersionMode ? "Add New Version" : "Add New Documentation";
11443
11496
  return /* @__PURE__ */ jsxs("div", { class: "mt-4 p-4 bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-300 dark:border-gray-600 relative animate-[fadeSlideIn_0.2s_ease-out]", children: [
11444
11497
  /* @__PURE__ */ jsx(
11445
11498
  "button",
11446
11499
  {
11447
11500
  type: "button",
11448
- "hx-get": "/web/jobs/new-button",
11449
- "hx-target": "#addJobForm",
11450
- "hx-swap": "innerHTML",
11501
+ ...closeButtonAttrs,
11451
11502
  class: "absolute top-3 right-3 p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-150",
11452
11503
  title: "Close",
11453
11504
  children: /* @__PURE__ */ jsx(
@@ -11471,16 +11522,20 @@ const ScrapeFormContent = ({
11471
11522
  )
11472
11523
  }
11473
11524
  ),
11474
- /* @__PURE__ */ jsx("h3", { class: "text-xl font-semibold text-gray-900 dark:text-white mb-2 pr-8", children: "Add New Documentation" }),
11525
+ /* @__PURE__ */ jsx("h3", { class: "text-xl font-semibold text-gray-900 dark:text-white mb-2 pr-8", children: title }),
11475
11526
  /* @__PURE__ */ jsxs(
11476
11527
  "form",
11477
11528
  {
11478
11529
  "hx-post": "/web/jobs/scrape",
11479
- "hx-target": "#addJobForm",
11530
+ "hx-target": formTarget,
11480
11531
  "hx-swap": "innerHTML",
11481
11532
  class: "space-y-2",
11482
- "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 }",
11533
+ "data-initial-url": urlValue,
11534
+ "data-initial-headers": headersJson,
11535
+ "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 }",
11536
+ "x-init": "\n url = $el.dataset.initialUrl || '';\n headers = JSON.parse($el.dataset.initialHeaders || '[]');\n checkUrlPath();\n ",
11483
11537
  children: [
11538
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: "formMode", value: mode }),
11484
11539
  /* @__PURE__ */ jsxs("div", { children: [
11485
11540
  /* @__PURE__ */ jsxs("div", { class: "flex items-center", children: [
11486
11541
  /* @__PURE__ */ jsx(
@@ -11521,6 +11576,7 @@ const ScrapeFormContent = ({
11521
11576
  "x-model": "url",
11522
11577
  "x-on:input": "checkUrlPath",
11523
11578
  "x-on:paste": "$nextTick(() => checkUrlPath())",
11579
+ placeholder: "https://docs.example.com/library/",
11524
11580
  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"
11525
11581
  }
11526
11582
  ),
@@ -11555,13 +11611,18 @@ const ScrapeFormContent = ({
11555
11611
  ),
11556
11612
  /* @__PURE__ */ jsx(Tooltip, { text: "The name of the library you're documenting. This will be used when searching." })
11557
11613
  ] }),
11558
- /* @__PURE__ */ jsx(
11614
+ isAddVersionMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
11615
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: "library", value: libraryValue }),
11616
+ /* @__PURE__ */ jsx("div", { class: "mt-0.5 px-2 py-1 text-sm text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-md border border-gray-300 dark:border-gray-600", children: /* @__PURE__ */ jsx("span", { safe: true, children: libraryValue }) })
11617
+ ] }) : /* @__PURE__ */ jsx(
11559
11618
  "input",
11560
11619
  {
11561
11620
  type: "text",
11562
11621
  name: "library",
11563
11622
  id: "library",
11564
11623
  required: true,
11624
+ value: libraryValue,
11625
+ placeholder: "e.g. react, vue, express",
11565
11626
  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"
11566
11627
  }
11567
11628
  )
@@ -11576,7 +11637,7 @@ const ScrapeFormContent = ({
11576
11637
  children: "Version (optional)"
11577
11638
  }
11578
11639
  ),
11579
- /* @__PURE__ */ jsx(Tooltip, { text: "Specify the version of the library documentation you're indexing. This allows for version-specific searches." })
11640
+ /* @__PURE__ */ jsx(Tooltip, { text: "Specify the version of the library documentation you're indexing (e.g. 2.0.0). Leave empty or enter 'latest' to index without a specific version. This allows for version-specific searches." })
11580
11641
  ] }),
11581
11642
  /* @__PURE__ */ jsx(
11582
11643
  "input",
@@ -11584,6 +11645,7 @@ const ScrapeFormContent = ({
11584
11645
  type: "text",
11585
11646
  name: "version",
11586
11647
  id: "version",
11648
+ placeholder: "e.g. 2.0.0 or leave empty for latest",
11587
11649
  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"
11588
11650
  }
11589
11651
  )
@@ -11592,7 +11654,9 @@ const ScrapeFormContent = ({
11592
11654
  "div",
11593
11655
  {
11594
11656
  class: "bg-gray-50 dark:bg-gray-900 p-2 rounded-md",
11595
- "x-data": "{ open: false, headers: [] }",
11657
+ "data-should-open": isAddVersionMode && (maxPagesValue || maxDepthValue || scopeValue !== "subpages" || includePatternsValue || excludePatternsText || scrapeModeValue !== ScrapeMode.Auto) ? "true" : "false",
11658
+ "x-data": "{ open: false }",
11659
+ "x-init": "open = $el.dataset.shouldOpen === 'true'",
11596
11660
  children: [
11597
11661
  /* @__PURE__ */ jsxs(
11598
11662
  "button",
@@ -11645,6 +11709,7 @@ const ScrapeFormContent = ({
11645
11709
  id: "maxPages",
11646
11710
  min: "1",
11647
11711
  placeholder: "1000",
11712
+ value: maxPagesValue,
11648
11713
  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"
11649
11714
  }
11650
11715
  )
@@ -11669,6 +11734,7 @@ const ScrapeFormContent = ({
11669
11734
  id: "maxDepth",
11670
11735
  min: "0",
11671
11736
  placeholder: "3",
11737
+ value: maxDepthValue,
11672
11738
  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"
11673
11739
  }
11674
11740
  )
@@ -11704,9 +11770,9 @@ const ScrapeFormContent = ({
11704
11770
  id: "scope",
11705
11771
  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",
11706
11772
  children: [
11707
- /* @__PURE__ */ jsx("option", { value: "subpages", selected: true, children: "Subpages (Default)" }),
11708
- /* @__PURE__ */ jsx("option", { value: "hostname", children: "Hostname" }),
11709
- /* @__PURE__ */ jsx("option", { value: "domain", children: "Domain" })
11773
+ /* @__PURE__ */ jsx("option", { value: "subpages", selected: scopeValue === "subpages", children: "Subpages (Default)" }),
11774
+ /* @__PURE__ */ jsx("option", { value: "hostname", selected: scopeValue === "hostname", children: "Hostname" }),
11775
+ /* @__PURE__ */ jsx("option", { value: "domain", selected: scopeValue === "domain", children: "Domain" })
11710
11776
  ]
11711
11777
  }
11712
11778
  )
@@ -11730,7 +11796,9 @@ const ScrapeFormContent = ({
11730
11796
  id: "includePatterns",
11731
11797
  rows: "2",
11732
11798
  placeholder: "e.g. docs/* or /api\\/v1.*/",
11733
- 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"
11799
+ 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",
11800
+ safe: true,
11801
+ children: includePatternsValue
11734
11802
  }
11735
11803
  )
11736
11804
  ] }),
@@ -11754,10 +11822,10 @@ const ScrapeFormContent = ({
11754
11822
  rows: "5",
11755
11823
  safe: true,
11756
11824
  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",
11757
- children: defaultExcludePatternsText
11825
+ children: excludePatternsText
11758
11826
  }
11759
11827
  ),
11760
- /* @__PURE__ */ jsx("p", { class: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: "Default patterns are pre-filled. Edit to customize or clear to exclude nothing." })
11828
+ /* @__PURE__ */ jsx("p", { class: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: isAddVersionMode ? "Patterns from previous version. Edit as needed." : "Default patterns are pre-filled. Edit to customize or clear to exclude nothing." })
11761
11829
  ] }),
11762
11830
  /* @__PURE__ */ jsxs("div", { children: [
11763
11831
  /* @__PURE__ */ jsxs("div", { class: "flex items-center", children: [
@@ -11787,9 +11855,30 @@ const ScrapeFormContent = ({
11787
11855
  id: "scrapeMode",
11788
11856
  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",
11789
11857
  children: [
11790
- /* @__PURE__ */ jsx("option", { value: ScrapeMode.Auto, selected: true, children: "Auto (Default)" }),
11791
- /* @__PURE__ */ jsx("option", { value: ScrapeMode.Fetch, children: "Fetch" }),
11792
- /* @__PURE__ */ jsx("option", { value: ScrapeMode.Playwright, children: "Playwright" })
11858
+ /* @__PURE__ */ jsx(
11859
+ "option",
11860
+ {
11861
+ value: ScrapeMode.Auto,
11862
+ selected: scrapeModeValue === ScrapeMode.Auto,
11863
+ children: "Auto (Default)"
11864
+ }
11865
+ ),
11866
+ /* @__PURE__ */ jsx(
11867
+ "option",
11868
+ {
11869
+ value: ScrapeMode.Fetch,
11870
+ selected: scrapeModeValue === ScrapeMode.Fetch,
11871
+ children: "Fetch"
11872
+ }
11873
+ ),
11874
+ /* @__PURE__ */ jsx(
11875
+ "option",
11876
+ {
11877
+ value: ScrapeMode.Playwright,
11878
+ selected: scrapeModeValue === ScrapeMode.Playwright,
11879
+ children: "Playwright"
11880
+ }
11881
+ )
11793
11882
  ]
11794
11883
  }
11795
11884
  )
@@ -11858,7 +11947,7 @@ const ScrapeFormContent = ({
11858
11947
  id: "followRedirects",
11859
11948
  name: "followRedirects",
11860
11949
  type: "checkbox",
11861
- checked: true,
11950
+ checked: followRedirectsValue,
11862
11951
  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"
11863
11952
  }
11864
11953
  ),
@@ -11878,7 +11967,7 @@ const ScrapeFormContent = ({
11878
11967
  id: "ignoreErrors",
11879
11968
  name: "ignoreErrors",
11880
11969
  type: "checkbox",
11881
- checked: true,
11970
+ checked: ignoreErrorsValue,
11882
11971
  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"
11883
11972
  }
11884
11973
  ),
@@ -12009,23 +12098,12 @@ function getEffectiveExclusionPatterns(userPatterns) {
12009
12098
  }
12010
12099
  return DEFAULT_EXCLUSION_PATTERNS;
12011
12100
  }
12012
- const ScrapeFormButton = () => /* @__PURE__ */ jsx(
12013
- "button",
12014
- {
12015
- type: "button",
12016
- "hx-get": "/web/jobs/new",
12017
- "hx-target": "#addJobForm",
12018
- "hx-swap": "innerHTML",
12019
- 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 transition-colors duration-150",
12020
- children: "Add New Documentation"
12021
- }
12022
- );
12023
12101
  function registerNewJobRoutes(server, scrapeTool) {
12024
12102
  server.get("/web/jobs/new", async () => {
12025
12103
  return /* @__PURE__ */ jsx(ScrapeForm, { defaultExcludePatterns: DEFAULT_EXCLUSION_PATTERNS });
12026
12104
  });
12027
12105
  server.get("/web/jobs/new-button", async () => {
12028
- return /* @__PURE__ */ jsx(ScrapeFormButton, {});
12106
+ return /* @__PURE__ */ jsx(AddJobButton, {});
12029
12107
  });
12030
12108
  server.post(
12031
12109
  "/web/jobs/scrape",
@@ -12061,11 +12139,11 @@ function registerNewJobRoutes(server, scrapeTool) {
12061
12139
  }
12062
12140
  );
12063
12141
  }
12142
+ const normalizedVersion = !body.version || body.version.trim() === "" || body.version.trim().toLowerCase() === "latest" ? null : body.version.trim();
12064
12143
  const scrapeOptions = {
12065
12144
  url: body.url,
12066
12145
  library: body.library,
12067
- version: body.version || null,
12068
- // Handle empty string as null
12146
+ version: normalizedVersion,
12069
12147
  waitForCompletion: false,
12070
12148
  // Don't wait in UI
12071
12149
  options: {
@@ -12084,16 +12162,20 @@ function registerNewJobRoutes(server, scrapeTool) {
12084
12162
  };
12085
12163
  const result = await scrapeTool.execute(scrapeOptions);
12086
12164
  if ("jobId" in result) {
12165
+ const versionDisplay = normalizedVersion || "latest";
12087
12166
  reply.header(
12088
12167
  "HX-Trigger",
12089
12168
  JSON.stringify({
12090
12169
  toast: {
12091
- message: "Job queued successfully!",
12170
+ message: `Indexing started for ${body.library}@${versionDisplay}`,
12092
12171
  type: "success"
12093
12172
  }
12094
12173
  })
12095
12174
  );
12096
- return /* @__PURE__ */ jsx(ScrapeFormButton, {});
12175
+ if (body.formMode === "add-version") {
12176
+ return /* @__PURE__ */ jsx(AddVersionButton, { libraryName: body.library });
12177
+ }
12178
+ return /* @__PURE__ */ jsx(AddJobButton, {});
12097
12179
  }
12098
12180
  return /* @__PURE__ */ jsx(Alert, { type: "warning", message: "Job finished unexpectedly quickly." });
12099
12181
  } catch (error) {
@@ -12119,15 +12201,16 @@ function registerNewJobRoutes(server, scrapeTool) {
12119
12201
  const VersionDetailsRow = ({
12120
12202
  version,
12121
12203
  libraryName,
12122
- showDelete = true
12123
- // Default to true
12204
+ showDelete = true,
12205
+ showRefresh = false
12124
12206
  }) => {
12125
12207
  const indexedDate = version.indexedAt ? new Date(version.indexedAt).toLocaleDateString() : "N/A";
12126
- const versionLabel = version.ref.version || "Unversioned";
12208
+ const versionLabel = version.ref.version || "Latest";
12127
12209
  const versionParam = version.ref.version || "";
12128
12210
  const sanitizedLibraryName = libraryName.replace(/[^a-zA-Z0-9-_]/g, "-");
12129
12211
  const sanitizedVersionParam = versionParam.replace(/[^a-zA-Z0-9-_]/g, "-");
12130
12212
  const rowId = `row-${sanitizedLibraryName}-${sanitizedVersionParam}`;
12213
+ const initialIsRefreshing = isActiveStatus(version.status);
12131
12214
  const defaultStateClasses = "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";
12132
12215
  const confirmingStateClasses = "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";
12133
12216
  return (
@@ -12139,14 +12222,16 @@ const VersionDetailsRow = ({
12139
12222
  class: "flex justify-between items-center py-1 border-b border-gray-200 dark:border-gray-600 last:border-b-0",
12140
12223
  "data-library-name": libraryName,
12141
12224
  "data-version-param": versionParam,
12142
- "x-data": "{ library: $el.dataset.libraryName, version: $el.dataset.versionParam, confirming: $el.dataset.confirming === 'true', isDeleting: false }",
12225
+ "data-is-refreshing": initialIsRefreshing ? "true" : "false",
12226
+ "x-data": "{ \n library: $el.dataset.libraryName, \n version: $el.dataset.versionParam, \n confirming: $el.dataset.confirming === 'true', \n isDeleting: false,\n isRefreshing: $el.dataset.isRefreshing === 'true',\n setRefreshing(val) {\n this.isRefreshing = !!val;\n this.$el.dataset.isRefreshing = val ? 'true' : 'false';\n },\n init() {\n const rowId = this.$el.id;\n const myLibrary = this.library;\n const myVersion = this.version;\n \n document.body.addEventListener('job-status-change', (e) => {\n const job = e.detail;\n const jobVersion = job.version || '';\n if (job.library === myLibrary && jobVersion === myVersion) {\n const newValue = ['queued', 'running'].includes(job.status);\n const el = document.getElementById(rowId);\n if (el) {\n el.dispatchEvent(new CustomEvent('set-refreshing', { detail: newValue, bubbles: true }));\n }\n }\n });\n }\n }",
12227
+ "x-on:set-refreshing": "setRefreshing($event.detail)",
12143
12228
  children: [
12144
12229
  /* @__PURE__ */ jsx(
12145
12230
  "span",
12146
12231
  {
12147
12232
  class: "text-sm text-gray-900 dark:text-white w-1/4 truncate",
12148
12233
  title: versionLabel,
12149
- children: version.ref.version ? /* @__PURE__ */ jsx(VersionBadge, { version: version.ref.version }) : /* @__PURE__ */ jsx("span", { children: "Unversioned" })
12234
+ children: version.ref.version ? /* @__PURE__ */ jsx(VersionBadge, { version: version.ref.version }) : /* @__PURE__ */ jsx("span", { class: "text-gray-600 dark:text-gray-400", children: "Latest" })
12150
12235
  }
12151
12236
  ),
12152
12237
  /* @__PURE__ */ jsxs("div", { class: "flex space-x-2 text-sm text-gray-600 dark:text-gray-400 w-3/4 justify-end items-center", children: [
@@ -12166,66 +12251,117 @@ const VersionDetailsRow = ({
12166
12251
  /* @__PURE__ */ jsx("span", { class: "font-semibold", safe: true, children: indexedDate })
12167
12252
  ] })
12168
12253
  ] }),
12169
- showDelete && /* @__PURE__ */ jsxs(
12170
- "button",
12171
- {
12172
- type: "button",
12173
- class: "ml-2 font-medium rounded-lg text-sm p-1 text-center inline-flex items-center transition-colors duration-150 ease-in-out",
12174
- title: "Remove this version",
12175
- "x-bind:class": `confirming ? '${confirmingStateClasses}' : '${defaultStateClasses}'`,
12176
- "x-bind:disabled": "isDeleting",
12177
- "x-on:click": "\n if (confirming) {\n isDeleting = true;\n window.confirmationManager.clear($root.id);\n $el.dispatchEvent(new CustomEvent('confirmed-delete', { bubbles: true }));\n } else {\n confirming = true;\n isDeleting = false;\n window.confirmationManager.start($root.id);\n }\n ",
12178
- "hx-delete": `/web/libraries/${encodeURIComponent(libraryName)}/versions/${encodeURIComponent(versionParam)}`,
12179
- "hx-target": `#${rowId}`,
12180
- "hx-swap": "outerHTML",
12181
- "hx-trigger": "confirmed-delete",
12182
- children: [
12183
- /* @__PURE__ */ jsxs("span", { "x-show": "!confirming && !isDeleting", children: [
12184
- /* @__PURE__ */ jsx(
12185
- "svg",
12186
- {
12187
- class: "w-4 h-4",
12188
- "aria-hidden": "true",
12189
- xmlns: "http://www.w3.org/2000/svg",
12190
- fill: "none",
12191
- viewBox: "0 0 18 20",
12192
- children: /* @__PURE__ */ jsx(
12193
- "path",
12194
- {
12195
- stroke: "currentColor",
12196
- "stroke-linecap": "round",
12197
- "stroke-linejoin": "round",
12198
- "stroke-width": "2",
12199
- 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"
12200
- }
12201
- )
12202
- }
12203
- ),
12204
- /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Remove version" })
12205
- ] }),
12206
- /* @__PURE__ */ jsxs("span", { "x-show": "confirming && !isDeleting", class: "mx-1", children: [
12207
- "Confirm?",
12208
- /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Confirm delete" })
12209
- ] }),
12210
- /* @__PURE__ */ jsxs("span", { "x-show": "isDeleting", children: [
12211
- /* @__PURE__ */ jsx(LoadingSpinner, {}),
12212
- /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Loading..." })
12213
- ] })
12214
- ]
12215
- }
12216
- )
12254
+ /* @__PURE__ */ jsxs("div", { class: "flex items-center ml-2 space-x-1", children: [
12255
+ showRefresh && /* @__PURE__ */ jsxs(Fragment, { children: [
12256
+ /* @__PURE__ */ jsx("template", { "x-if": "!isRefreshing", children: /* @__PURE__ */ jsxs(
12257
+ "button",
12258
+ {
12259
+ type: "button",
12260
+ class: "font-medium rounded-lg text-sm p-1 w-6 h-6 text-center inline-flex items-center justify-center transition-colors duration-150 ease-in-out text-gray-500 border border-gray-300 hover:bg-gray-100 hover:text-gray-700 focus:ring-4 focus:outline-none focus:ring-gray-200 dark:border-gray-600 dark:text-gray-400 dark:hover:text-white dark:focus:ring-gray-700 dark:hover:bg-gray-600",
12261
+ title: "Refresh this version (re-scrape changed pages)",
12262
+ "x-on:click": "\n isRefreshing = true;\n $root.dataset.isRefreshing = 'true';\n $el.dispatchEvent(new CustomEvent('trigger-refresh', { bubbles: true }));\n ",
12263
+ "hx-post": `/web/libraries/${encodeURIComponent(libraryName)}/versions/${encodeURIComponent(versionParam)}/refresh`,
12264
+ "hx-swap": "none",
12265
+ "hx-trigger": "trigger-refresh",
12266
+ children: [
12267
+ /* @__PURE__ */ jsx(
12268
+ "svg",
12269
+ {
12270
+ class: "w-4 h-4",
12271
+ "aria-hidden": "true",
12272
+ xmlns: "http://www.w3.org/2000/svg",
12273
+ fill: "none",
12274
+ viewBox: "0 0 24 24",
12275
+ children: /* @__PURE__ */ jsx(
12276
+ "path",
12277
+ {
12278
+ stroke: "currentColor",
12279
+ "stroke-linecap": "round",
12280
+ "stroke-linejoin": "round",
12281
+ "stroke-width": "2",
12282
+ d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
12283
+ }
12284
+ )
12285
+ }
12286
+ ),
12287
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Refresh version" })
12288
+ ]
12289
+ }
12290
+ ) }),
12291
+ /* @__PURE__ */ jsx("template", { "x-if": "isRefreshing", children: /* @__PURE__ */ jsxs(
12292
+ "button",
12293
+ {
12294
+ type: "button",
12295
+ class: "font-medium rounded-lg text-sm p-1 w-6 h-6 text-center inline-flex items-center justify-center transition-colors duration-150 ease-in-out text-gray-500 border border-gray-300 dark:border-gray-600 dark:text-gray-400",
12296
+ title: "Refresh in progress...",
12297
+ disabled: true,
12298
+ children: [
12299
+ /* @__PURE__ */ jsx(LoadingSpinner, { class: "text-gray-500 dark:text-gray-400" }),
12300
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Refreshing..." })
12301
+ ]
12302
+ }
12303
+ ) })
12304
+ ] }),
12305
+ showDelete && /* @__PURE__ */ jsxs(
12306
+ "button",
12307
+ {
12308
+ type: "button",
12309
+ class: "font-medium rounded-lg text-sm p-1 min-w-6 h-6 text-center inline-flex items-center justify-center transition-colors duration-150 ease-in-out",
12310
+ title: "Remove this version",
12311
+ "x-bind:class": `confirming ? '${confirmingStateClasses}' : '${defaultStateClasses}'`,
12312
+ "x-bind:disabled": "isDeleting",
12313
+ "x-on:click": "\n if (confirming) {\n isDeleting = true;\n window.confirmationManager.clear($root.id);\n $el.dispatchEvent(new CustomEvent('confirmed-delete', { bubbles: true }));\n } else {\n confirming = true;\n isDeleting = false;\n window.confirmationManager.start($root.id);\n }\n ",
12314
+ "hx-delete": `/web/libraries/${encodeURIComponent(libraryName)}/versions/${encodeURIComponent(versionParam)}`,
12315
+ "hx-target": `#${rowId}`,
12316
+ "hx-swap": "outerHTML",
12317
+ "hx-trigger": "confirmed-delete",
12318
+ children: [
12319
+ /* @__PURE__ */ jsxs("span", { "x-show": "!confirming && !isDeleting", children: [
12320
+ /* @__PURE__ */ jsx(
12321
+ "svg",
12322
+ {
12323
+ class: "w-4 h-4",
12324
+ "aria-hidden": "true",
12325
+ xmlns: "http://www.w3.org/2000/svg",
12326
+ fill: "none",
12327
+ viewBox: "0 0 18 20",
12328
+ children: /* @__PURE__ */ jsx(
12329
+ "path",
12330
+ {
12331
+ stroke: "currentColor",
12332
+ "stroke-linecap": "round",
12333
+ "stroke-linejoin": "round",
12334
+ "stroke-width": "2",
12335
+ 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"
12336
+ }
12337
+ )
12338
+ }
12339
+ ),
12340
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Remove version" })
12341
+ ] }),
12342
+ /* @__PURE__ */ jsxs("span", { "x-show": "confirming && !isDeleting", class: "mx-1", children: [
12343
+ "Confirm?",
12344
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Confirm delete" })
12345
+ ] }),
12346
+ /* @__PURE__ */ jsxs("span", { "x-show": "isDeleting", children: [
12347
+ /* @__PURE__ */ jsx(LoadingSpinner, {}),
12348
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Loading..." })
12349
+ ] })
12350
+ ]
12351
+ }
12352
+ )
12353
+ ] })
12217
12354
  ]
12218
12355
  }
12219
12356
  )
12220
12357
  );
12221
12358
  };
12222
12359
  const LibraryDetailCard = ({ library }) => {
12223
- const versions = library.versions?.reverse() || [];
12360
+ const versions = library.versions || [];
12224
12361
  const latestVersion = versions[0];
12225
- return (
12226
- // Use Flowbite Card structure with updated padding and border, and white background
12227
- /* @__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: [
12228
- /* @__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 }) }),
12362
+ 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: [
12363
+ /* @__PURE__ */ jsx("div", { class: "flex justify-between items-start mb-1", children: /* @__PURE__ */ jsxs("div", { children: [
12364
+ /* @__PURE__ */ jsx("h3", { class: "text-lg font-medium text-gray-900 dark:text-white", children: /* @__PURE__ */ jsx("span", { safe: true, children: library.name }) }),
12229
12365
  latestVersion?.sourceUrl ? /* @__PURE__ */ jsx("div", { class: "text-sm text-gray-500 dark:text-gray-400", children: /* @__PURE__ */ jsx(
12230
12366
  "a",
12231
12367
  {
@@ -12235,31 +12371,43 @@ const LibraryDetailCard = ({ library }) => {
12235
12371
  safe: true,
12236
12372
  children: latestVersion.sourceUrl
12237
12373
  }
12238
- ) }) : null,
12239
- /* @__PURE__ */ jsx("div", { class: "mt-2", children: versions.length > 0 ? versions.map((v) => {
12240
- const adapted = {
12241
- id: -1,
12242
- ref: { library: library.name, version: v.version },
12243
- status: v.status,
12244
- progress: v.progress,
12245
- counts: {
12246
- documents: v.documentCount,
12247
- uniqueUrls: v.uniqueUrlCount
12248
- },
12249
- indexedAt: v.indexedAt,
12250
- sourceUrl: v.sourceUrl ?? void 0
12251
- };
12252
- return /* @__PURE__ */ jsx(
12253
- VersionDetailsRow,
12254
- {
12255
- libraryName: library.name,
12256
- version: adapted,
12257
- showDelete: false
12258
- }
12259
- );
12260
- }) : /* @__PURE__ */ jsx("p", { class: "text-sm text-gray-500 dark:text-gray-400 italic", children: "No versions indexed." }) })
12261
- ] })
12262
- );
12374
+ ) }) : null
12375
+ ] }) }),
12376
+ /* @__PURE__ */ jsx(
12377
+ "div",
12378
+ {
12379
+ class: "mt-2",
12380
+ id: "version-list",
12381
+ "hx-get": `/web/libraries/${encodeURIComponent(library.name)}/versions-list`,
12382
+ "hx-trigger": "library-change from:body",
12383
+ "hx-swap": "morph:innerHTML",
12384
+ children: versions.length > 0 ? versions.map((v) => {
12385
+ const adapted = {
12386
+ id: -1,
12387
+ ref: { library: library.name, version: v.version },
12388
+ status: v.status,
12389
+ progress: v.progress,
12390
+ counts: {
12391
+ documents: v.documentCount,
12392
+ uniqueUrls: v.uniqueUrlCount
12393
+ },
12394
+ indexedAt: v.indexedAt,
12395
+ sourceUrl: v.sourceUrl ?? void 0
12396
+ };
12397
+ return /* @__PURE__ */ jsx(
12398
+ VersionDetailsRow,
12399
+ {
12400
+ libraryName: library.name,
12401
+ version: adapted,
12402
+ showDelete: true,
12403
+ showRefresh: true
12404
+ }
12405
+ );
12406
+ }) : /* @__PURE__ */ jsx("p", { class: "text-sm text-gray-500 dark:text-gray-400 italic", children: "No versions indexed." })
12407
+ }
12408
+ ),
12409
+ /* @__PURE__ */ jsx("div", { id: "add-version-form-container", class: "mt-4", children: /* @__PURE__ */ jsx(AddVersionButton, { libraryName: library.name }) })
12410
+ ] });
12263
12411
  };
12264
12412
  const LibrarySearchCard = ({ library }) => {
12265
12413
  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: [
@@ -12285,7 +12433,7 @@ const LibrarySearchCard = ({ library }) => {
12285
12433
  children: [
12286
12434
  /* @__PURE__ */ jsx("option", { value: "", children: "Latest" }),
12287
12435
  " ",
12288
- library.versions.map((version) => /* @__PURE__ */ jsx("option", { value: version.version || "unversioned", safe: true, children: version.version || "Unversioned" }))
12436
+ library.versions.map((version) => /* @__PURE__ */ jsx("option", { value: version.version || "latest", safe: true, children: version.version || "Latest" }))
12289
12437
  ]
12290
12438
  }
12291
12439
  ),
@@ -12359,7 +12507,7 @@ const SearchResultSkeletonItem = () => /* @__PURE__ */ jsxs("div", { class: "blo
12359
12507
  /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-full mb-2" }),
12360
12508
  /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-5/6" })
12361
12509
  ] });
12362
- function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool) {
12510
+ function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool, scrapeTool, docService) {
12363
12511
  server.get(
12364
12512
  "/libraries/:libraryName",
12365
12513
  async (request, reply) => {
@@ -12367,7 +12515,7 @@ function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool) {
12367
12515
  try {
12368
12516
  const result = await listLibrariesTool.execute();
12369
12517
  const libraryInfo = result.libraries.find(
12370
- (lib) => lib.name === libraryName
12518
+ (lib) => lib.name.toLowerCase() === libraryName.toLowerCase()
12371
12519
  );
12372
12520
  if (!libraryInfo) {
12373
12521
  reply.status(404).send("Library not found");
@@ -12404,7 +12552,7 @@ function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool) {
12404
12552
  reply.status(400).send("Search query is required.");
12405
12553
  return;
12406
12554
  }
12407
- const versionParam = version === "unversioned" ? void 0 : version;
12555
+ const versionParam = version === "latest" ? void 0 : version;
12408
12556
  try {
12409
12557
  const searchResult = await searchTool.execute({
12410
12558
  library: libraryName,
@@ -12423,9 +12571,128 @@ function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool) {
12423
12571
  }
12424
12572
  }
12425
12573
  );
12574
+ server.get(
12575
+ "/web/libraries/:libraryName/versions-list",
12576
+ async (request, reply) => {
12577
+ const { libraryName } = request.params;
12578
+ try {
12579
+ const result = await listLibrariesTool.execute();
12580
+ const libraryInfo = result.libraries.find(
12581
+ (lib) => lib.name.toLowerCase() === libraryName.toLowerCase()
12582
+ );
12583
+ if (!libraryInfo) {
12584
+ reply.status(404).send("Library not found");
12585
+ return;
12586
+ }
12587
+ const versions = libraryInfo.versions || [];
12588
+ reply.type("text/html; charset=utf-8");
12589
+ if (versions.length === 0) {
12590
+ return /* @__PURE__ */ jsx("p", { class: "text-sm text-gray-500 dark:text-gray-400 italic", children: "No versions indexed." });
12591
+ }
12592
+ return /* @__PURE__ */ jsx(Fragment, { children: versions.map((v) => {
12593
+ const adapted = {
12594
+ id: -1,
12595
+ ref: { library: libraryInfo.name, version: v.version },
12596
+ status: v.status,
12597
+ progress: v.progress,
12598
+ counts: {
12599
+ documents: v.documentCount,
12600
+ uniqueUrls: v.uniqueUrlCount
12601
+ },
12602
+ indexedAt: v.indexedAt,
12603
+ sourceUrl: v.sourceUrl ?? void 0
12604
+ };
12605
+ return /* @__PURE__ */ jsx(
12606
+ VersionDetailsRow,
12607
+ {
12608
+ libraryName: libraryInfo.name,
12609
+ version: adapted,
12610
+ showDelete: true,
12611
+ showRefresh: true
12612
+ }
12613
+ );
12614
+ }) });
12615
+ } catch (error) {
12616
+ logger.error(`Failed to fetch versions for ${libraryName}: ${error}`);
12617
+ reply.status(500).send("Internal Server Error");
12618
+ }
12619
+ }
12620
+ );
12621
+ server.get(
12622
+ "/web/libraries/:libraryName/add-version-button",
12623
+ async (request, reply) => {
12624
+ const { libraryName } = request.params;
12625
+ reply.type("text/html; charset=utf-8");
12626
+ return /* @__PURE__ */ jsx(AddVersionButton, { libraryName });
12627
+ }
12628
+ );
12629
+ server.get(
12630
+ "/web/libraries/:libraryName/add-version-form",
12631
+ async (request, reply) => {
12632
+ const { libraryName } = request.params;
12633
+ try {
12634
+ const result = await listLibrariesTool.execute();
12635
+ const libraryInfo = result.libraries.find(
12636
+ (lib) => lib.name.toLowerCase() === libraryName.toLowerCase()
12637
+ );
12638
+ if (!libraryInfo) {
12639
+ reply.status(404).send("Library not found");
12640
+ return;
12641
+ }
12642
+ const versions = libraryInfo.versions || [];
12643
+ const latestVersion = versions[0];
12644
+ let initialValues = {
12645
+ library: libraryName
12646
+ };
12647
+ if (latestVersion) {
12648
+ const summaries = await docService.listLibraries();
12649
+ const libSummary = summaries.find(
12650
+ (s) => s.library.toLowerCase() === libraryName.toLowerCase()
12651
+ );
12652
+ if (libSummary) {
12653
+ const versionSummary = libSummary.versions.find(
12654
+ (v) => v.ref.version === (latestVersion.version || "") || !latestVersion.version && v.ref.version === ""
12655
+ );
12656
+ if (versionSummary) {
12657
+ const scraperConfig = await docService.getScraperOptions(
12658
+ versionSummary.id
12659
+ );
12660
+ if (scraperConfig) {
12661
+ const opts = scraperConfig.options;
12662
+ initialValues = {
12663
+ library: libraryName,
12664
+ url: scraperConfig.sourceUrl,
12665
+ maxPages: opts.maxPages,
12666
+ maxDepth: opts.maxDepth,
12667
+ scope: opts.scope,
12668
+ includePatterns: opts.includePatterns?.join("\n"),
12669
+ excludePatterns: opts.excludePatterns?.join("\n"),
12670
+ scrapeMode: opts.scrapeMode,
12671
+ headers: opts.headers ? Object.entries(opts.headers).map(([name, value]) => ({
12672
+ name,
12673
+ value
12674
+ })) : void 0,
12675
+ followRedirects: opts.followRedirects,
12676
+ ignoreErrors: opts.ignoreErrors
12677
+ };
12678
+ }
12679
+ }
12680
+ }
12681
+ }
12682
+ reply.type("text/html; charset=utf-8");
12683
+ return /* @__PURE__ */ jsx(ScrapeFormContent, { initialValues, mode: "add-version" });
12684
+ } catch (error) {
12685
+ logger.error(
12686
+ `Failed to load add-version form for ${libraryName}: ${error}`
12687
+ );
12688
+ reply.type("text/html; charset=utf-8");
12689
+ return /* @__PURE__ */ jsx(Alert, { type: "error", message: "Failed to load the add version form." });
12690
+ }
12691
+ }
12692
+ );
12426
12693
  }
12427
12694
  const LibraryItem = ({ library }) => {
12428
- const versions = library.versions?.reverse() || [];
12695
+ const versions = library.versions || [];
12429
12696
  const latestVersion = versions[0];
12430
12697
  return (
12431
12698
  // Use Flowbite Card structure with updated padding and border, and white background
@@ -12514,7 +12781,7 @@ const LibraryList = ({ libraries }) => {
12514
12781
  }
12515
12782
  );
12516
12783
  };
12517
- function registerLibrariesRoutes(server, listLibrariesTool, removeTool) {
12784
+ function registerLibrariesRoutes(server, listLibrariesTool, removeTool, refreshVersionTool) {
12518
12785
  server.get("/web/libraries", async (_request, reply) => {
12519
12786
  try {
12520
12787
  const result = await listLibrariesTool.execute();
@@ -12529,9 +12796,16 @@ function registerLibrariesRoutes(server, listLibrariesTool, removeTool) {
12529
12796
  "/web/libraries/:libraryName/versions/:versionParam",
12530
12797
  async (request, reply) => {
12531
12798
  const { libraryName, versionParam } = request.params;
12532
- const version = versionParam === "unversioned" ? void 0 : versionParam;
12799
+ const version = versionParam === "latest" ? void 0 : versionParam;
12533
12800
  try {
12534
12801
  await removeTool.execute({ library: libraryName, version });
12802
+ const result = await listLibrariesTool.execute();
12803
+ const libraryStillExists = result.libraries.some(
12804
+ (lib) => lib.name.toLowerCase() === libraryName.toLowerCase()
12805
+ );
12806
+ if (!libraryStillExists) {
12807
+ reply.header("HX-Redirect", "/");
12808
+ }
12535
12809
  reply.status(204).send();
12536
12810
  } catch (error) {
12537
12811
  logger.error(
@@ -12541,6 +12815,46 @@ function registerLibrariesRoutes(server, listLibrariesTool, removeTool) {
12541
12815
  }
12542
12816
  }
12543
12817
  );
12818
+ server.post(
12819
+ "/web/libraries/:libraryName/versions/:versionParam/refresh",
12820
+ async (request, reply) => {
12821
+ const { libraryName, versionParam } = request.params;
12822
+ const version = versionParam === "latest" || versionParam === "" ? void 0 : versionParam;
12823
+ try {
12824
+ await refreshVersionTool.execute({
12825
+ library: libraryName,
12826
+ version,
12827
+ waitForCompletion: false
12828
+ });
12829
+ const versionDisplay = version || "latest";
12830
+ reply.header(
12831
+ "HX-Trigger",
12832
+ JSON.stringify({
12833
+ toast: {
12834
+ message: `Refresh started for ${libraryName}@${versionDisplay}`,
12835
+ type: "success"
12836
+ }
12837
+ })
12838
+ );
12839
+ reply.status(204).send();
12840
+ } catch (error) {
12841
+ logger.error(
12842
+ `Failed to refresh ${libraryName}@${versionParam}: ${error}`
12843
+ );
12844
+ const errorMessage = error instanceof Error ? error.message : "Failed to refresh version.";
12845
+ reply.header(
12846
+ "HX-Trigger",
12847
+ JSON.stringify({
12848
+ toast: {
12849
+ message: errorMessage,
12850
+ type: "error"
12851
+ }
12852
+ })
12853
+ );
12854
+ reply.status(500).send();
12855
+ }
12856
+ }
12857
+ );
12544
12858
  }
12545
12859
  function formatNumber(num) {
12546
12860
  if (num >= 1e9) {
@@ -12616,12 +12930,19 @@ async function registerWebService(server, docService, pipeline, eventBus, config
12616
12930
  const listJobsTool = new ListJobsTool(pipeline);
12617
12931
  const scrapeTool = new ScrapeTool(pipeline);
12618
12932
  const removeTool = new RemoveTool(docService, pipeline);
12933
+ const refreshVersionTool = new RefreshVersionTool(pipeline);
12619
12934
  const searchTool = new SearchTool(docService);
12620
12935
  const cancelJobTool = new CancelJobTool(pipeline);
12621
12936
  const clearCompletedJobsTool = new ClearCompletedJobsTool(pipeline);
12622
12937
  registerIndexRoute(server, config);
12623
- registerLibrariesRoutes(server, listLibrariesTool, removeTool);
12624
- registerLibraryDetailRoutes(server, listLibrariesTool, searchTool);
12938
+ registerLibrariesRoutes(server, listLibrariesTool, removeTool, refreshVersionTool);
12939
+ registerLibraryDetailRoutes(
12940
+ server,
12941
+ listLibrariesTool,
12942
+ searchTool,
12943
+ scrapeTool,
12944
+ docService
12945
+ );
12625
12946
  registerJobListRoutes(server, listJobsTool);
12626
12947
  registerNewJobRoutes(server, scrapeTool);
12627
12948
  registerCancelJobRoute(server, cancelJobTool);
@@ -12683,7 +13004,7 @@ class AppServer {
12683
13004
  try {
12684
13005
  if (telemetry.isEnabled()) {
12685
13006
  telemetry.setGlobalContext({
12686
- appVersion: "1.30.0",
13007
+ appVersion: "1.31.0",
12687
13008
  appPlatform: process.platform,
12688
13009
  appNodeVersion: process.version,
12689
13010
  appServicesEnabled: this.getActiveServicesList(),
@@ -14347,11 +14668,11 @@ class PipelineWorker {
14347
14668
  if (!scraperOptions.isRefresh) {
14348
14669
  await this.store.removeAllDocuments(library, version);
14349
14670
  logger.info(
14350
- `💾 Cleared store for ${library}@${version || "[no version]"} before scraping.`
14671
+ `💾 Cleared store for ${library}@${version || "latest"} before scraping.`
14351
14672
  );
14352
14673
  } else {
14353
14674
  logger.info(
14354
- `🔄 Refresh operation - preserving existing data for ${library}@${version || "[no version]"}.`
14675
+ `🔄 Refresh operation - preserving existing data for ${library}@${version || "latest"}.`
14355
14676
  );
14356
14677
  }
14357
14678
  await this.scraperService.scrape(
@@ -14503,7 +14824,7 @@ class PipelineManager {
14503
14824
  for (const version of runningVersions) {
14504
14825
  await this.store.updateVersionStatus(version.id, VersionStatus.QUEUED);
14505
14826
  logger.info(
14506
- `🔄 Reset interrupted job to QUEUED: ${version.library_name}@${version.name || "unversioned"}`
14827
+ `🔄 Reset interrupted job to QUEUED: ${version.library_name}@${version.name || "latest"}`
14507
14828
  );
14508
14829
  }
14509
14830
  const queuedVersions = await this.store.getVersionsByStatus([VersionStatus.QUEUED]);
@@ -14524,7 +14845,7 @@ class PipelineManager {
14524
14845
  parsedScraperOptions = JSON.parse(version.scraper_options);
14525
14846
  } catch (error) {
14526
14847
  logger.warn(
14527
- `⚠️ Failed to parse scraper options for ${version.library_name}@${version.name || "unversioned"}: ${error}`
14848
+ `⚠️ Failed to parse scraper options for ${version.library_name}@${version.name || "latest"}: ${error}`
14528
14849
  );
14529
14850
  }
14530
14851
  }
@@ -14631,7 +14952,7 @@ class PipelineManager {
14631
14952
  this.jobMap.set(jobId, job);
14632
14953
  this.jobQueue.push(jobId);
14633
14954
  logger.info(
14634
- `📝 Job enqueued: ${jobId} for ${library}${normalizedVersion ? `@${normalizedVersion}` : " (unversioned)"}`
14955
+ `📝 Job enqueued: ${jobId} for ${library}${normalizedVersion ? `@${normalizedVersion}` : " (latest)"}`
14635
14956
  );
14636
14957
  await this.updateJobStatus(job, PipelineJobStatus.QUEUED);
14637
14958
  if (this.isRunning) {
@@ -14665,7 +14986,7 @@ class PipelineManager {
14665
14986
  }
14666
14987
  if (versionInfo && versionInfo.status !== VersionStatus.COMPLETED) {
14667
14988
  logger.info(
14668
- `⚠️ Version ${library}@${normalizedVersion || "unversioned"} has status "${versionInfo.status}". Performing full re-scrape instead of refresh.`
14989
+ `⚠️ Version ${library}@${normalizedVersion || "latest"} has status "${versionInfo.status}". Performing full re-scrape instead of refresh.`
14669
14990
  );
14670
14991
  return this.enqueueJobWithStoredOptions(library, normalizedVersion);
14671
14992
  }
@@ -14677,11 +14998,11 @@ class PipelineManager {
14677
14998
  }
14678
14999
  if (pages.length === 0) {
14679
15000
  throw new Error(
14680
- `No pages found for ${library}@${normalizedVersion || "unversioned"}. Use scrape_docs to index it first.`
15001
+ `No pages found for ${library}@${normalizedVersion || "latest"}. Use scrape_docs to index it first.`
14681
15002
  );
14682
15003
  }
14683
15004
  logger.info(
14684
- `🔄 Preparing refresh job for ${library}@${normalizedVersion || "unversioned"} with ${pages.length} page(s)`
15005
+ `🔄 Preparing refresh job for ${library}@${normalizedVersion || "latest"} with ${pages.length} page(s)`
14685
15006
  );
14686
15007
  const initialQueue = pages.map((page) => ({
14687
15008
  url: page.url,
@@ -14705,7 +15026,7 @@ class PipelineManager {
14705
15026
  // Mark this as a refresh operation
14706
15027
  };
14707
15028
  logger.info(
14708
- `📝 Enqueueing refresh job for ${library}@${normalizedVersion || "unversioned"}`
15029
+ `📝 Enqueueing refresh job for ${library}@${normalizedVersion || "latest"}`
14709
15030
  );
14710
15031
  return this.enqueueScrapeJob(library, normalizedVersion, scraperOptions);
14711
15032
  } catch (error) {
@@ -14727,7 +15048,7 @@ class PipelineManager {
14727
15048
  const stored = await this.store.getScraperOptions(versionId);
14728
15049
  if (!stored) {
14729
15050
  throw new Error(
14730
- `No stored scraper options found for ${library}@${normalizedVersion || "unversioned"}`
15051
+ `No stored scraper options found for ${library}@${normalizedVersion || "latest"}`
14731
15052
  );
14732
15053
  }
14733
15054
  const storedOptions = stored.options;
@@ -14738,7 +15059,7 @@ class PipelineManager {
14738
15059
  ...storedOptions
14739
15060
  };
14740
15061
  logger.info(
14741
- `🔄 Re-indexing ${library}@${normalizedVersion || "unversioned"} with stored options from ${stored.sourceUrl}`
15062
+ `🔄 Re-indexing ${library}@${normalizedVersion || "latest"} with stored options from ${stored.sourceUrl}`
14742
15063
  );
14743
15064
  return this.enqueueScrapeJob(library, normalizedVersion, completeOptions);
14744
15065
  } catch (error) {
@@ -15774,7 +16095,7 @@ async function removeAction(library, options, command) {
15774
16095
  function createRemoveCommand(program) {
15775
16096
  return program.command("remove <library>").description("Remove documents for a specific library and version").option(
15776
16097
  "-v, --version <string>",
15777
- "Version to remove (optional, removes unversioned if omitted)"
16098
+ "Version to remove (optional, removes latest if omitted)"
15778
16099
  ).option(
15779
16100
  "--server-url <url>",
15780
16101
  "URL of external pipeline worker RPC (e.g., http://localhost:8080/api)"
@@ -16170,7 +16491,7 @@ function createCliProgram() {
16170
16491
  const commandStartTimes = /* @__PURE__ */ new Map();
16171
16492
  let globalEventBus = null;
16172
16493
  let globalTelemetryService = null;
16173
- program.name("docs-mcp-server").description("Unified CLI, MCP Server, and Web Interface for Docs MCP Server.").version("1.30.0").addOption(
16494
+ program.name("docs-mcp-server").description("Unified CLI, MCP Server, and Web Interface for Docs MCP Server.").version("1.31.0").addOption(
16174
16495
  new Option("--verbose", "Enable verbose (debug) logging").conflicts("silent")
16175
16496
  ).addOption(new Option("--silent", "Disable all logging except errors")).addOption(
16176
16497
  new Option("--telemetry", "Enable telemetry collection").env("DOCS_MCP_TELEMETRY").argParser((value) => {
@@ -16204,7 +16525,7 @@ function createCliProgram() {
16204
16525
  if (shouldEnableTelemetry()) {
16205
16526
  if (telemetry.isEnabled()) {
16206
16527
  telemetry.setGlobalContext({
16207
- appVersion: "1.30.0",
16528
+ appVersion: "1.31.0",
16208
16529
  appPlatform: process.platform,
16209
16530
  appNodeVersion: process.version,
16210
16531
  appInterface: "cli",