@arabold/docs-mcp-server 1.28.0 → 1.29.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
@@ -56,7 +56,7 @@ import { fastifyTRPCPlugin } from "@trpc/server/adapters/fastify";
56
56
  import { applyWSSHandler } from "@trpc/server/adapters/ws";
57
57
  import { observable } from "@trpc/server/observable";
58
58
  import { z as z$1 } from "zod";
59
- import { jsxs, jsx, Fragment } from "@kitajs/html/jsx-runtime";
59
+ import { jsx, jsxs, Fragment } from "@kitajs/html/jsx-runtime";
60
60
  import DOMPurify from "dompurify";
61
61
  import { escapeHtml } from "@kitajs/html";
62
62
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -1978,6 +1978,8 @@ const DEFAULT_HOST = "127.0.0.1";
1978
1978
  const DEFAULT_PAGE_TIMEOUT = 5e3;
1979
1979
  const FETCHER_MAX_RETRIES = 6;
1980
1980
  const FETCHER_BASE_DELAY = 1e3;
1981
+ const FETCHER_MAX_CACHE_ITEMS = 200;
1982
+ const FETCHER_MAX_CACHE_ITEM_SIZE_BYTES = 500 * 1024;
1981
1983
  const SPLITTER_MIN_CHUNK_SIZE = 500;
1982
1984
  const SPLITTER_PREFERRED_CHUNK_SIZE = 1500;
1983
1985
  const SPLITTER_MAX_CHUNK_SIZE = 5e3;
@@ -5497,8 +5499,78 @@ var ScrapeMode = /* @__PURE__ */ ((ScrapeMode2) => {
5497
5499
  ScrapeMode2["Auto"] = "auto";
5498
5500
  return ScrapeMode2;
5499
5501
  })(ScrapeMode || {});
5502
+ class SimpleMemoryCache {
5503
+ cache;
5504
+ maxSize;
5505
+ constructor(maxSize) {
5506
+ if (maxSize <= 0) {
5507
+ throw new Error("maxSize must be positive");
5508
+ }
5509
+ this.cache = /* @__PURE__ */ new Map();
5510
+ this.maxSize = maxSize;
5511
+ }
5512
+ /**
5513
+ * Retrieve a value from the cache.
5514
+ * Marks the key as recently used (moves to end of Map).
5515
+ */
5516
+ get(key) {
5517
+ const value = this.cache.get(key);
5518
+ if (value !== void 0) {
5519
+ this.cache.delete(key);
5520
+ this.cache.set(key, value);
5521
+ }
5522
+ return value;
5523
+ }
5524
+ /**
5525
+ * Store a value in the cache.
5526
+ * If cache is full, evicts the oldest entry first.
5527
+ */
5528
+ set(key, value) {
5529
+ if (this.cache.has(key)) {
5530
+ this.cache.delete(key);
5531
+ } else if (this.cache.size >= this.maxSize) {
5532
+ const oldestKey = this.cache.keys().next().value;
5533
+ if (oldestKey !== void 0) {
5534
+ this.cache.delete(oldestKey);
5535
+ }
5536
+ }
5537
+ this.cache.set(key, value);
5538
+ }
5539
+ /**
5540
+ * Check if a key exists in the cache.
5541
+ * Marks the key as recently used (moves to end of Map) to maintain LRU semantics.
5542
+ */
5543
+ has(key) {
5544
+ const exists = this.cache.has(key);
5545
+ if (exists) {
5546
+ const value = this.cache.get(key);
5547
+ if (value !== void 0) {
5548
+ this.cache.delete(key);
5549
+ this.cache.set(key, value);
5550
+ }
5551
+ }
5552
+ return exists;
5553
+ }
5554
+ /**
5555
+ * Get current cache size.
5556
+ */
5557
+ get size() {
5558
+ return this.cache.size;
5559
+ }
5560
+ /**
5561
+ * Clear all entries from the cache.
5562
+ */
5563
+ clear() {
5564
+ this.cache.clear();
5565
+ }
5566
+ }
5500
5567
  class HtmlPlaywrightMiddleware {
5501
5568
  browser = null;
5569
+ // Static LRU cache shared across all instances for all fetched resources
5570
+ // Max 200 entries, each limited in size to prevent caching large resources
5571
+ static resourceCache = new SimpleMemoryCache(
5572
+ FETCHER_MAX_CACHE_ITEMS
5573
+ );
5502
5574
  /**
5503
5575
  * Initializes the Playwright browser instance.
5504
5576
  * Consider making this more robust (e.g., lazy initialization, singleton).
@@ -5843,25 +5915,97 @@ class HtmlPlaywrightMiddleware {
5843
5915
  return [];
5844
5916
  }
5845
5917
  }
5918
+ /**
5919
+ * Sets up caching route interception for a Playwright page.
5920
+ * This handles:
5921
+ * - Aborting non-essential resources (images, fonts, media)
5922
+ * - Caching GET requests to speed up subsequent loads
5923
+ * - Forwarding custom headers and credentials for same-origin requests
5924
+ *
5925
+ * @param page The Playwright page to set up routing for
5926
+ * @param customHeaders Custom headers to forward with requests
5927
+ * @param credentials Optional credentials for same-origin requests
5928
+ * @param origin The origin for same-origin credential checking
5929
+ */
5930
+ async setupCachingRouteInterception(page, customHeaders = {}, credentials, origin) {
5931
+ await page.route("**/*", async (route) => {
5932
+ const reqUrl = route.request().url();
5933
+ const reqOrigin = (() => {
5934
+ try {
5935
+ return new URL(reqUrl).origin;
5936
+ } catch {
5937
+ return null;
5938
+ }
5939
+ })();
5940
+ const resourceType = route.request().resourceType();
5941
+ if (["image", "font", "media"].includes(resourceType)) {
5942
+ return route.abort();
5943
+ }
5944
+ if (route.request().method() === "GET") {
5945
+ const cached = HtmlPlaywrightMiddleware.resourceCache.get(reqUrl);
5946
+ if (cached !== void 0) {
5947
+ logger.debug(`✓ Cache hit for ${resourceType}: ${reqUrl}`);
5948
+ return route.fulfill({
5949
+ status: 200,
5950
+ contentType: cached.contentType,
5951
+ body: cached.body
5952
+ });
5953
+ }
5954
+ const headers2 = mergePlaywrightHeaders(
5955
+ route.request().headers(),
5956
+ customHeaders,
5957
+ credentials,
5958
+ origin,
5959
+ reqOrigin ?? void 0
5960
+ );
5961
+ const response = await route.fetch({ headers: headers2 });
5962
+ const body = await response.text();
5963
+ if (response.status() >= 200 && response.status() < 300 && body.length > 0) {
5964
+ const contentSizeBytes = Buffer.byteLength(body, "utf8");
5965
+ if (contentSizeBytes <= FETCHER_MAX_CACHE_ITEM_SIZE_BYTES) {
5966
+ const contentType = response.headers()["content-type"] || "application/octet-stream";
5967
+ HtmlPlaywrightMiddleware.resourceCache.set(reqUrl, { body, contentType });
5968
+ logger.debug(
5969
+ `Cached ${resourceType}: ${reqUrl} (${contentSizeBytes} bytes, cache size: ${HtmlPlaywrightMiddleware.resourceCache.size})`
5970
+ );
5971
+ } else {
5972
+ logger.debug(
5973
+ `Resource too large to cache: ${reqUrl} (${contentSizeBytes} bytes > ${FETCHER_MAX_CACHE_ITEM_SIZE_BYTES} bytes limit)`
5974
+ );
5975
+ }
5976
+ }
5977
+ return route.fulfill({ response });
5978
+ }
5979
+ const headers = mergePlaywrightHeaders(
5980
+ route.request().headers(),
5981
+ customHeaders,
5982
+ credentials,
5983
+ origin,
5984
+ reqOrigin ?? void 0
5985
+ );
5986
+ return route.continue({ headers });
5987
+ });
5988
+ }
5846
5989
  /**
5847
5990
  * Fetches content from a frame URL by navigating to it in a new page.
5991
+ * Uses LRU cache to avoid re-fetching identical frames across multiple pages.
5848
5992
  *
5849
5993
  * @param parentPage The parent page (used to resolve relative URLs and share context)
5850
5994
  * @param frameUrl The URL of the frame to fetch content from
5851
5995
  * @returns The HTML content of the frame
5852
5996
  */
5853
5997
  async fetchFrameContent(parentPage, frameUrl) {
5998
+ const resolvedUrl = new URL(frameUrl, parentPage.url()).href;
5999
+ const cached = HtmlPlaywrightMiddleware.resourceCache.get(resolvedUrl);
6000
+ if (cached !== void 0) {
6001
+ logger.debug(`✓ Cache hit for frame: ${resolvedUrl}`);
6002
+ return cached.body;
6003
+ }
6004
+ logger.debug(`Cache miss for frame: ${resolvedUrl}`);
5854
6005
  let framePage = null;
5855
6006
  try {
5856
- const resolvedUrl = new URL(frameUrl, parentPage.url()).href;
5857
6007
  framePage = await parentPage.context().newPage();
5858
- await framePage.route("**/*", async (route) => {
5859
- const resourceType = route.request().resourceType();
5860
- if (["image", "font", "media"].includes(resourceType)) {
5861
- return route.abort();
5862
- }
5863
- return route.continue();
5864
- });
6008
+ await this.setupCachingRouteInterception(framePage);
5865
6009
  logger.debug(`Fetching frame content from: ${resolvedUrl}`);
5866
6010
  await framePage.goto(resolvedUrl, {
5867
6011
  waitUntil: "load",
@@ -5873,8 +6017,23 @@ class HtmlPlaywrightMiddleware {
5873
6017
  "body",
5874
6018
  (el) => el.innerHTML
5875
6019
  );
6020
+ const content = bodyContent || "";
6021
+ const contentSizeBytes = Buffer.byteLength(content, "utf8");
6022
+ if (contentSizeBytes <= FETCHER_MAX_CACHE_ITEM_SIZE_BYTES) {
6023
+ HtmlPlaywrightMiddleware.resourceCache.set(resolvedUrl, {
6024
+ body: content,
6025
+ contentType: "text/html; charset=utf-8"
6026
+ });
6027
+ logger.debug(
6028
+ `Cached frame content: ${resolvedUrl} (${contentSizeBytes} bytes, cache size: ${HtmlPlaywrightMiddleware.resourceCache.size})`
6029
+ );
6030
+ } else {
6031
+ logger.debug(
6032
+ `Frame content too large to cache: ${resolvedUrl} (${contentSizeBytes} bytes > ${FETCHER_MAX_CACHE_ITEM_SIZE_BYTES} bytes limit)`
6033
+ );
6034
+ }
5876
6035
  logger.debug(`Successfully fetched frame content from: ${resolvedUrl}`);
5877
- return bodyContent || "";
6036
+ return content;
5878
6037
  } catch (error) {
5879
6038
  logger.debug(`Error fetching frame content from ${frameUrl}: ${error}`);
5880
6039
  return "";
@@ -5973,25 +6132,59 @@ ${frame.content}
5973
6132
  await this.injectShadowDOMExtractor(page);
5974
6133
  await page.route("**/*", async (route) => {
5975
6134
  const reqUrl = route.request().url();
5976
- const reqOrigin = (() => {
5977
- try {
5978
- return new URL(reqUrl).origin;
5979
- } catch {
5980
- return null;
5981
- }
5982
- })();
5983
6135
  if (reqUrl === context.source) {
5984
6136
  return route.fulfill({
5985
6137
  status: 200,
5986
6138
  contentType: "text/html; charset=utf-8",
5987
6139
  body: context.content
5988
- // context.content is always a string in middleware
5989
6140
  });
5990
6141
  }
6142
+ const reqOrigin = (() => {
6143
+ try {
6144
+ return new URL(reqUrl).origin;
6145
+ } catch {
6146
+ return null;
6147
+ }
6148
+ })();
5991
6149
  const resourceType = route.request().resourceType();
5992
6150
  if (["image", "font", "media"].includes(resourceType)) {
5993
6151
  return route.abort();
5994
6152
  }
6153
+ if (route.request().method() === "GET") {
6154
+ const cached = HtmlPlaywrightMiddleware.resourceCache.get(reqUrl);
6155
+ if (cached !== void 0) {
6156
+ logger.debug(`✓ Cache hit for ${resourceType}: ${reqUrl}`);
6157
+ return route.fulfill({
6158
+ status: 200,
6159
+ contentType: cached.contentType,
6160
+ body: cached.body
6161
+ });
6162
+ }
6163
+ const headers2 = mergePlaywrightHeaders(
6164
+ route.request().headers(),
6165
+ customHeaders,
6166
+ credentials ?? void 0,
6167
+ origin ?? void 0,
6168
+ reqOrigin ?? void 0
6169
+ );
6170
+ const response = await route.fetch({ headers: headers2 });
6171
+ const body = await response.text();
6172
+ if (response.status() >= 200 && response.status() < 300 && body.length > 0) {
6173
+ const contentSizeBytes = Buffer.byteLength(body, "utf8");
6174
+ if (contentSizeBytes <= FETCHER_MAX_CACHE_ITEM_SIZE_BYTES) {
6175
+ const contentType2 = response.headers()["content-type"] || "application/octet-stream";
6176
+ HtmlPlaywrightMiddleware.resourceCache.set(reqUrl, { body, contentType: contentType2 });
6177
+ logger.debug(
6178
+ `Cached ${resourceType}: ${reqUrl} (${contentSizeBytes} bytes, cache size: ${HtmlPlaywrightMiddleware.resourceCache.size})`
6179
+ );
6180
+ } else {
6181
+ logger.debug(
6182
+ `Resource too large to cache: ${reqUrl} (${contentSizeBytes} bytes > ${FETCHER_MAX_CACHE_ITEM_SIZE_BYTES} bytes limit)`
6183
+ );
6184
+ }
6185
+ }
6186
+ return route.fulfill({ response });
6187
+ }
5995
6188
  const headers = mergePlaywrightHeaders(
5996
6189
  route.request().headers(),
5997
6190
  customHeaders,
@@ -6172,6 +6365,8 @@ class HtmlSanitizerMiddleware {
6172
6365
  return;
6173
6366
  }
6174
6367
  try {
6368
+ const bodyBeforeSanitization = $("body").html() || "";
6369
+ const textLengthBefore = $("body").text().trim().length;
6175
6370
  const selectorsToRemove = [
6176
6371
  ...context.options.excludeSelectors || [],
6177
6372
  // Use options from the context
@@ -6184,9 +6379,13 @@ class HtmlSanitizerMiddleware {
6184
6379
  for (const selector of selectorsToRemove) {
6185
6380
  try {
6186
6381
  const elements = $(selector);
6187
- const count = elements.length;
6382
+ const filteredElements = elements.filter(function() {
6383
+ const tagName = $(this).prop("tagName")?.toLowerCase();
6384
+ return tagName !== "html" && tagName !== "body";
6385
+ });
6386
+ const count = filteredElements.length;
6188
6387
  if (count > 0) {
6189
- elements.remove();
6388
+ filteredElements.remove();
6190
6389
  removedCount += count;
6191
6390
  }
6192
6391
  } catch (selectorError) {
@@ -6199,6 +6398,13 @@ class HtmlSanitizerMiddleware {
6199
6398
  }
6200
6399
  }
6201
6400
  logger.debug(`Removed ${removedCount} elements for ${context.source}`);
6401
+ const textLengthAfter = $("body").text().trim().length;
6402
+ if (textLengthBefore > 0 && textLengthAfter === 0) {
6403
+ logger.warn(
6404
+ `⚠️ Sanitization removed all content from ${context.source}. Reverting to pre-sanitization state.`
6405
+ );
6406
+ $("body").html(bodyBeforeSanitization);
6407
+ }
6202
6408
  } catch (error) {
6203
6409
  logger.error(
6204
6410
  `❌ Error during HTML element removal for ${context.source}: ${error}`
@@ -6349,6 +6555,29 @@ class MarkdownMetadataExtractorMiddleware {
6349
6555
  }
6350
6556
  }
6351
6557
  class HtmlNormalizationMiddleware {
6558
+ // Known tracking/analytics domains and patterns to filter out
6559
+ trackingPatterns = [
6560
+ "adroll.com",
6561
+ "doubleclick.net",
6562
+ "google-analytics.com",
6563
+ "googletagmanager.com",
6564
+ "analytics.twitter.com",
6565
+ "twitter.com/1/i/adsct",
6566
+ "t.co/1/i/adsct",
6567
+ "bat.bing.com",
6568
+ "pixel.rubiconproject.com",
6569
+ "casalemedia.com",
6570
+ "tremorhub.com",
6571
+ "rlcdn.com",
6572
+ "facebook.com/tr",
6573
+ "linkedin.com/px",
6574
+ "quantserve.com",
6575
+ "scorecardresearch.com",
6576
+ "hotjar.com",
6577
+ "mouseflow.com",
6578
+ "crazyegg.com",
6579
+ "clarity.ms"
6580
+ ];
6352
6581
  async process(context, next) {
6353
6582
  if (!context.dom) {
6354
6583
  logger.debug(
@@ -6372,14 +6601,34 @@ class HtmlNormalizationMiddleware {
6372
6601
  }
6373
6602
  await next();
6374
6603
  }
6604
+ /**
6605
+ * Checks if an image should be kept based on its source URL.
6606
+ * Filters out tracking pixels and analytics beacons.
6607
+ */
6608
+ shouldKeepImage(src) {
6609
+ const srcLower = src.toLowerCase();
6610
+ return !this.trackingPatterns.some((pattern) => srcLower.includes(pattern));
6611
+ }
6375
6612
  /**
6376
6613
  * Normalizes image URLs by converting relative URLs to absolute URLs.
6614
+ * Removes tracking/analytics images.
6615
+ * Preserves data URIs (inline images).
6377
6616
  */
6378
6617
  normalizeImageUrls($, baseUrl) {
6379
6618
  $("img").each((_index, element) => {
6380
6619
  const $img = $(element);
6381
6620
  const src = $img.attr("src");
6382
- if (!src) return;
6621
+ if (!src) {
6622
+ $img.remove();
6623
+ return;
6624
+ }
6625
+ if (src.startsWith("data:")) {
6626
+ return;
6627
+ }
6628
+ if (!this.shouldKeepImage(src)) {
6629
+ $img.remove();
6630
+ return;
6631
+ }
6383
6632
  try {
6384
6633
  new URL(src);
6385
6634
  } catch {
@@ -6388,6 +6637,7 @@ class HtmlNormalizationMiddleware {
6388
6637
  $img.attr("src", absoluteUrl);
6389
6638
  } catch (error) {
6390
6639
  logger.debug(`Failed to resolve relative image URL: ${src} - ${error}`);
6640
+ $img.remove();
6391
6641
  }
6392
6642
  }
6393
6643
  });
@@ -8543,8 +8793,8 @@ class DocumentStore {
8543
8793
  return escapedTokens[0];
8544
8794
  }
8545
8795
  const exactMatch = `"${tokens.join(" ").replace(/"/g, '""')}"`;
8546
- const termsQuery = escapedTokens.join(" ");
8547
- return `${exactMatch} OR (${termsQuery})`;
8796
+ const termsQuery = escapedTokens.join(" OR ");
8797
+ return `${exactMatch} OR ${termsQuery}`;
8548
8798
  }
8549
8799
  /**
8550
8800
  * Initializes database connection and ensures readiness
@@ -8672,6 +8922,35 @@ class DocumentStore {
8672
8922
  throw new StoreError(`Failed to get library by ID: ${error}`);
8673
8923
  }
8674
8924
  }
8925
+ /**
8926
+ * Retrieves a library by its name.
8927
+ * @param name The library name to retrieve
8928
+ * @returns The library record, or null if not found
8929
+ */
8930
+ async getLibrary(name) {
8931
+ try {
8932
+ const normalizedName = name.toLowerCase();
8933
+ const row = this.statements.getLibraryIdByName.get(normalizedName);
8934
+ if (!row) {
8935
+ return null;
8936
+ }
8937
+ return { id: row.id, name: normalizedName };
8938
+ } catch (error) {
8939
+ throw new StoreError(`Failed to get library by name: ${error}`);
8940
+ }
8941
+ }
8942
+ /**
8943
+ * Deletes a library by its ID.
8944
+ * This should only be called when the library has no remaining versions.
8945
+ * @param libraryId The library ID to delete
8946
+ */
8947
+ async deleteLibrary(libraryId) {
8948
+ try {
8949
+ this.statements.deleteLibraryById.run(libraryId);
8950
+ } catch (error) {
8951
+ throw new StoreError(`Failed to delete library: ${error}`);
8952
+ }
8953
+ }
8675
8954
  /**
8676
8955
  * Stores scraper options for a version to enable reproducible indexing.
8677
8956
  * @param versionId The version ID to update
@@ -9533,30 +9812,26 @@ class DocumentManagementService {
9533
9812
  return this.store.findVersionsBySourceUrl(url);
9534
9813
  }
9535
9814
  /**
9536
- * Validates if a library exists in the store (either versioned or unversioned).
9815
+ * Validates if a library exists in the store.
9816
+ * Checks if the library record exists in the database, regardless of whether it has versions or documents.
9537
9817
  * Throws LibraryNotFoundInStoreError with suggestions if the library is not found.
9538
9818
  * @param library The name of the library to validate.
9539
9819
  * @throws {LibraryNotFoundInStoreError} If the library does not exist.
9540
9820
  */
9541
9821
  async validateLibraryExists(library) {
9542
9822
  logger.info(`🔎 Validating existence of library: ${library}`);
9543
- const normalizedLibrary = library.toLowerCase();
9544
- const versions = await this.listVersions(normalizedLibrary);
9545
- const hasUnversioned = await this.exists(normalizedLibrary, "");
9546
- if (versions.length === 0 && !hasUnversioned) {
9823
+ const libraryRecord = await this.store.getLibrary(library);
9824
+ if (!libraryRecord) {
9547
9825
  logger.warn(`⚠️ Library '${library}' not found.`);
9548
9826
  const allLibraries = await this.listLibraries();
9549
9827
  const libraryNames = allLibraries.map((lib) => lib.library);
9550
9828
  let suggestions = [];
9551
9829
  if (libraryNames.length > 0) {
9552
9830
  const fuse = new Fuse(libraryNames, {
9553
- // Configure fuse.js options if needed (e.g., threshold)
9554
- // isCaseSensitive: false, // Handled by normalizing library names
9555
- // includeScore: true,
9556
9831
  threshold: 0.7
9557
9832
  // Adjust threshold for desired fuzziness (0=exact, 1=match anything)
9558
9833
  });
9559
- const results = fuse.search(normalizedLibrary);
9834
+ const results = fuse.search(library.toLowerCase());
9560
9835
  suggestions = results.slice(0, 3).map((result) => result.item);
9561
9836
  logger.info(`🔍 Found suggestions: ${suggestions.join(", ")}`);
9562
9837
  }
@@ -9672,6 +9947,7 @@ class DocumentManagementService {
9672
9947
  /**
9673
9948
  * Completely removes a library version and all associated documents.
9674
9949
  * Also removes the library if no other versions remain.
9950
+ * If the specified version doesn't exist but the library exists with no versions, removes the library.
9675
9951
  * @param library Library name
9676
9952
  * @param version Version string (null/undefined for unversioned)
9677
9953
  */
@@ -9688,6 +9964,15 @@ class DocumentManagementService {
9688
9964
  logger.warn(
9689
9965
  `⚠️ Version ${library}@${normalizedVersion || "[no version]"} not found`
9690
9966
  );
9967
+ const libraryRecord = await this.store.getLibrary(library);
9968
+ if (libraryRecord) {
9969
+ const versions = await this.store.queryUniqueVersions(library);
9970
+ if (versions.length === 0) {
9971
+ logger.info(`🗑️ Library ${library} has no versions, removing library record`);
9972
+ await this.store.deleteLibrary(libraryRecord.id);
9973
+ logger.info(`🗑️ Completely removed library ${library} (had no versions)`);
9974
+ }
9975
+ }
9691
9976
  }
9692
9977
  this.eventBus.emit(EventType.LIBRARY_CHANGE, void 0);
9693
9978
  }
@@ -10414,13 +10699,135 @@ function registerEventsRoute(server, eventBus) {
10414
10699
  });
10415
10700
  });
10416
10701
  }
10702
+ const Toast = () => {
10703
+ return /* @__PURE__ */ jsx(
10704
+ "div",
10705
+ {
10706
+ "x-data": true,
10707
+ "x-show": "$store.toast.visible",
10708
+ "x-transition:enter": "transition ease-out duration-300",
10709
+ "x-transition:enter-start": "opacity-0 transform translate-y-2",
10710
+ "x-transition:enter-end": "opacity-100 transform translate-y-0",
10711
+ "x-transition:leave": "transition ease-in duration-200",
10712
+ "x-transition:leave-start": "opacity-100",
10713
+ "x-transition:leave-end": "opacity-0",
10714
+ class: "fixed top-5 right-5 z-50",
10715
+ style: "display: none;",
10716
+ children: /* @__PURE__ */ jsxs(
10717
+ "div",
10718
+ {
10719
+ class: "flex items-center w-full max-w-xs p-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800",
10720
+ role: "alert",
10721
+ children: [
10722
+ /* @__PURE__ */ jsxs(
10723
+ "div",
10724
+ {
10725
+ class: "inline-flex items-center justify-center shrink-0 w-8 h-8 rounded-lg",
10726
+ "x-bind:class": "{\n 'text-green-500 bg-green-100 dark:bg-green-800 dark:text-green-200': $store.toast.type === 'success',\n 'text-red-500 bg-red-100 dark:bg-red-800 dark:text-red-200': $store.toast.type === 'error',\n 'text-orange-500 bg-orange-100 dark:bg-orange-700 dark:text-orange-200': $store.toast.type === 'warning',\n 'text-blue-500 bg-blue-100 dark:bg-blue-800 dark:text-blue-200': $store.toast.type === 'info'\n }",
10727
+ children: [
10728
+ /* @__PURE__ */ jsx(
10729
+ "svg",
10730
+ {
10731
+ "x-show": "$store.toast.type === 'success'",
10732
+ class: "w-5 h-5",
10733
+ "aria-hidden": "true",
10734
+ xmlns: "http://www.w3.org/2000/svg",
10735
+ fill: "currentColor",
10736
+ viewBox: "0 0 20 20",
10737
+ children: /* @__PURE__ */ jsx("path", { d: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z" })
10738
+ }
10739
+ ),
10740
+ /* @__PURE__ */ jsx(
10741
+ "svg",
10742
+ {
10743
+ "x-show": "$store.toast.type === 'error'",
10744
+ class: "w-5 h-5",
10745
+ "aria-hidden": "true",
10746
+ xmlns: "http://www.w3.org/2000/svg",
10747
+ fill: "currentColor",
10748
+ viewBox: "0 0 20 20",
10749
+ children: /* @__PURE__ */ jsx("path", { d: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 11.793a1 1 0 1 1-1.414 1.414L10 11.414l-2.293 2.293a1 1 0 0 1-1.414-1.414L8.586 10 6.293 7.707a1 1 0 0 1 1.414-1.414L10 8.586l2.293-2.293a1 1 0 0 1 1.414 1.414L11.414 10l2.293 2.293Z" })
10750
+ }
10751
+ ),
10752
+ /* @__PURE__ */ jsx(
10753
+ "svg",
10754
+ {
10755
+ "x-show": "$store.toast.type === 'warning'",
10756
+ class: "w-5 h-5",
10757
+ "aria-hidden": "true",
10758
+ xmlns: "http://www.w3.org/2000/svg",
10759
+ fill: "currentColor",
10760
+ viewBox: "0 0 20 20",
10761
+ children: /* @__PURE__ */ jsx("path", { d: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM10 15a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-4a1 1 0 0 1-2 0V6a1 1 0 0 1 2 0v5Z" })
10762
+ }
10763
+ ),
10764
+ /* @__PURE__ */ jsx(
10765
+ "svg",
10766
+ {
10767
+ "x-show": "$store.toast.type === 'info'",
10768
+ class: "w-5 h-5",
10769
+ "aria-hidden": "true",
10770
+ xmlns: "http://www.w3.org/2000/svg",
10771
+ fill: "currentColor",
10772
+ viewBox: "0 0 20 20",
10773
+ children: /* @__PURE__ */ jsx("path", { d: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" })
10774
+ }
10775
+ )
10776
+ ]
10777
+ }
10778
+ ),
10779
+ /* @__PURE__ */ jsx(
10780
+ "div",
10781
+ {
10782
+ class: "ml-3 text-sm font-normal",
10783
+ "x-text": "$store.toast.message"
10784
+ }
10785
+ ),
10786
+ /* @__PURE__ */ jsxs(
10787
+ "button",
10788
+ {
10789
+ type: "button",
10790
+ class: "ml-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700",
10791
+ "x-on:click": "$store.toast.hide()",
10792
+ "aria-label": "Close",
10793
+ children: [
10794
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Close" }),
10795
+ /* @__PURE__ */ jsx(
10796
+ "svg",
10797
+ {
10798
+ class: "w-3 h-3",
10799
+ "aria-hidden": "true",
10800
+ xmlns: "http://www.w3.org/2000/svg",
10801
+ fill: "none",
10802
+ viewBox: "0 0 14 14",
10803
+ children: /* @__PURE__ */ jsx(
10804
+ "path",
10805
+ {
10806
+ stroke: "currentColor",
10807
+ "stroke-linecap": "round",
10808
+ "stroke-linejoin": "round",
10809
+ "stroke-width": "2",
10810
+ d: "m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
10811
+ }
10812
+ )
10813
+ }
10814
+ )
10815
+ ]
10816
+ }
10817
+ )
10818
+ ]
10819
+ }
10820
+ )
10821
+ }
10822
+ );
10823
+ };
10417
10824
  const Layout = ({
10418
10825
  title,
10419
10826
  version,
10420
10827
  children,
10421
10828
  eventClientConfig
10422
10829
  }) => {
10423
- const versionString = version || "1.28.0";
10830
+ const versionString = version || "1.29.0";
10424
10831
  const versionInitializer = `versionUpdate({ currentVersion: ${`'${versionString}'`} })`;
10425
10832
  return /* @__PURE__ */ jsxs("html", { lang: "en", children: [
10426
10833
  /* @__PURE__ */ jsxs("head", { children: [
@@ -10566,6 +10973,7 @@ const Layout = ({
10566
10973
  ` })
10567
10974
  ] }),
10568
10975
  /* @__PURE__ */ jsxs("body", { class: "bg-gray-50 dark:bg-gray-900", children: [
10976
+ /* @__PURE__ */ jsx(Toast, {}),
10569
10977
  /* @__PURE__ */ jsx(
10570
10978
  "header",
10571
10979
  {
@@ -10917,76 +11325,51 @@ const LoadingSpinner = () => /* @__PURE__ */ jsxs(
10917
11325
  const JobItem = ({ job }) => {
10918
11326
  job.dbStatus || job.status;
10919
11327
  const isActiveJob = job.dbStatus ? isActiveStatus(job.dbStatus) : job.status === PipelineJobStatus.QUEUED || job.status === PipelineJobStatus.RUNNING;
10920
- return /* @__PURE__ */ jsx("div", { class: "block p-3 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600", children: /* @__PURE__ */ jsxs("div", { class: "flex items-start justify-between", children: [
10921
- /* @__PURE__ */ jsxs("div", { class: "flex-1", children: [
10922
- /* @__PURE__ */ jsxs("p", { class: "text-sm font-medium text-gray-900 dark:text-white", children: [
10923
- /* @__PURE__ */ jsx("span", { safe: true, children: job.library }),
10924
- " ",
10925
- /* @__PURE__ */ jsx(VersionBadge, { version: job.version })
10926
- ] }),
10927
- /* @__PURE__ */ jsx("div", { class: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: job.startedAt ? /* @__PURE__ */ jsxs("div", { children: [
10928
- "Last Indexed:",
10929
- " ",
10930
- /* @__PURE__ */ jsx("span", { safe: true, children: new Date(job.startedAt).toLocaleString() })
10931
- ] }) : null }),
10932
- job.progress && job.progress.totalPages > 0 && isActiveJob ? /* @__PURE__ */ jsx("div", { class: "mt-2", children: /* @__PURE__ */ jsx(ProgressBar, { progress: job.progress }) }) : null,
10933
- job.errorMessage || job.error ? /* @__PURE__ */ jsxs("div", { class: "mt-2 p-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded text-xs", children: [
10934
- /* @__PURE__ */ jsx("div", { class: "font-medium text-red-800 dark:text-red-300 mb-1", children: "Error:" }),
10935
- /* @__PURE__ */ jsx("div", { safe: true, class: "text-red-700 dark:text-red-400", children: job.errorMessage || job.error })
10936
- ] }) : null
10937
- ] }),
10938
- /* @__PURE__ */ jsxs("div", { class: "flex flex-col items-end gap-2 ml-4", children: [
10939
- /* @__PURE__ */ jsxs("div", { class: "flex items-center gap-2", children: [
10940
- job.dbStatus ? /* @__PURE__ */ jsx(StatusBadge, { status: job.dbStatus }) : /* @__PURE__ */ jsx(
10941
- "span",
10942
- {
10943
- class: `px-1.5 py-0.5 text-xs font-medium rounded ${job.status === PipelineJobStatus.COMPLETED ? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300" : job.error ? "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300" : "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300"}`,
10944
- children: job.status
10945
- }
10946
- ),
10947
- isActiveJob && /* @__PURE__ */ jsxs(
10948
- "button",
10949
- {
10950
- type: "button",
10951
- class: "font-medium rounded-lg text-xs p-1 text-center inline-flex items-center transition-colors duration-150 ease-in-out border border-gray-300 bg-white text-red-600 hover:bg-red-50 focus:ring-4 focus:outline-none focus:ring-red-100 dark:border-gray-600 dark:bg-gray-800 dark:text-red-400 dark:hover:bg-gray-700 dark:focus:ring-red-900",
10952
- title: "Stop this job",
10953
- "x-data": "{}",
10954
- "x-on:click": `
10955
- if ($store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}') {
10956
- $store.confirmingAction.isStopping = true;
10957
- fetch('/web/jobs/' + '${job.id}' + '/cancel', {
10958
- method: 'POST',
10959
- headers: { 'Accept': 'application/json' },
10960
- })
10961
- .then(r => r.json())
10962
- .then(() => {
10963
- $store.confirmingAction.type = null;
10964
- $store.confirmingAction.id = null;
10965
- $store.confirmingAction.isStopping = false;
10966
- if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }
10967
- document.dispatchEvent(new CustomEvent('job-list-refresh'));
10968
- })
10969
- .catch(() => { $store.confirmingAction.isStopping = false; });
10970
- } else {
10971
- if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }
10972
- $store.confirmingAction.type = 'job-cancel';
10973
- $store.confirmingAction.id = '${job.id}';
10974
- $store.confirmingAction.isStopping = false;
10975
- $store.confirmingAction.timeoutId = setTimeout(() => {
10976
- $store.confirmingAction.type = null;
10977
- $store.confirmingAction.id = null;
10978
- $store.confirmingAction.isStopping = false;
10979
- $store.confirmingAction.timeoutId = null;
10980
- }, 3000);
10981
- }
10982
- `,
10983
- "x-bind:disabled": `$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}' && $store.confirmingAction.isStopping`,
10984
- children: [
10985
- /* @__PURE__ */ jsxs(
10986
- "span",
10987
- {
10988
- "x-show": `$store.confirmingAction.type !== 'job-cancel' || $store.confirmingAction.id !== '${job.id}' || $store.confirmingAction.isStopping`,
10989
- children: [
11328
+ return /* @__PURE__ */ jsx(
11329
+ "div",
11330
+ {
11331
+ id: `job-item-${job.id}`,
11332
+ class: "block p-3 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600",
11333
+ "data-job-id": job.id,
11334
+ "x-data": "{ jobId: $el.dataset.jobId }",
11335
+ "x-bind:hx-preserve": "$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === jobId",
11336
+ children: /* @__PURE__ */ jsxs("div", { class: "flex items-start justify-between", children: [
11337
+ /* @__PURE__ */ jsxs("div", { class: "flex-1", children: [
11338
+ /* @__PURE__ */ jsxs("p", { class: "text-sm font-medium text-gray-900 dark:text-white", children: [
11339
+ /* @__PURE__ */ jsx("span", { safe: true, children: job.library }),
11340
+ " ",
11341
+ /* @__PURE__ */ jsx(VersionBadge, { version: job.version })
11342
+ ] }),
11343
+ /* @__PURE__ */ jsx("div", { class: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: job.startedAt ? /* @__PURE__ */ jsxs("div", { children: [
11344
+ "Last Indexed:",
11345
+ " ",
11346
+ /* @__PURE__ */ jsx("span", { safe: true, children: new Date(job.startedAt).toLocaleString() })
11347
+ ] }) : null }),
11348
+ job.progress && job.progress.totalPages > 0 && isActiveJob ? /* @__PURE__ */ jsx("div", { class: "mt-2", children: /* @__PURE__ */ jsx(ProgressBar, { progress: job.progress }) }) : null,
11349
+ job.errorMessage || job.error ? /* @__PURE__ */ jsxs("div", { class: "mt-2 p-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded text-xs", children: [
11350
+ /* @__PURE__ */ jsx("div", { class: "font-medium text-red-800 dark:text-red-300 mb-1", children: "Error:" }),
11351
+ /* @__PURE__ */ jsx("div", { safe: true, class: "text-red-700 dark:text-red-400", children: job.errorMessage || job.error })
11352
+ ] }) : null
11353
+ ] }),
11354
+ /* @__PURE__ */ jsxs("div", { class: "flex flex-col items-end gap-2 ml-4", children: [
11355
+ /* @__PURE__ */ jsxs("div", { class: "flex items-center gap-2", children: [
11356
+ job.dbStatus ? /* @__PURE__ */ jsx(StatusBadge, { status: job.dbStatus }) : /* @__PURE__ */ jsx(
11357
+ "span",
11358
+ {
11359
+ class: `px-1.5 py-0.5 text-xs font-medium rounded ${job.status === PipelineJobStatus.COMPLETED ? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300" : job.error ? "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300" : "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300"}`,
11360
+ children: job.status
11361
+ }
11362
+ ),
11363
+ isActiveJob && /* @__PURE__ */ jsxs(
11364
+ "button",
11365
+ {
11366
+ type: "button",
11367
+ class: "font-medium rounded-lg text-xs p-1 text-center inline-flex items-center transition-colors duration-150 ease-in-out border border-gray-300 bg-white text-red-600 hover:bg-red-50 focus:ring-4 focus:outline-none focus:ring-red-100 dark:border-gray-600 dark:bg-gray-800 dark:text-red-400 dark:hover:bg-gray-700 dark:focus:ring-red-900",
11368
+ title: "Stop this job",
11369
+ "x-on:click": "\n if ($store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === jobId) {\n $store.confirmingAction.isStopping = true;\n fetch('/web/jobs/' + jobId + '/cancel', {\n method: 'POST',\n headers: { 'Accept': 'application/json' },\n })\n .then(r => r.json())\n .then(() => {\n $store.confirmingAction.type = null;\n $store.confirmingAction.id = null;\n $store.confirmingAction.isStopping = false;\n if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }\n document.dispatchEvent(new CustomEvent('job-list-refresh'));\n })\n .catch(() => { $store.confirmingAction.isStopping = false; });\n } else {\n if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }\n $store.confirmingAction.type = 'job-cancel';\n $store.confirmingAction.id = jobId;\n $store.confirmingAction.isStopping = false;\n $store.confirmingAction.timeoutId = setTimeout(() => {\n $store.confirmingAction.type = null;\n $store.confirmingAction.id = null;\n $store.confirmingAction.isStopping = false;\n $store.confirmingAction.timeoutId = null;\n }, 3000);\n }\n ",
11370
+ "x-bind:disabled": "$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === jobId && $store.confirmingAction.isStopping",
11371
+ children: [
11372
+ /* @__PURE__ */ jsxs("span", { "x-show": "$store.confirmingAction.type !== 'job-cancel' || $store.confirmingAction.id !== jobId || $store.confirmingAction.isStopping", children: [
10990
11373
  /* @__PURE__ */ jsx(
10991
11374
  "svg",
10992
11375
  {
@@ -10998,37 +11381,31 @@ const JobItem = ({ job }) => {
10998
11381
  }
10999
11382
  ),
11000
11383
  /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Stop job" })
11001
- ]
11002
- }
11003
- ),
11004
- /* @__PURE__ */ jsx(
11005
- "span",
11006
- {
11007
- "x-show": `$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}' && !$store.confirmingAction.isStopping`,
11008
- class: "px-2",
11009
- children: "Cancel?"
11010
- }
11011
- ),
11012
- /* @__PURE__ */ jsxs(
11013
- "span",
11014
- {
11015
- "x-show": `$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === '${job.id}' && $store.confirmingAction.isStopping`,
11016
- children: [
11384
+ ] }),
11385
+ /* @__PURE__ */ jsx(
11386
+ "span",
11387
+ {
11388
+ "x-show": "$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === jobId && !$store.confirmingAction.isStopping",
11389
+ class: "px-2",
11390
+ children: "Cancel?"
11391
+ }
11392
+ ),
11393
+ /* @__PURE__ */ jsxs("span", { "x-show": "$store.confirmingAction.type === 'job-cancel' && $store.confirmingAction.id === jobId && $store.confirmingAction.isStopping", children: [
11017
11394
  /* @__PURE__ */ jsx(LoadingSpinner, {}),
11018
11395
  /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Stopping..." })
11019
- ]
11020
- }
11021
- )
11022
- ]
11023
- }
11024
- )
11025
- ] }),
11026
- job.error ? (
11027
- // Keep the error badge for clarity if an error occurred
11028
- /* @__PURE__ */ jsx("span", { class: "bg-red-100 text-red-800 text-xs font-medium px-1.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300", children: "Error" })
11029
- ) : null
11030
- ] })
11031
- ] }) });
11396
+ ] })
11397
+ ]
11398
+ }
11399
+ )
11400
+ ] }),
11401
+ job.error ? (
11402
+ // Keep the error badge for clarity if an error occurred
11403
+ /* @__PURE__ */ jsx("span", { class: "bg-red-100 text-red-800 text-xs font-medium px-1.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300", children: "Error" })
11404
+ ) : null
11405
+ ] })
11406
+ ] })
11407
+ }
11408
+ );
11032
11409
  };
11033
11410
  const JobList = ({ jobs }) => /* @__PURE__ */ jsx("div", { id: "job-list", class: "space-y-2", children: jobs.length === 0 ? /* @__PURE__ */ jsx("p", { class: "text-center text-gray-500 dark:text-gray-400", children: "No pending jobs." }) : jobs.map((job) => /* @__PURE__ */ jsx(JobItem, { job })) });
11034
11411
  function registerJobListRoutes(server, listJobsTool) {
@@ -11048,7 +11425,7 @@ const Alert = ({ type, title, message }) => {
11048
11425
  iconSvg = /* @__PURE__ */ jsx(
11049
11426
  "svg",
11050
11427
  {
11051
- class: "flex-shrink-0 inline w-4 h-4 me-3",
11428
+ class: "shrink-0 inline w-4 h-4 me-3",
11052
11429
  "aria-hidden": "true",
11053
11430
  xmlns: "http://www.w3.org/2000/svg",
11054
11431
  fill: "currentColor",
@@ -11063,7 +11440,7 @@ const Alert = ({ type, title, message }) => {
11063
11440
  iconSvg = /* @__PURE__ */ jsx(
11064
11441
  "svg",
11065
11442
  {
11066
- class: "flex-shrink-0 inline w-4 h-4 me-3",
11443
+ class: "shrink-0 inline w-4 h-4 me-3",
11067
11444
  "aria-hidden": "true",
11068
11445
  xmlns: "http://www.w3.org/2000/svg",
11069
11446
  fill: "currentColor",
@@ -11078,7 +11455,7 @@ const Alert = ({ type, title, message }) => {
11078
11455
  iconSvg = /* @__PURE__ */ jsx(
11079
11456
  "svg",
11080
11457
  {
11081
- class: "flex-shrink-0 inline w-4 h-4 me-3",
11458
+ class: "shrink-0 inline w-4 h-4 me-3",
11082
11459
  "aria-hidden": "true",
11083
11460
  xmlns: "http://www.w3.org/2000/svg",
11084
11461
  fill: "currentColor",
@@ -11094,7 +11471,7 @@ const Alert = ({ type, title, message }) => {
11094
11471
  iconSvg = /* @__PURE__ */ jsx(
11095
11472
  "svg",
11096
11473
  {
11097
- class: "flex-shrink-0 inline w-4 h-4 me-3",
11474
+ class: "shrink-0 inline w-4 h-4 me-3",
11098
11475
  "aria-hidden": "true",
11099
11476
  xmlns: "http://www.w3.org/2000/svg",
11100
11477
  fill: "currentColor",
@@ -11584,18 +11961,7 @@ const ScrapeFormContent = ({
11584
11961
  ]
11585
11962
  }
11586
11963
  ),
11587
- /* @__PURE__ */ jsx("div", { id: "job-response", class: "mt-2 text-sm" }),
11588
- /* @__PURE__ */ jsx("script", { children: `
11589
- document.addEventListener('htmx:responseError', function(evt) {
11590
- // Handle error responses from the form submission
11591
- if (evt.detail.xhr && evt.detail.xhr.response) {
11592
- const responseDiv = document.getElementById('job-response');
11593
- if (responseDiv) {
11594
- responseDiv.innerHTML = evt.detail.xhr.response;
11595
- }
11596
- }
11597
- });
11598
- ` })
11964
+ /* @__PURE__ */ jsx("div", { id: "job-response", class: "mt-2 text-sm" })
11599
11965
  ] });
11600
11966
  };
11601
11967
  const ScrapeForm = ({ defaultExcludePatterns }) => /* @__PURE__ */ jsx("div", { id: "scrape-form-container", children: /* @__PURE__ */ jsx(ScrapeFormContent, { defaultExcludePatterns }) });
@@ -11821,6 +12187,10 @@ const VersionDetailsRow = ({
11821
12187
  {
11822
12188
  id: rowId,
11823
12189
  class: "flex justify-between items-center py-1 border-b border-gray-200 dark:border-gray-600 last:border-b-0",
12190
+ "data-library-name": libraryName,
12191
+ "data-version-param": versionParam,
12192
+ "x-data": "{ library: $el.dataset.libraryName, version: $el.dataset.versionParam, deleteId: $el.dataset.libraryName + ':' + $el.dataset.versionParam }",
12193
+ "x-bind:hx-preserve": "$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId",
11824
12194
  children: [
11825
12195
  /* @__PURE__ */ jsx(
11826
12196
  "span",
@@ -11853,64 +12223,41 @@ const VersionDetailsRow = ({
11853
12223
  type: "button",
11854
12224
  class: "ml-2 font-medium rounded-lg text-sm p-1 text-center inline-flex items-center transition-colors duration-150 ease-in-out",
11855
12225
  title: "Remove this version",
11856
- "x-data": "{}",
11857
- "x-bind:class": `$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' ? '${confirmingStateClasses}' : '${defaultStateClasses}'`,
11858
- "x-bind:disabled": `$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && $store.confirmingAction.isDeleting`,
11859
- "x-on:click": `
11860
- if ($store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}') {
11861
- $store.confirmingAction.isDeleting = true;
11862
- $el.dispatchEvent(new CustomEvent('confirmed-delete', { bubbles: true }));
11863
- } else {
11864
- if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }
11865
- $store.confirmingAction.type = 'version-delete';
11866
- $store.confirmingAction.id = '${libraryName}:${versionParam}';
11867
- $store.confirmingAction.isDeleting = false;
11868
- $store.confirmingAction.timeoutId = setTimeout(() => {
11869
- $store.confirmingAction.type = null;
11870
- $store.confirmingAction.id = null;
11871
- $store.confirmingAction.isDeleting = false;
11872
- $store.confirmingAction.timeoutId = null;
11873
- }, 3000);
11874
- }
11875
- `,
12226
+ "x-bind:class": `$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId ? '${confirmingStateClasses}' : '${defaultStateClasses}'`,
12227
+ "x-bind:disabled": "$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId && $store.confirmingAction.isDeleting",
12228
+ "x-on:click": "\n if ($store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId) {\n $store.confirmingAction.isDeleting = true;\n $el.dispatchEvent(new CustomEvent('confirmed-delete', { bubbles: true }));\n } else {\n if ($store.confirmingAction.timeoutId) { clearTimeout($store.confirmingAction.timeoutId); $store.confirmingAction.timeoutId = null; }\n $store.confirmingAction.type = 'version-delete';\n $store.confirmingAction.id = deleteId;\n $store.confirmingAction.isDeleting = false;\n $store.confirmingAction.timeoutId = setTimeout(() => {\n $store.confirmingAction.type = null;\n $store.confirmingAction.id = null;\n $store.confirmingAction.isDeleting = false;\n $store.confirmingAction.timeoutId = null;\n }, 3000);\n }\n ",
11876
12229
  "hx-delete": `/web/libraries/${encodeURIComponent(libraryName)}/versions/${encodeURIComponent(versionParam)}`,
11877
12230
  "hx-target": `#${rowId}`,
11878
12231
  "hx-swap": "outerHTML",
11879
12232
  "hx-trigger": "confirmed-delete",
11880
12233
  children: [
11881
- /* @__PURE__ */ jsxs(
11882
- "span",
11883
- {
11884
- "x-show": `!($store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && $store.confirmingAction.isDeleting)`,
11885
- children: [
11886
- /* @__PURE__ */ jsx(
11887
- "svg",
12234
+ /* @__PURE__ */ jsxs("span", { "x-show": "!($store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId && $store.confirmingAction.isDeleting)", children: [
12235
+ /* @__PURE__ */ jsx(
12236
+ "svg",
12237
+ {
12238
+ class: "w-4 h-4",
12239
+ "aria-hidden": "true",
12240
+ xmlns: "http://www.w3.org/2000/svg",
12241
+ fill: "none",
12242
+ viewBox: "0 0 18 20",
12243
+ children: /* @__PURE__ */ jsx(
12244
+ "path",
11888
12245
  {
11889
- class: "w-4 h-4",
11890
- "aria-hidden": "true",
11891
- xmlns: "http://www.w3.org/2000/svg",
11892
- fill: "none",
11893
- viewBox: "0 0 18 20",
11894
- children: /* @__PURE__ */ jsx(
11895
- "path",
11896
- {
11897
- stroke: "currentColor",
11898
- "stroke-linecap": "round",
11899
- "stroke-linejoin": "round",
11900
- "stroke-width": "2",
11901
- 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"
11902
- }
11903
- )
12246
+ stroke: "currentColor",
12247
+ "stroke-linecap": "round",
12248
+ "stroke-linejoin": "round",
12249
+ "stroke-width": "2",
12250
+ 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"
11904
12251
  }
11905
- ),
11906
- /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Remove version" })
11907
- ]
11908
- }
11909
- ),
12252
+ )
12253
+ }
12254
+ ),
12255
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Remove version" })
12256
+ ] }),
11910
12257
  /* @__PURE__ */ jsxs(
11911
12258
  "span",
11912
12259
  {
11913
- "x-show": `$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && !$store.confirmingAction.isDeleting`,
12260
+ "x-show": "$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId && !$store.confirmingAction.isDeleting",
11914
12261
  class: "mx-1",
11915
12262
  children: [
11916
12263
  "Confirm?",
@@ -11918,16 +12265,10 @@ const VersionDetailsRow = ({
11918
12265
  ]
11919
12266
  }
11920
12267
  ),
11921
- /* @__PURE__ */ jsxs(
11922
- "span",
11923
- {
11924
- "x-show": `$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === '${libraryName}:${versionParam}' && $store.confirmingAction.isDeleting`,
11925
- children: [
11926
- /* @__PURE__ */ jsx(LoadingSpinner, {}),
11927
- /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Loading..." })
11928
- ]
11929
- }
11930
- )
12268
+ /* @__PURE__ */ jsxs("span", { "x-show": "$store.confirmingAction.type === 'version-delete' && $store.confirmingAction.id === deleteId && $store.confirmingAction.isDeleting", children: [
12269
+ /* @__PURE__ */ jsx(LoadingSpinner, {}),
12270
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Loading..." })
12271
+ ] })
11931
12272
  ]
11932
12273
  }
11933
12274
  )
@@ -12135,7 +12476,8 @@ function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool) {
12135
12476
  } catch (error) {
12136
12477
  server.log.error(error, `Failed to search library ${libraryName}`);
12137
12478
  reply.type("text/html; charset=utf-8");
12138
- return /* @__PURE__ */ jsx("p", { class: "text-red-500 dark:text-red-400 italic", children: "An unexpected error occurred during the search." });
12479
+ const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred during the search.";
12480
+ return /* @__PURE__ */ jsx(Alert, { type: "error", message: errorMessage });
12139
12481
  }
12140
12482
  }
12141
12483
  );
@@ -12288,7 +12630,7 @@ class AppServer {
12288
12630
  try {
12289
12631
  if (telemetry.isEnabled()) {
12290
12632
  telemetry.setGlobalContext({
12291
- appVersion: "1.28.0",
12633
+ appVersion: "1.29.0",
12292
12634
  appPlatform: process.platform,
12293
12635
  appNodeVersion: process.version,
12294
12636
  appServicesEnabled: this.getActiveServicesList(),
@@ -12356,6 +12698,9 @@ class AppServer {
12356
12698
  await cleanupMcpService(this.mcpServer);
12357
12699
  }
12358
12700
  if (this.wss) {
12701
+ for (const client of this.wss.clients) {
12702
+ client.terminate();
12703
+ }
12359
12704
  await new Promise((resolve, reject) => {
12360
12705
  this.wss?.close((err) => {
12361
12706
  if (err) {
@@ -12374,6 +12719,9 @@ class AppServer {
12374
12719
  });
12375
12720
  }
12376
12721
  await telemetry.shutdown();
12722
+ if (this.server.server) {
12723
+ this.server.server.closeAllConnections();
12724
+ }
12377
12725
  await this.server.close();
12378
12726
  logger.info("🛑 AppServer stopped");
12379
12727
  } catch (error) {
@@ -15554,7 +15902,7 @@ function createCliProgram() {
15554
15902
  const commandStartTimes = /* @__PURE__ */ new Map();
15555
15903
  let globalEventBus = null;
15556
15904
  let globalTelemetryService = null;
15557
- program.name("docs-mcp-server").description("Unified CLI, MCP Server, and Web Interface for Docs MCP Server.").version("1.28.0").addOption(
15905
+ program.name("docs-mcp-server").description("Unified CLI, MCP Server, and Web Interface for Docs MCP Server.").version("1.29.0").addOption(
15558
15906
  new Option("--verbose", "Enable verbose (debug) logging").conflicts("silent")
15559
15907
  ).addOption(new Option("--silent", "Disable all logging except errors")).addOption(
15560
15908
  new Option("--telemetry", "Enable telemetry collection").env("DOCS_MCP_TELEMETRY").argParser((value) => {
@@ -15588,7 +15936,7 @@ function createCliProgram() {
15588
15936
  if (shouldEnableTelemetry()) {
15589
15937
  if (telemetry.isEnabled()) {
15590
15938
  telemetry.setGlobalContext({
15591
- appVersion: "1.28.0",
15939
+ appVersion: "1.29.0",
15592
15940
  appPlatform: process.platform,
15593
15941
  appNodeVersion: process.version,
15594
15942
  appInterface: "cli",