@arabold/docs-mcp-server 1.30.0 → 1.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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",
@@ -5269,11 +5269,28 @@ class HtmlPlaywrightMiddleware {
5269
5269
  logger.debug(`Could not access content frame for iframe ${index + 1}`);
5270
5270
  return;
5271
5271
  }
5272
- await frame.waitForSelector("body", { timeout: DEFAULT_PAGE_TIMEOUT }).catch(() => {
5273
- logger.debug(`Timeout waiting for body in iframe ${index + 1}`);
5274
- });
5275
- await this.waitForLoadingToComplete(frame);
5276
- const content = await this.extractIframeContent(frame);
5272
+ try {
5273
+ await frame.waitForSelector("body", { timeout: DEFAULT_PAGE_TIMEOUT });
5274
+ } catch {
5275
+ logger.debug(
5276
+ `Timeout waiting for body in iframe ${index + 1} - skipping content extraction`
5277
+ );
5278
+ return;
5279
+ }
5280
+ try {
5281
+ await this.waitForLoadingToComplete(frame);
5282
+ } catch {
5283
+ logger.debug(
5284
+ `Timeout waiting for loading indicators in iframe ${index + 1} - proceeding anyway`
5285
+ );
5286
+ }
5287
+ let content = null;
5288
+ try {
5289
+ content = await this.extractIframeContent(frame);
5290
+ } catch (error) {
5291
+ logger.debug(`Error extracting content from iframe ${index + 1}: ${error}`);
5292
+ return;
5293
+ }
5277
5294
  if (content && content.trim().length > 0) {
5278
5295
  await this.replaceIframeWithContent(page, index, content);
5279
5296
  logger.debug(
@@ -5284,7 +5301,7 @@ class HtmlPlaywrightMiddleware {
5284
5301
  }
5285
5302
  logger.debug(`Successfully loaded iframe ${index + 1}: ${src}`);
5286
5303
  } catch (error) {
5287
- logger.debug(`Error waiting for iframe ${index + 1}: ${error}`);
5304
+ logger.debug(`Error processing iframe ${index + 1}: ${error}`);
5288
5305
  }
5289
5306
  }
5290
5307
  /**
@@ -5447,23 +5464,31 @@ class HtmlPlaywrightMiddleware {
5447
5464
  origin,
5448
5465
  reqOrigin ?? void 0
5449
5466
  );
5450
- const response = await route.fetch({ headers: headers2 });
5451
- const body = await response.text();
5452
- if (response.status() >= 200 && response.status() < 300 && body.length > 0) {
5453
- const contentSizeBytes = Buffer.byteLength(body, "utf8");
5454
- if (contentSizeBytes <= FETCHER_MAX_CACHE_ITEM_SIZE_BYTES) {
5455
- const contentType = response.headers()["content-type"] || "application/octet-stream";
5456
- HtmlPlaywrightMiddleware.resourceCache.set(reqUrl, { body, contentType });
5457
- logger.debug(
5458
- `Cached ${resourceType}: ${reqUrl} (${contentSizeBytes} bytes, cache size: ${HtmlPlaywrightMiddleware.resourceCache.size})`
5459
- );
5460
- } else {
5461
- logger.debug(
5462
- `Resource too large to cache: ${reqUrl} (${contentSizeBytes} bytes > ${FETCHER_MAX_CACHE_ITEM_SIZE_BYTES} bytes limit)`
5463
- );
5467
+ try {
5468
+ const response = await route.fetch({ headers: headers2 });
5469
+ const body = await response.text();
5470
+ if (response.status() >= 200 && response.status() < 300 && body.length > 0) {
5471
+ const contentSizeBytes = Buffer.byteLength(body, "utf8");
5472
+ if (contentSizeBytes <= FETCHER_MAX_CACHE_ITEM_SIZE_BYTES) {
5473
+ const contentType = response.headers()["content-type"] || "application/octet-stream";
5474
+ HtmlPlaywrightMiddleware.resourceCache.set(reqUrl, { body, contentType });
5475
+ logger.debug(
5476
+ `Cached ${resourceType}: ${reqUrl} (${contentSizeBytes} bytes, cache size: ${HtmlPlaywrightMiddleware.resourceCache.size})`
5477
+ );
5478
+ } else {
5479
+ logger.debug(
5480
+ `Resource too large to cache: ${reqUrl} (${contentSizeBytes} bytes > ${FETCHER_MAX_CACHE_ITEM_SIZE_BYTES} bytes limit)`
5481
+ );
5482
+ }
5464
5483
  }
5484
+ return route.fulfill({ response });
5485
+ } catch (error) {
5486
+ const errorMessage = error instanceof Error ? error.message : String(error);
5487
+ logger.debug(
5488
+ `Network error fetching ${resourceType} ${reqUrl}: ${errorMessage}`
5489
+ );
5490
+ return route.abort("failed");
5465
5491
  }
5466
- return route.fulfill({ response });
5467
5492
  }
5468
5493
  const headers = mergePlaywrightHeaders(
5469
5494
  route.request().headers(),
@@ -5472,7 +5497,13 @@ class HtmlPlaywrightMiddleware {
5472
5497
  origin,
5473
5498
  reqOrigin ?? void 0
5474
5499
  );
5475
- return route.continue({ headers });
5500
+ try {
5501
+ return await route.continue({ headers });
5502
+ } catch (error) {
5503
+ const errorMessage = error instanceof Error ? error.message : String(error);
5504
+ logger.debug(`Network error for ${resourceType} ${reqUrl}: ${errorMessage}`);
5505
+ return route.abort("failed");
5506
+ }
5476
5507
  });
5477
5508
  }
5478
5509
  /**
@@ -5656,23 +5687,31 @@ ${frame.content}
5656
5687
  origin ?? void 0,
5657
5688
  reqOrigin ?? void 0
5658
5689
  );
5659
- const response = await route.fetch({ headers: headers2 });
5660
- const body = await response.text();
5661
- if (response.status() >= 200 && response.status() < 300 && body.length > 0) {
5662
- const contentSizeBytes = Buffer.byteLength(body, "utf8");
5663
- if (contentSizeBytes <= FETCHER_MAX_CACHE_ITEM_SIZE_BYTES) {
5664
- const contentType2 = response.headers()["content-type"] || "application/octet-stream";
5665
- HtmlPlaywrightMiddleware.resourceCache.set(reqUrl, { body, contentType: contentType2 });
5666
- logger.debug(
5667
- `Cached ${resourceType}: ${reqUrl} (${contentSizeBytes} bytes, cache size: ${HtmlPlaywrightMiddleware.resourceCache.size})`
5668
- );
5669
- } else {
5670
- logger.debug(
5671
- `Resource too large to cache: ${reqUrl} (${contentSizeBytes} bytes > ${FETCHER_MAX_CACHE_ITEM_SIZE_BYTES} bytes limit)`
5672
- );
5690
+ try {
5691
+ const response = await route.fetch({ headers: headers2 });
5692
+ const body = await response.text();
5693
+ if (response.status() >= 200 && response.status() < 300 && body.length > 0) {
5694
+ const contentSizeBytes = Buffer.byteLength(body, "utf8");
5695
+ if (contentSizeBytes <= FETCHER_MAX_CACHE_ITEM_SIZE_BYTES) {
5696
+ const contentType2 = response.headers()["content-type"] || "application/octet-stream";
5697
+ HtmlPlaywrightMiddleware.resourceCache.set(reqUrl, { body, contentType: contentType2 });
5698
+ logger.debug(
5699
+ `Cached ${resourceType}: ${reqUrl} (${contentSizeBytes} bytes, cache size: ${HtmlPlaywrightMiddleware.resourceCache.size})`
5700
+ );
5701
+ } else {
5702
+ logger.debug(
5703
+ `Resource too large to cache: ${reqUrl} (${contentSizeBytes} bytes > ${FETCHER_MAX_CACHE_ITEM_SIZE_BYTES} bytes limit)`
5704
+ );
5705
+ }
5673
5706
  }
5707
+ return route.fulfill({ response });
5708
+ } catch (error) {
5709
+ const errorMessage = error instanceof Error ? error.message : String(error);
5710
+ logger.debug(
5711
+ `Network error fetching ${resourceType} ${reqUrl}: ${errorMessage}`
5712
+ );
5713
+ return route.abort("failed");
5674
5714
  }
5675
- return route.fulfill({ response });
5676
5715
  }
5677
5716
  const headers = mergePlaywrightHeaders(
5678
5717
  route.request().headers(),
@@ -5681,7 +5720,13 @@ ${frame.content}
5681
5720
  origin ?? void 0,
5682
5721
  reqOrigin ?? void 0
5683
5722
  );
5684
- return route.continue({ headers });
5723
+ try {
5724
+ return await route.continue({ headers });
5725
+ } catch (error) {
5726
+ const errorMessage = error instanceof Error ? error.message : String(error);
5727
+ logger.debug(`Network error for ${resourceType} ${reqUrl}: ${errorMessage}`);
5728
+ return route.abort("failed");
5729
+ }
5685
5730
  });
5686
5731
  await page.goto(context.source, { waitUntil: "load" });
5687
5732
  await page.waitForSelector("body, frameset", { timeout: DEFAULT_PAGE_TIMEOUT });
@@ -7180,6 +7225,24 @@ let PipelineFactory$1 = class PipelineFactory {
7180
7225
  ];
7181
7226
  }
7182
7227
  };
7228
+ function compareVersionsDescending(a, b) {
7229
+ const aIsUnversioned = a === "" || a === null || a === void 0;
7230
+ const bIsUnversioned = b === "" || b === null || b === void 0;
7231
+ if (aIsUnversioned && bIsUnversioned) return 0;
7232
+ if (aIsUnversioned) return -1;
7233
+ if (bIsUnversioned) return 1;
7234
+ const aSemver = semver__default.valid(a) ?? semver__default.valid(semver__default.coerce(a));
7235
+ const bSemver = semver__default.valid(b) ?? semver__default.valid(semver__default.coerce(b));
7236
+ if (aSemver && bSemver) {
7237
+ return semver__default.rcompare(aSemver, bSemver);
7238
+ }
7239
+ const aLower = a.toLowerCase();
7240
+ const bLower = b.toLowerCase();
7241
+ return bLower.localeCompare(aLower);
7242
+ }
7243
+ function sortVersionsDescending(versions) {
7244
+ return [...versions].sort(compareVersionsDescending);
7245
+ }
7183
7246
  class HierarchicalAssemblyStrategy {
7184
7247
  /**
7185
7248
  * Determines if this strategy can handle the given content type.
@@ -8887,22 +8950,7 @@ class DocumentStore {
8887
8950
  });
8888
8951
  }
8889
8952
  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
- });
8953
+ versions.sort((a, b) => compareVersionsDescending(a.version, b.version));
8906
8954
  }
8907
8955
  return libraryMap;
8908
8956
  } catch (error) {
@@ -9680,10 +9728,12 @@ class DocumentManagementService {
9680
9728
  }
9681
9729
  /**
9682
9730
  * Returns a list of all available semantic versions for a library.
9731
+ * Sorted in descending order (latest first).
9683
9732
  */
9684
9733
  async listVersions(library) {
9685
9734
  const versions = await this.store.queryUniqueVersions(library);
9686
- return versions.filter((v) => semver__default.valid(v));
9735
+ const validVersions = versions.filter((v) => semver__default.valid(v));
9736
+ return sortVersionsDescending(validVersions);
9687
9737
  }
9688
9738
  /**
9689
9739
  * Checks if documents exist for a given library and optional version.
@@ -9761,7 +9811,7 @@ class DocumentManagementService {
9761
9811
  async removeAllDocuments(library, version) {
9762
9812
  const normalizedVersion = this.normalizeVersion(version);
9763
9813
  logger.info(
9764
- `🗑️ Removing all documents from ${library}@${normalizedVersion || "[no version]"} store`
9814
+ `🗑️ Removing all documents from ${library}@${normalizedVersion || "latest"} store`
9765
9815
  );
9766
9816
  const count = await this.store.deletePages(library, normalizedVersion);
9767
9817
  logger.info(`🗑️ Deleted ${count} documents`);
@@ -9792,17 +9842,15 @@ class DocumentManagementService {
9792
9842
  */
9793
9843
  async removeVersion(library, version) {
9794
9844
  const normalizedVersion = this.normalizeVersion(version);
9795
- logger.debug(`Removing version: ${library}@${normalizedVersion || "[no version]"}`);
9845
+ logger.debug(`Removing version: ${library}@${normalizedVersion || "latest"}`);
9796
9846
  const result = await this.store.removeVersion(library, normalizedVersion, true);
9797
9847
  logger.info(`🗑️ Removed ${result.documentsDeleted} documents`);
9798
9848
  if (result.versionDeleted && result.libraryDeleted) {
9799
9849
  logger.info(`🗑️ Completely removed library ${library} (was last version)`);
9800
9850
  } else if (result.versionDeleted) {
9801
- logger.info(`🗑️ Removed version ${library}@${normalizedVersion || "[no version]"}`);
9851
+ logger.info(`🗑️ Removed version ${library}@${normalizedVersion || "latest"}`);
9802
9852
  } else {
9803
- logger.warn(
9804
- `⚠️ Version ${library}@${normalizedVersion || "[no version]"} not found`
9805
- );
9853
+ logger.warn(`⚠️ Version ${library}@${normalizedVersion || "latest"} not found`);
9806
9854
  const libraryRecord = await this.store.getLibrary(library);
9807
9855
  if (libraryRecord) {
9808
9856
  const versions = await this.store.queryUniqueVersions(library);
@@ -10538,6 +10586,29 @@ function registerEventsRoute(server, eventBus) {
10538
10586
  });
10539
10587
  });
10540
10588
  }
10589
+ const PrimaryButton = ({
10590
+ children,
10591
+ type = "button",
10592
+ class: className = "",
10593
+ disabled = false,
10594
+ ...rest
10595
+ }) => {
10596
+ 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";
10597
+ const disabledClasses = disabled ? "opacity-50 cursor-not-allowed" : "";
10598
+ const combinedClasses = `${baseClasses} ${disabledClasses} ${className}`.trim();
10599
+ return /* @__PURE__ */ jsx("button", { type, class: combinedClasses, disabled, ...rest, children });
10600
+ };
10601
+ const AddJobButton = () => {
10602
+ return /* @__PURE__ */ jsx(
10603
+ PrimaryButton,
10604
+ {
10605
+ "hx-get": "/web/jobs/new",
10606
+ "hx-target": "#addJobForm",
10607
+ "hx-swap": "innerHTML",
10608
+ children: "Add New Documentation"
10609
+ }
10610
+ );
10611
+ };
10541
10612
  const Toast = () => {
10542
10613
  return /* @__PURE__ */ jsx(
10543
10614
  "div",
@@ -10666,7 +10737,7 @@ const Layout = ({
10666
10737
  children,
10667
10738
  eventClientConfig
10668
10739
  }) => {
10669
- const versionString = version || "1.30.0";
10740
+ const versionString = version || "1.31.1";
10670
10741
  const versionInitializer = `versionUpdate({ currentVersion: ${`'${versionString}'`} })`;
10671
10742
  return /* @__PURE__ */ jsxs("html", { lang: "en", children: [
10672
10743
  /* @__PURE__ */ jsxs("head", { children: [
@@ -11014,17 +11085,7 @@ function registerIndexRoute(server, config) {
11014
11085
  }
11015
11086
  )
11016
11087
  ] }),
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
- ) }) }),
11088
+ /* @__PURE__ */ jsx("section", { class: "mb-8", children: /* @__PURE__ */ jsx("div", { id: "addJobForm", children: /* @__PURE__ */ jsx(AddJobButton, {}) }) }),
11028
11089
  /* @__PURE__ */ jsxs("div", { children: [
11029
11090
  /* @__PURE__ */ jsx("h2", { class: "text-xl font-semibold mb-2 text-gray-900 dark:text-white", children: "Indexed Documentation" }),
11030
11091
  /* @__PURE__ */ jsx(
@@ -11155,10 +11216,12 @@ const ProgressBar = ({ progress, showText = true }) => {
11155
11216
  ) })
11156
11217
  ] });
11157
11218
  };
11158
- const LoadingSpinner = () => /* @__PURE__ */ jsxs(
11219
+ const LoadingSpinner = ({
11220
+ class: className = "text-white"
11221
+ }) => /* @__PURE__ */ jsxs(
11159
11222
  "svg",
11160
11223
  {
11161
- class: "animate-spin h-4 w-4 text-white",
11224
+ class: `animate-spin h-4 w-4 ${className}`,
11162
11225
  xmlns: "http://www.w3.org/2000/svg",
11163
11226
  fill: "none",
11164
11227
  viewBox: "0 0 24 24",
@@ -11293,6 +11356,17 @@ function registerJobListRoutes(server, listJobsTool) {
11293
11356
  return /* @__PURE__ */ jsx(JobList, { jobs: result.jobs });
11294
11357
  });
11295
11358
  }
11359
+ const AddVersionButton = ({ libraryName }) => {
11360
+ return /* @__PURE__ */ jsx(
11361
+ PrimaryButton,
11362
+ {
11363
+ "hx-get": `/web/libraries/${encodeURIComponent(libraryName)}/add-version-form`,
11364
+ "hx-target": "#add-version-form-container",
11365
+ "hx-swap": "innerHTML",
11366
+ children: "Add New Version"
11367
+ }
11368
+ );
11369
+ };
11296
11370
  const Alert = ({ type, title, message }) => {
11297
11371
  let iconSvg;
11298
11372
  let colorClasses;
@@ -11437,17 +11511,39 @@ const Tooltip = ({ text, position = "top" }) => {
11437
11511
  );
11438
11512
  };
11439
11513
  const ScrapeFormContent = ({
11440
- defaultExcludePatterns
11514
+ defaultExcludePatterns,
11515
+ initialValues,
11516
+ mode = "new"
11441
11517
  }) => {
11442
- const defaultExcludePatternsText = defaultExcludePatterns?.join("\n") || "";
11518
+ const isAddVersionMode = mode === "add-version";
11519
+ const urlValue = initialValues?.url || "";
11520
+ const libraryValue = initialValues?.library || "";
11521
+ const maxPagesValue = initialValues?.maxPages?.toString() || "";
11522
+ const maxDepthValue = initialValues?.maxDepth?.toString() || "";
11523
+ const scopeValue = initialValues?.scope || "subpages";
11524
+ const includePatternsValue = initialValues?.includePatterns || "";
11525
+ const scrapeModeValue = initialValues?.scrapeMode || ScrapeMode.Auto;
11526
+ const followRedirectsValue = initialValues?.followRedirects ?? true;
11527
+ const ignoreErrorsValue = initialValues?.ignoreErrors ?? true;
11528
+ const excludePatternsText = initialValues?.excludePatterns !== void 0 ? initialValues.excludePatterns : defaultExcludePatterns?.join("\n") || "";
11529
+ const headersJson = JSON.stringify(initialValues?.headers || []);
11530
+ const closeButtonAttrs = isAddVersionMode ? {
11531
+ "hx-get": `/web/libraries/${encodeURIComponent(libraryValue)}/add-version-button`,
11532
+ "hx-target": "#add-version-form-container",
11533
+ "hx-swap": "innerHTML"
11534
+ } : {
11535
+ "hx-get": "/web/jobs/new-button",
11536
+ "hx-target": "#addJobForm",
11537
+ "hx-swap": "innerHTML"
11538
+ };
11539
+ const formTarget = isAddVersionMode ? "#add-version-form-container" : "#addJobForm";
11540
+ const title = isAddVersionMode ? "Add New Version" : "Add New Documentation";
11443
11541
  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
11542
  /* @__PURE__ */ jsx(
11445
11543
  "button",
11446
11544
  {
11447
11545
  type: "button",
11448
- "hx-get": "/web/jobs/new-button",
11449
- "hx-target": "#addJobForm",
11450
- "hx-swap": "innerHTML",
11546
+ ...closeButtonAttrs,
11451
11547
  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
11548
  title: "Close",
11453
11549
  children: /* @__PURE__ */ jsx(
@@ -11471,16 +11567,20 @@ const ScrapeFormContent = ({
11471
11567
  )
11472
11568
  }
11473
11569
  ),
11474
- /* @__PURE__ */ jsx("h3", { class: "text-xl font-semibold text-gray-900 dark:text-white mb-2 pr-8", children: "Add New Documentation" }),
11570
+ /* @__PURE__ */ jsx("h3", { class: "text-xl font-semibold text-gray-900 dark:text-white mb-2 pr-8", children: title }),
11475
11571
  /* @__PURE__ */ jsxs(
11476
11572
  "form",
11477
11573
  {
11478
11574
  "hx-post": "/web/jobs/scrape",
11479
- "hx-target": "#addJobForm",
11575
+ "hx-target": formTarget,
11480
11576
  "hx-swap": "innerHTML",
11481
11577
  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 }",
11578
+ "data-initial-url": urlValue,
11579
+ "data-initial-headers": headersJson,
11580
+ "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 }",
11581
+ "x-init": "\n url = $el.dataset.initialUrl || '';\n headers = JSON.parse($el.dataset.initialHeaders || '[]');\n checkUrlPath();\n ",
11483
11582
  children: [
11583
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: "formMode", value: mode }),
11484
11584
  /* @__PURE__ */ jsxs("div", { children: [
11485
11585
  /* @__PURE__ */ jsxs("div", { class: "flex items-center", children: [
11486
11586
  /* @__PURE__ */ jsx(
@@ -11521,6 +11621,7 @@ const ScrapeFormContent = ({
11521
11621
  "x-model": "url",
11522
11622
  "x-on:input": "checkUrlPath",
11523
11623
  "x-on:paste": "$nextTick(() => checkUrlPath())",
11624
+ placeholder: "https://docs.example.com/library/",
11524
11625
  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
11626
  }
11526
11627
  ),
@@ -11555,13 +11656,18 @@ const ScrapeFormContent = ({
11555
11656
  ),
11556
11657
  /* @__PURE__ */ jsx(Tooltip, { text: "The name of the library you're documenting. This will be used when searching." })
11557
11658
  ] }),
11558
- /* @__PURE__ */ jsx(
11659
+ isAddVersionMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
11660
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: "library", value: libraryValue }),
11661
+ /* @__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 }) })
11662
+ ] }) : /* @__PURE__ */ jsx(
11559
11663
  "input",
11560
11664
  {
11561
11665
  type: "text",
11562
11666
  name: "library",
11563
11667
  id: "library",
11564
11668
  required: true,
11669
+ value: libraryValue,
11670
+ placeholder: "e.g. react, vue, express",
11565
11671
  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
11672
  }
11567
11673
  )
@@ -11576,7 +11682,7 @@ const ScrapeFormContent = ({
11576
11682
  children: "Version (optional)"
11577
11683
  }
11578
11684
  ),
11579
- /* @__PURE__ */ jsx(Tooltip, { text: "Specify the version of the library documentation you're indexing. This allows for version-specific searches." })
11685
+ /* @__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
11686
  ] }),
11581
11687
  /* @__PURE__ */ jsx(
11582
11688
  "input",
@@ -11584,6 +11690,7 @@ const ScrapeFormContent = ({
11584
11690
  type: "text",
11585
11691
  name: "version",
11586
11692
  id: "version",
11693
+ placeholder: "e.g. 2.0.0 or leave empty for latest",
11587
11694
  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
11695
  }
11589
11696
  )
@@ -11592,7 +11699,9 @@ const ScrapeFormContent = ({
11592
11699
  "div",
11593
11700
  {
11594
11701
  class: "bg-gray-50 dark:bg-gray-900 p-2 rounded-md",
11595
- "x-data": "{ open: false, headers: [] }",
11702
+ "data-should-open": isAddVersionMode && (maxPagesValue || maxDepthValue || scopeValue !== "subpages" || includePatternsValue || excludePatternsText || scrapeModeValue !== ScrapeMode.Auto) ? "true" : "false",
11703
+ "x-data": "{ open: false }",
11704
+ "x-init": "open = $el.dataset.shouldOpen === 'true'",
11596
11705
  children: [
11597
11706
  /* @__PURE__ */ jsxs(
11598
11707
  "button",
@@ -11645,6 +11754,7 @@ const ScrapeFormContent = ({
11645
11754
  id: "maxPages",
11646
11755
  min: "1",
11647
11756
  placeholder: "1000",
11757
+ value: maxPagesValue,
11648
11758
  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
11759
  }
11650
11760
  )
@@ -11669,6 +11779,7 @@ const ScrapeFormContent = ({
11669
11779
  id: "maxDepth",
11670
11780
  min: "0",
11671
11781
  placeholder: "3",
11782
+ value: maxDepthValue,
11672
11783
  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
11784
  }
11674
11785
  )
@@ -11704,9 +11815,9 @@ const ScrapeFormContent = ({
11704
11815
  id: "scope",
11705
11816
  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
11817
  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" })
11818
+ /* @__PURE__ */ jsx("option", { value: "subpages", selected: scopeValue === "subpages", children: "Subpages (Default)" }),
11819
+ /* @__PURE__ */ jsx("option", { value: "hostname", selected: scopeValue === "hostname", children: "Hostname" }),
11820
+ /* @__PURE__ */ jsx("option", { value: "domain", selected: scopeValue === "domain", children: "Domain" })
11710
11821
  ]
11711
11822
  }
11712
11823
  )
@@ -11730,7 +11841,9 @@ const ScrapeFormContent = ({
11730
11841
  id: "includePatterns",
11731
11842
  rows: "2",
11732
11843
  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"
11844
+ 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",
11845
+ safe: true,
11846
+ children: includePatternsValue
11734
11847
  }
11735
11848
  )
11736
11849
  ] }),
@@ -11754,10 +11867,10 @@ const ScrapeFormContent = ({
11754
11867
  rows: "5",
11755
11868
  safe: true,
11756
11869
  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
11870
+ children: excludePatternsText
11758
11871
  }
11759
11872
  ),
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." })
11873
+ /* @__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
11874
  ] }),
11762
11875
  /* @__PURE__ */ jsxs("div", { children: [
11763
11876
  /* @__PURE__ */ jsxs("div", { class: "flex items-center", children: [
@@ -11787,9 +11900,30 @@ const ScrapeFormContent = ({
11787
11900
  id: "scrapeMode",
11788
11901
  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
11902
  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" })
11903
+ /* @__PURE__ */ jsx(
11904
+ "option",
11905
+ {
11906
+ value: ScrapeMode.Auto,
11907
+ selected: scrapeModeValue === ScrapeMode.Auto,
11908
+ children: "Auto (Default)"
11909
+ }
11910
+ ),
11911
+ /* @__PURE__ */ jsx(
11912
+ "option",
11913
+ {
11914
+ value: ScrapeMode.Fetch,
11915
+ selected: scrapeModeValue === ScrapeMode.Fetch,
11916
+ children: "Fetch"
11917
+ }
11918
+ ),
11919
+ /* @__PURE__ */ jsx(
11920
+ "option",
11921
+ {
11922
+ value: ScrapeMode.Playwright,
11923
+ selected: scrapeModeValue === ScrapeMode.Playwright,
11924
+ children: "Playwright"
11925
+ }
11926
+ )
11793
11927
  ]
11794
11928
  }
11795
11929
  )
@@ -11858,7 +11992,7 @@ const ScrapeFormContent = ({
11858
11992
  id: "followRedirects",
11859
11993
  name: "followRedirects",
11860
11994
  type: "checkbox",
11861
- checked: true,
11995
+ checked: followRedirectsValue,
11862
11996
  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
11997
  }
11864
11998
  ),
@@ -11878,7 +12012,7 @@ const ScrapeFormContent = ({
11878
12012
  id: "ignoreErrors",
11879
12013
  name: "ignoreErrors",
11880
12014
  type: "checkbox",
11881
- checked: true,
12015
+ checked: ignoreErrorsValue,
11882
12016
  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
12017
  }
11884
12018
  ),
@@ -12009,23 +12143,12 @@ function getEffectiveExclusionPatterns(userPatterns) {
12009
12143
  }
12010
12144
  return DEFAULT_EXCLUSION_PATTERNS;
12011
12145
  }
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
12146
  function registerNewJobRoutes(server, scrapeTool) {
12024
12147
  server.get("/web/jobs/new", async () => {
12025
12148
  return /* @__PURE__ */ jsx(ScrapeForm, { defaultExcludePatterns: DEFAULT_EXCLUSION_PATTERNS });
12026
12149
  });
12027
12150
  server.get("/web/jobs/new-button", async () => {
12028
- return /* @__PURE__ */ jsx(ScrapeFormButton, {});
12151
+ return /* @__PURE__ */ jsx(AddJobButton, {});
12029
12152
  });
12030
12153
  server.post(
12031
12154
  "/web/jobs/scrape",
@@ -12061,11 +12184,11 @@ function registerNewJobRoutes(server, scrapeTool) {
12061
12184
  }
12062
12185
  );
12063
12186
  }
12187
+ const normalizedVersion = !body.version || body.version.trim() === "" || body.version.trim().toLowerCase() === "latest" ? null : body.version.trim();
12064
12188
  const scrapeOptions = {
12065
12189
  url: body.url,
12066
12190
  library: body.library,
12067
- version: body.version || null,
12068
- // Handle empty string as null
12191
+ version: normalizedVersion,
12069
12192
  waitForCompletion: false,
12070
12193
  // Don't wait in UI
12071
12194
  options: {
@@ -12084,16 +12207,20 @@ function registerNewJobRoutes(server, scrapeTool) {
12084
12207
  };
12085
12208
  const result = await scrapeTool.execute(scrapeOptions);
12086
12209
  if ("jobId" in result) {
12210
+ const versionDisplay = normalizedVersion || "latest";
12087
12211
  reply.header(
12088
12212
  "HX-Trigger",
12089
12213
  JSON.stringify({
12090
12214
  toast: {
12091
- message: "Job queued successfully!",
12215
+ message: `Indexing started for ${body.library}@${versionDisplay}`,
12092
12216
  type: "success"
12093
12217
  }
12094
12218
  })
12095
12219
  );
12096
- return /* @__PURE__ */ jsx(ScrapeFormButton, {});
12220
+ if (body.formMode === "add-version") {
12221
+ return /* @__PURE__ */ jsx(AddVersionButton, { libraryName: body.library });
12222
+ }
12223
+ return /* @__PURE__ */ jsx(AddJobButton, {});
12097
12224
  }
12098
12225
  return /* @__PURE__ */ jsx(Alert, { type: "warning", message: "Job finished unexpectedly quickly." });
12099
12226
  } catch (error) {
@@ -12119,15 +12246,16 @@ function registerNewJobRoutes(server, scrapeTool) {
12119
12246
  const VersionDetailsRow = ({
12120
12247
  version,
12121
12248
  libraryName,
12122
- showDelete = true
12123
- // Default to true
12249
+ showDelete = true,
12250
+ showRefresh = false
12124
12251
  }) => {
12125
12252
  const indexedDate = version.indexedAt ? new Date(version.indexedAt).toLocaleDateString() : "N/A";
12126
- const versionLabel = version.ref.version || "Unversioned";
12253
+ const versionLabel = version.ref.version || "Latest";
12127
12254
  const versionParam = version.ref.version || "";
12128
12255
  const sanitizedLibraryName = libraryName.replace(/[^a-zA-Z0-9-_]/g, "-");
12129
12256
  const sanitizedVersionParam = versionParam.replace(/[^a-zA-Z0-9-_]/g, "-");
12130
12257
  const rowId = `row-${sanitizedLibraryName}-${sanitizedVersionParam}`;
12258
+ const initialIsRefreshing = isActiveStatus(version.status);
12131
12259
  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
12260
  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
12261
  return (
@@ -12139,14 +12267,16 @@ const VersionDetailsRow = ({
12139
12267
  class: "flex justify-between items-center py-1 border-b border-gray-200 dark:border-gray-600 last:border-b-0",
12140
12268
  "data-library-name": libraryName,
12141
12269
  "data-version-param": versionParam,
12142
- "x-data": "{ library: $el.dataset.libraryName, version: $el.dataset.versionParam, confirming: $el.dataset.confirming === 'true', isDeleting: false }",
12270
+ "data-is-refreshing": initialIsRefreshing ? "true" : "false",
12271
+ "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 }",
12272
+ "x-on:set-refreshing": "setRefreshing($event.detail)",
12143
12273
  children: [
12144
12274
  /* @__PURE__ */ jsx(
12145
12275
  "span",
12146
12276
  {
12147
12277
  class: "text-sm text-gray-900 dark:text-white w-1/4 truncate",
12148
12278
  title: versionLabel,
12149
- children: version.ref.version ? /* @__PURE__ */ jsx(VersionBadge, { version: version.ref.version }) : /* @__PURE__ */ jsx("span", { children: "Unversioned" })
12279
+ 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
12280
  }
12151
12281
  ),
12152
12282
  /* @__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 +12296,117 @@ const VersionDetailsRow = ({
12166
12296
  /* @__PURE__ */ jsx("span", { class: "font-semibold", safe: true, children: indexedDate })
12167
12297
  ] })
12168
12298
  ] }),
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
- )
12299
+ /* @__PURE__ */ jsxs("div", { class: "flex items-center ml-2 space-x-1", children: [
12300
+ showRefresh && /* @__PURE__ */ jsxs(Fragment, { children: [
12301
+ /* @__PURE__ */ jsx("template", { "x-if": "!isRefreshing", children: /* @__PURE__ */ jsxs(
12302
+ "button",
12303
+ {
12304
+ type: "button",
12305
+ 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",
12306
+ title: "Refresh this version (re-scrape changed pages)",
12307
+ "x-on:click": "\n isRefreshing = true;\n $root.dataset.isRefreshing = 'true';\n $el.dispatchEvent(new CustomEvent('trigger-refresh', { bubbles: true }));\n ",
12308
+ "hx-post": `/web/libraries/${encodeURIComponent(libraryName)}/versions/${encodeURIComponent(versionParam)}/refresh`,
12309
+ "hx-swap": "none",
12310
+ "hx-trigger": "trigger-refresh",
12311
+ children: [
12312
+ /* @__PURE__ */ jsx(
12313
+ "svg",
12314
+ {
12315
+ class: "w-4 h-4",
12316
+ "aria-hidden": "true",
12317
+ xmlns: "http://www.w3.org/2000/svg",
12318
+ fill: "none",
12319
+ viewBox: "0 0 24 24",
12320
+ children: /* @__PURE__ */ jsx(
12321
+ "path",
12322
+ {
12323
+ stroke: "currentColor",
12324
+ "stroke-linecap": "round",
12325
+ "stroke-linejoin": "round",
12326
+ "stroke-width": "2",
12327
+ 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"
12328
+ }
12329
+ )
12330
+ }
12331
+ ),
12332
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Refresh version" })
12333
+ ]
12334
+ }
12335
+ ) }),
12336
+ /* @__PURE__ */ jsx("template", { "x-if": "isRefreshing", children: /* @__PURE__ */ jsxs(
12337
+ "button",
12338
+ {
12339
+ type: "button",
12340
+ 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",
12341
+ title: "Refresh in progress...",
12342
+ disabled: true,
12343
+ children: [
12344
+ /* @__PURE__ */ jsx(LoadingSpinner, { class: "text-gray-500 dark:text-gray-400" }),
12345
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Refreshing..." })
12346
+ ]
12347
+ }
12348
+ ) })
12349
+ ] }),
12350
+ showDelete && /* @__PURE__ */ jsxs(
12351
+ "button",
12352
+ {
12353
+ type: "button",
12354
+ 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",
12355
+ title: "Remove this version",
12356
+ "x-bind:class": `confirming ? '${confirmingStateClasses}' : '${defaultStateClasses}'`,
12357
+ "x-bind:disabled": "isDeleting",
12358
+ "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 ",
12359
+ "hx-delete": `/web/libraries/${encodeURIComponent(libraryName)}/versions/${encodeURIComponent(versionParam)}`,
12360
+ "hx-target": `#${rowId}`,
12361
+ "hx-swap": "outerHTML",
12362
+ "hx-trigger": "confirmed-delete",
12363
+ children: [
12364
+ /* @__PURE__ */ jsxs("span", { "x-show": "!confirming && !isDeleting", children: [
12365
+ /* @__PURE__ */ jsx(
12366
+ "svg",
12367
+ {
12368
+ class: "w-4 h-4",
12369
+ "aria-hidden": "true",
12370
+ xmlns: "http://www.w3.org/2000/svg",
12371
+ fill: "none",
12372
+ viewBox: "0 0 18 20",
12373
+ children: /* @__PURE__ */ jsx(
12374
+ "path",
12375
+ {
12376
+ stroke: "currentColor",
12377
+ "stroke-linecap": "round",
12378
+ "stroke-linejoin": "round",
12379
+ "stroke-width": "2",
12380
+ 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"
12381
+ }
12382
+ )
12383
+ }
12384
+ ),
12385
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Remove version" })
12386
+ ] }),
12387
+ /* @__PURE__ */ jsxs("span", { "x-show": "confirming && !isDeleting", class: "mx-1", children: [
12388
+ "Confirm?",
12389
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Confirm delete" })
12390
+ ] }),
12391
+ /* @__PURE__ */ jsxs("span", { "x-show": "isDeleting", children: [
12392
+ /* @__PURE__ */ jsx(LoadingSpinner, {}),
12393
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Loading..." })
12394
+ ] })
12395
+ ]
12396
+ }
12397
+ )
12398
+ ] })
12217
12399
  ]
12218
12400
  }
12219
12401
  )
12220
12402
  );
12221
12403
  };
12222
12404
  const LibraryDetailCard = ({ library }) => {
12223
- const versions = library.versions?.reverse() || [];
12405
+ const versions = library.versions || [];
12224
12406
  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 }) }),
12407
+ 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: [
12408
+ /* @__PURE__ */ jsx("div", { class: "flex justify-between items-start mb-1", children: /* @__PURE__ */ jsxs("div", { children: [
12409
+ /* @__PURE__ */ jsx("h3", { class: "text-lg font-medium text-gray-900 dark:text-white", children: /* @__PURE__ */ jsx("span", { safe: true, children: library.name }) }),
12229
12410
  latestVersion?.sourceUrl ? /* @__PURE__ */ jsx("div", { class: "text-sm text-gray-500 dark:text-gray-400", children: /* @__PURE__ */ jsx(
12230
12411
  "a",
12231
12412
  {
@@ -12235,31 +12416,43 @@ const LibraryDetailCard = ({ library }) => {
12235
12416
  safe: true,
12236
12417
  children: latestVersion.sourceUrl
12237
12418
  }
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
- );
12419
+ ) }) : null
12420
+ ] }) }),
12421
+ /* @__PURE__ */ jsx(
12422
+ "div",
12423
+ {
12424
+ class: "mt-2",
12425
+ id: "version-list",
12426
+ "hx-get": `/web/libraries/${encodeURIComponent(library.name)}/versions-list`,
12427
+ "hx-trigger": "library-change from:body",
12428
+ "hx-swap": "morph:innerHTML",
12429
+ children: versions.length > 0 ? versions.map((v) => {
12430
+ const adapted = {
12431
+ id: -1,
12432
+ ref: { library: library.name, version: v.version },
12433
+ status: v.status,
12434
+ progress: v.progress,
12435
+ counts: {
12436
+ documents: v.documentCount,
12437
+ uniqueUrls: v.uniqueUrlCount
12438
+ },
12439
+ indexedAt: v.indexedAt,
12440
+ sourceUrl: v.sourceUrl ?? void 0
12441
+ };
12442
+ return /* @__PURE__ */ jsx(
12443
+ VersionDetailsRow,
12444
+ {
12445
+ libraryName: library.name,
12446
+ version: adapted,
12447
+ showDelete: true,
12448
+ showRefresh: true
12449
+ }
12450
+ );
12451
+ }) : /* @__PURE__ */ jsx("p", { class: "text-sm text-gray-500 dark:text-gray-400 italic", children: "No versions indexed." })
12452
+ }
12453
+ ),
12454
+ /* @__PURE__ */ jsx("div", { id: "add-version-form-container", class: "mt-4", children: /* @__PURE__ */ jsx(AddVersionButton, { libraryName: library.name }) })
12455
+ ] });
12263
12456
  };
12264
12457
  const LibrarySearchCard = ({ library }) => {
12265
12458
  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 +12478,7 @@ const LibrarySearchCard = ({ library }) => {
12285
12478
  children: [
12286
12479
  /* @__PURE__ */ jsx("option", { value: "", children: "Latest" }),
12287
12480
  " ",
12288
- library.versions.map((version) => /* @__PURE__ */ jsx("option", { value: version.version || "unversioned", safe: true, children: version.version || "Unversioned" }))
12481
+ library.versions.map((version) => /* @__PURE__ */ jsx("option", { value: version.version || "latest", safe: true, children: version.version || "Latest" }))
12289
12482
  ]
12290
12483
  }
12291
12484
  ),
@@ -12359,7 +12552,7 @@ const SearchResultSkeletonItem = () => /* @__PURE__ */ jsxs("div", { class: "blo
12359
12552
  /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-full mb-2" }),
12360
12553
  /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-5/6" })
12361
12554
  ] });
12362
- function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool) {
12555
+ function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool, scrapeTool, docService) {
12363
12556
  server.get(
12364
12557
  "/libraries/:libraryName",
12365
12558
  async (request, reply) => {
@@ -12367,7 +12560,7 @@ function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool) {
12367
12560
  try {
12368
12561
  const result = await listLibrariesTool.execute();
12369
12562
  const libraryInfo = result.libraries.find(
12370
- (lib) => lib.name === libraryName
12563
+ (lib) => lib.name.toLowerCase() === libraryName.toLowerCase()
12371
12564
  );
12372
12565
  if (!libraryInfo) {
12373
12566
  reply.status(404).send("Library not found");
@@ -12404,7 +12597,7 @@ function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool) {
12404
12597
  reply.status(400).send("Search query is required.");
12405
12598
  return;
12406
12599
  }
12407
- const versionParam = version === "unversioned" ? void 0 : version;
12600
+ const versionParam = version === "latest" ? void 0 : version;
12408
12601
  try {
12409
12602
  const searchResult = await searchTool.execute({
12410
12603
  library: libraryName,
@@ -12423,9 +12616,128 @@ function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool) {
12423
12616
  }
12424
12617
  }
12425
12618
  );
12619
+ server.get(
12620
+ "/web/libraries/:libraryName/versions-list",
12621
+ async (request, reply) => {
12622
+ const { libraryName } = request.params;
12623
+ try {
12624
+ const result = await listLibrariesTool.execute();
12625
+ const libraryInfo = result.libraries.find(
12626
+ (lib) => lib.name.toLowerCase() === libraryName.toLowerCase()
12627
+ );
12628
+ if (!libraryInfo) {
12629
+ reply.status(404).send("Library not found");
12630
+ return;
12631
+ }
12632
+ const versions = libraryInfo.versions || [];
12633
+ reply.type("text/html; charset=utf-8");
12634
+ if (versions.length === 0) {
12635
+ return /* @__PURE__ */ jsx("p", { class: "text-sm text-gray-500 dark:text-gray-400 italic", children: "No versions indexed." });
12636
+ }
12637
+ return /* @__PURE__ */ jsx(Fragment, { children: versions.map((v) => {
12638
+ const adapted = {
12639
+ id: -1,
12640
+ ref: { library: libraryInfo.name, version: v.version },
12641
+ status: v.status,
12642
+ progress: v.progress,
12643
+ counts: {
12644
+ documents: v.documentCount,
12645
+ uniqueUrls: v.uniqueUrlCount
12646
+ },
12647
+ indexedAt: v.indexedAt,
12648
+ sourceUrl: v.sourceUrl ?? void 0
12649
+ };
12650
+ return /* @__PURE__ */ jsx(
12651
+ VersionDetailsRow,
12652
+ {
12653
+ libraryName: libraryInfo.name,
12654
+ version: adapted,
12655
+ showDelete: true,
12656
+ showRefresh: true
12657
+ }
12658
+ );
12659
+ }) });
12660
+ } catch (error) {
12661
+ logger.error(`Failed to fetch versions for ${libraryName}: ${error}`);
12662
+ reply.status(500).send("Internal Server Error");
12663
+ }
12664
+ }
12665
+ );
12666
+ server.get(
12667
+ "/web/libraries/:libraryName/add-version-button",
12668
+ async (request, reply) => {
12669
+ const { libraryName } = request.params;
12670
+ reply.type("text/html; charset=utf-8");
12671
+ return /* @__PURE__ */ jsx(AddVersionButton, { libraryName });
12672
+ }
12673
+ );
12674
+ server.get(
12675
+ "/web/libraries/:libraryName/add-version-form",
12676
+ async (request, reply) => {
12677
+ const { libraryName } = request.params;
12678
+ try {
12679
+ const result = await listLibrariesTool.execute();
12680
+ const libraryInfo = result.libraries.find(
12681
+ (lib) => lib.name.toLowerCase() === libraryName.toLowerCase()
12682
+ );
12683
+ if (!libraryInfo) {
12684
+ reply.status(404).send("Library not found");
12685
+ return;
12686
+ }
12687
+ const versions = libraryInfo.versions || [];
12688
+ const latestVersion = versions[0];
12689
+ let initialValues = {
12690
+ library: libraryName
12691
+ };
12692
+ if (latestVersion) {
12693
+ const summaries = await docService.listLibraries();
12694
+ const libSummary = summaries.find(
12695
+ (s) => s.library.toLowerCase() === libraryName.toLowerCase()
12696
+ );
12697
+ if (libSummary) {
12698
+ const versionSummary = libSummary.versions.find(
12699
+ (v) => v.ref.version === (latestVersion.version || "") || !latestVersion.version && v.ref.version === ""
12700
+ );
12701
+ if (versionSummary) {
12702
+ const scraperConfig = await docService.getScraperOptions(
12703
+ versionSummary.id
12704
+ );
12705
+ if (scraperConfig) {
12706
+ const opts = scraperConfig.options;
12707
+ initialValues = {
12708
+ library: libraryName,
12709
+ url: scraperConfig.sourceUrl,
12710
+ maxPages: opts.maxPages,
12711
+ maxDepth: opts.maxDepth,
12712
+ scope: opts.scope,
12713
+ includePatterns: opts.includePatterns?.join("\n"),
12714
+ excludePatterns: opts.excludePatterns?.join("\n"),
12715
+ scrapeMode: opts.scrapeMode,
12716
+ headers: opts.headers ? Object.entries(opts.headers).map(([name, value]) => ({
12717
+ name,
12718
+ value
12719
+ })) : void 0,
12720
+ followRedirects: opts.followRedirects,
12721
+ ignoreErrors: opts.ignoreErrors
12722
+ };
12723
+ }
12724
+ }
12725
+ }
12726
+ }
12727
+ reply.type("text/html; charset=utf-8");
12728
+ return /* @__PURE__ */ jsx(ScrapeFormContent, { initialValues, mode: "add-version" });
12729
+ } catch (error) {
12730
+ logger.error(
12731
+ `Failed to load add-version form for ${libraryName}: ${error}`
12732
+ );
12733
+ reply.type("text/html; charset=utf-8");
12734
+ return /* @__PURE__ */ jsx(Alert, { type: "error", message: "Failed to load the add version form." });
12735
+ }
12736
+ }
12737
+ );
12426
12738
  }
12427
12739
  const LibraryItem = ({ library }) => {
12428
- const versions = library.versions?.reverse() || [];
12740
+ const versions = library.versions || [];
12429
12741
  const latestVersion = versions[0];
12430
12742
  return (
12431
12743
  // Use Flowbite Card structure with updated padding and border, and white background
@@ -12514,7 +12826,7 @@ const LibraryList = ({ libraries }) => {
12514
12826
  }
12515
12827
  );
12516
12828
  };
12517
- function registerLibrariesRoutes(server, listLibrariesTool, removeTool) {
12829
+ function registerLibrariesRoutes(server, listLibrariesTool, removeTool, refreshVersionTool) {
12518
12830
  server.get("/web/libraries", async (_request, reply) => {
12519
12831
  try {
12520
12832
  const result = await listLibrariesTool.execute();
@@ -12529,9 +12841,16 @@ function registerLibrariesRoutes(server, listLibrariesTool, removeTool) {
12529
12841
  "/web/libraries/:libraryName/versions/:versionParam",
12530
12842
  async (request, reply) => {
12531
12843
  const { libraryName, versionParam } = request.params;
12532
- const version = versionParam === "unversioned" ? void 0 : versionParam;
12844
+ const version = versionParam === "latest" ? void 0 : versionParam;
12533
12845
  try {
12534
12846
  await removeTool.execute({ library: libraryName, version });
12847
+ const result = await listLibrariesTool.execute();
12848
+ const libraryStillExists = result.libraries.some(
12849
+ (lib) => lib.name.toLowerCase() === libraryName.toLowerCase()
12850
+ );
12851
+ if (!libraryStillExists) {
12852
+ reply.header("HX-Redirect", "/");
12853
+ }
12535
12854
  reply.status(204).send();
12536
12855
  } catch (error) {
12537
12856
  logger.error(
@@ -12541,6 +12860,46 @@ function registerLibrariesRoutes(server, listLibrariesTool, removeTool) {
12541
12860
  }
12542
12861
  }
12543
12862
  );
12863
+ server.post(
12864
+ "/web/libraries/:libraryName/versions/:versionParam/refresh",
12865
+ async (request, reply) => {
12866
+ const { libraryName, versionParam } = request.params;
12867
+ const version = versionParam === "latest" || versionParam === "" ? void 0 : versionParam;
12868
+ try {
12869
+ await refreshVersionTool.execute({
12870
+ library: libraryName,
12871
+ version,
12872
+ waitForCompletion: false
12873
+ });
12874
+ const versionDisplay = version || "latest";
12875
+ reply.header(
12876
+ "HX-Trigger",
12877
+ JSON.stringify({
12878
+ toast: {
12879
+ message: `Refresh started for ${libraryName}@${versionDisplay}`,
12880
+ type: "success"
12881
+ }
12882
+ })
12883
+ );
12884
+ reply.status(204).send();
12885
+ } catch (error) {
12886
+ logger.error(
12887
+ `Failed to refresh ${libraryName}@${versionParam}: ${error}`
12888
+ );
12889
+ const errorMessage = error instanceof Error ? error.message : "Failed to refresh version.";
12890
+ reply.header(
12891
+ "HX-Trigger",
12892
+ JSON.stringify({
12893
+ toast: {
12894
+ message: errorMessage,
12895
+ type: "error"
12896
+ }
12897
+ })
12898
+ );
12899
+ reply.status(500).send();
12900
+ }
12901
+ }
12902
+ );
12544
12903
  }
12545
12904
  function formatNumber(num) {
12546
12905
  if (num >= 1e9) {
@@ -12616,12 +12975,19 @@ async function registerWebService(server, docService, pipeline, eventBus, config
12616
12975
  const listJobsTool = new ListJobsTool(pipeline);
12617
12976
  const scrapeTool = new ScrapeTool(pipeline);
12618
12977
  const removeTool = new RemoveTool(docService, pipeline);
12978
+ const refreshVersionTool = new RefreshVersionTool(pipeline);
12619
12979
  const searchTool = new SearchTool(docService);
12620
12980
  const cancelJobTool = new CancelJobTool(pipeline);
12621
12981
  const clearCompletedJobsTool = new ClearCompletedJobsTool(pipeline);
12622
12982
  registerIndexRoute(server, config);
12623
- registerLibrariesRoutes(server, listLibrariesTool, removeTool);
12624
- registerLibraryDetailRoutes(server, listLibrariesTool, searchTool);
12983
+ registerLibrariesRoutes(server, listLibrariesTool, removeTool, refreshVersionTool);
12984
+ registerLibraryDetailRoutes(
12985
+ server,
12986
+ listLibrariesTool,
12987
+ searchTool,
12988
+ scrapeTool,
12989
+ docService
12990
+ );
12625
12991
  registerJobListRoutes(server, listJobsTool);
12626
12992
  registerNewJobRoutes(server, scrapeTool);
12627
12993
  registerCancelJobRoute(server, cancelJobTool);
@@ -12683,7 +13049,7 @@ class AppServer {
12683
13049
  try {
12684
13050
  if (telemetry.isEnabled()) {
12685
13051
  telemetry.setGlobalContext({
12686
- appVersion: "1.30.0",
13052
+ appVersion: "1.31.1",
12687
13053
  appPlatform: process.platform,
12688
13054
  appNodeVersion: process.version,
12689
13055
  appServicesEnabled: this.getActiveServicesList(),
@@ -14347,11 +14713,11 @@ class PipelineWorker {
14347
14713
  if (!scraperOptions.isRefresh) {
14348
14714
  await this.store.removeAllDocuments(library, version);
14349
14715
  logger.info(
14350
- `💾 Cleared store for ${library}@${version || "[no version]"} before scraping.`
14716
+ `💾 Cleared store for ${library}@${version || "latest"} before scraping.`
14351
14717
  );
14352
14718
  } else {
14353
14719
  logger.info(
14354
- `🔄 Refresh operation - preserving existing data for ${library}@${version || "[no version]"}.`
14720
+ `🔄 Refresh operation - preserving existing data for ${library}@${version || "latest"}.`
14355
14721
  );
14356
14722
  }
14357
14723
  await this.scraperService.scrape(
@@ -14503,7 +14869,7 @@ class PipelineManager {
14503
14869
  for (const version of runningVersions) {
14504
14870
  await this.store.updateVersionStatus(version.id, VersionStatus.QUEUED);
14505
14871
  logger.info(
14506
- `🔄 Reset interrupted job to QUEUED: ${version.library_name}@${version.name || "unversioned"}`
14872
+ `🔄 Reset interrupted job to QUEUED: ${version.library_name}@${version.name || "latest"}`
14507
14873
  );
14508
14874
  }
14509
14875
  const queuedVersions = await this.store.getVersionsByStatus([VersionStatus.QUEUED]);
@@ -14524,7 +14890,7 @@ class PipelineManager {
14524
14890
  parsedScraperOptions = JSON.parse(version.scraper_options);
14525
14891
  } catch (error) {
14526
14892
  logger.warn(
14527
- `⚠️ Failed to parse scraper options for ${version.library_name}@${version.name || "unversioned"}: ${error}`
14893
+ `⚠️ Failed to parse scraper options for ${version.library_name}@${version.name || "latest"}: ${error}`
14528
14894
  );
14529
14895
  }
14530
14896
  }
@@ -14631,7 +14997,7 @@ class PipelineManager {
14631
14997
  this.jobMap.set(jobId, job);
14632
14998
  this.jobQueue.push(jobId);
14633
14999
  logger.info(
14634
- `📝 Job enqueued: ${jobId} for ${library}${normalizedVersion ? `@${normalizedVersion}` : " (unversioned)"}`
15000
+ `📝 Job enqueued: ${jobId} for ${library}${normalizedVersion ? `@${normalizedVersion}` : " (latest)"}`
14635
15001
  );
14636
15002
  await this.updateJobStatus(job, PipelineJobStatus.QUEUED);
14637
15003
  if (this.isRunning) {
@@ -14665,7 +15031,7 @@ class PipelineManager {
14665
15031
  }
14666
15032
  if (versionInfo && versionInfo.status !== VersionStatus.COMPLETED) {
14667
15033
  logger.info(
14668
- `⚠️ Version ${library}@${normalizedVersion || "unversioned"} has status "${versionInfo.status}". Performing full re-scrape instead of refresh.`
15034
+ `⚠️ Version ${library}@${normalizedVersion || "latest"} has status "${versionInfo.status}". Performing full re-scrape instead of refresh.`
14669
15035
  );
14670
15036
  return this.enqueueJobWithStoredOptions(library, normalizedVersion);
14671
15037
  }
@@ -14677,11 +15043,11 @@ class PipelineManager {
14677
15043
  }
14678
15044
  if (pages.length === 0) {
14679
15045
  throw new Error(
14680
- `No pages found for ${library}@${normalizedVersion || "unversioned"}. Use scrape_docs to index it first.`
15046
+ `No pages found for ${library}@${normalizedVersion || "latest"}. Use scrape_docs to index it first.`
14681
15047
  );
14682
15048
  }
14683
15049
  logger.info(
14684
- `🔄 Preparing refresh job for ${library}@${normalizedVersion || "unversioned"} with ${pages.length} page(s)`
15050
+ `🔄 Preparing refresh job for ${library}@${normalizedVersion || "latest"} with ${pages.length} page(s)`
14685
15051
  );
14686
15052
  const initialQueue = pages.map((page) => ({
14687
15053
  url: page.url,
@@ -14705,7 +15071,7 @@ class PipelineManager {
14705
15071
  // Mark this as a refresh operation
14706
15072
  };
14707
15073
  logger.info(
14708
- `📝 Enqueueing refresh job for ${library}@${normalizedVersion || "unversioned"}`
15074
+ `📝 Enqueueing refresh job for ${library}@${normalizedVersion || "latest"}`
14709
15075
  );
14710
15076
  return this.enqueueScrapeJob(library, normalizedVersion, scraperOptions);
14711
15077
  } catch (error) {
@@ -14727,7 +15093,7 @@ class PipelineManager {
14727
15093
  const stored = await this.store.getScraperOptions(versionId);
14728
15094
  if (!stored) {
14729
15095
  throw new Error(
14730
- `No stored scraper options found for ${library}@${normalizedVersion || "unversioned"}`
15096
+ `No stored scraper options found for ${library}@${normalizedVersion || "latest"}`
14731
15097
  );
14732
15098
  }
14733
15099
  const storedOptions = stored.options;
@@ -14738,7 +15104,7 @@ class PipelineManager {
14738
15104
  ...storedOptions
14739
15105
  };
14740
15106
  logger.info(
14741
- `🔄 Re-indexing ${library}@${normalizedVersion || "unversioned"} with stored options from ${stored.sourceUrl}`
15107
+ `🔄 Re-indexing ${library}@${normalizedVersion || "latest"} with stored options from ${stored.sourceUrl}`
14742
15108
  );
14743
15109
  return this.enqueueScrapeJob(library, normalizedVersion, completeOptions);
14744
15110
  } catch (error) {
@@ -15774,7 +16140,7 @@ async function removeAction(library, options, command) {
15774
16140
  function createRemoveCommand(program) {
15775
16141
  return program.command("remove <library>").description("Remove documents for a specific library and version").option(
15776
16142
  "-v, --version <string>",
15777
- "Version to remove (optional, removes unversioned if omitted)"
16143
+ "Version to remove (optional, removes latest if omitted)"
15778
16144
  ).option(
15779
16145
  "--server-url <url>",
15780
16146
  "URL of external pipeline worker RPC (e.g., http://localhost:8080/api)"
@@ -16170,7 +16536,7 @@ function createCliProgram() {
16170
16536
  const commandStartTimes = /* @__PURE__ */ new Map();
16171
16537
  let globalEventBus = null;
16172
16538
  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(
16539
+ program.name("docs-mcp-server").description("Unified CLI, MCP Server, and Web Interface for Docs MCP Server.").version("1.31.1").addOption(
16174
16540
  new Option("--verbose", "Enable verbose (debug) logging").conflicts("silent")
16175
16541
  ).addOption(new Option("--silent", "Disable all logging except errors")).addOption(
16176
16542
  new Option("--telemetry", "Enable telemetry collection").env("DOCS_MCP_TELEMETRY").argParser((value) => {
@@ -16204,7 +16570,7 @@ function createCliProgram() {
16204
16570
  if (shouldEnableTelemetry()) {
16205
16571
  if (telemetry.isEnabled()) {
16206
16572
  telemetry.setGlobalContext({
16207
- appVersion: "1.30.0",
16573
+ appVersion: "1.31.1",
16208
16574
  appPlatform: process.platform,
16209
16575
  appNodeVersion: process.version,
16210
16576
  appInterface: "cli",