@arabold/docs-mcp-server 1.11.0 → 1.12.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/web.js ADDED
@@ -0,0 +1,937 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { program } from "commander";
4
+ import { P as PipelineJobStatus, j as ScrapeMode, l as logger, r as createJSDOM, D as DocumentManagementService, a as PipelineManager, t as getProjectRoot, e as ScrapeTool, f as ListLibrariesTool, S as SearchTool, u as DEFAULT_WEB_PORT } from "./DocumentManagementService-_qCZ1Hi2.js";
5
+ import path from "node:path";
6
+ import formBody from "@fastify/formbody";
7
+ import fastifyStatic from "@fastify/static";
8
+ import Fastify from "fastify";
9
+ import semver__default from "semver";
10
+ import "cheerio";
11
+ import "node:vm";
12
+ import "jsdom";
13
+ import "playwright";
14
+ import "@joplin/turndown-plugin-gfm";
15
+ import "turndown";
16
+ import { L as ListJobsTool, R as RemoveTool } from "./RemoveTool-DmB1YJTA.js";
17
+ import { jsxs, jsx, Fragment } from "@kitajs/html/jsx-runtime";
18
+ import { unified } from "unified";
19
+ import remarkParse from "remark-parse";
20
+ import remarkGfm from "remark-gfm";
21
+ import remarkHtml from "remark-html";
22
+ import DOMPurify from "dompurify";
23
+ const Layout = ({ title, children }) => /* @__PURE__ */ jsxs("html", { lang: "en", children: [
24
+ /* @__PURE__ */ jsxs("head", { children: [
25
+ /* @__PURE__ */ jsx("meta", { charset: "UTF-8" }),
26
+ /* @__PURE__ */ jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
27
+ /* @__PURE__ */ jsx("title", { safe: true, children: title }),
28
+ /* @__PURE__ */ jsx("link", { rel: "stylesheet", href: "/assets/main.css" }),
29
+ /* @__PURE__ */ jsx("style", { children: `
30
+ .htmx-indicator {
31
+ display: none;
32
+ }
33
+ .htmx-request .htmx-indicator {
34
+ display: block;
35
+ }
36
+ .htmx-request.htmx-indicator {
37
+ display: block;
38
+ }
39
+ /* Default: Hide skeleton, show results container */
40
+ #searchResultsContainer .search-skeleton { display: none; }
41
+ #searchResultsContainer .search-results { display: block; } /* Or as needed */
42
+
43
+ /* Request in progress: Show skeleton, hide results */
44
+ #searchResultsContainer.htmx-request .search-skeleton { display: block; } /* Or flex etc. */
45
+ #searchResultsContainer.htmx-request .search-results { display: none; }
46
+
47
+ /* Keep button spinner logic */
48
+ form .htmx-indicator .spinner { display: flex; }
49
+ form .htmx-indicator .search-text { display: none; }
50
+ form .spinner { display: none; }
51
+ ` })
52
+ ] }),
53
+ /* @__PURE__ */ jsxs("body", { class: "bg-gray-50 dark:bg-gray-900", children: [
54
+ /* @__PURE__ */ jsxs("div", { class: "container max-w-2xl mx-auto px-4 py-4", children: [
55
+ /* @__PURE__ */ jsx("header", { class: "mb-4", children: /* @__PURE__ */ jsx("h1", { class: "text-3xl font-bold text-gray-900 dark:text-white", children: /* @__PURE__ */ jsx("a", { href: "/", children: "MCP Docs" }) }) }),
56
+ /* @__PURE__ */ jsx("main", { children })
57
+ ] }),
58
+ /* @__PURE__ */ jsx("script", { type: "module", src: "/assets/main.js" })
59
+ ] })
60
+ ] });
61
+ function registerIndexRoute(server) {
62
+ server.get("/", async (_, reply) => {
63
+ reply.type("text/html");
64
+ return "<!DOCTYPE html>" + /* @__PURE__ */ jsxs(Layout, { title: "MCP Docs", children: [
65
+ /* @__PURE__ */ jsxs("section", { class: "mb-4 p-4 bg-white rounded-lg shadow dark:bg-gray-800 border border-gray-300 dark:border-gray-600", children: [
66
+ /* @__PURE__ */ jsx("h2", { class: "text-xl font-semibold mb-2 text-gray-900 dark:text-white", children: "Job Queue" }),
67
+ /* @__PURE__ */ jsx("div", { id: "jobQueue", "hx-get": "/api/jobs", "hx-trigger": "load, every 1s", children: /* @__PURE__ */ jsxs("div", { class: "animate-pulse", children: [
68
+ /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4" }),
69
+ /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5" }),
70
+ /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5" })
71
+ ] }) })
72
+ ] }),
73
+ /* @__PURE__ */ jsx("section", { class: "mb-8", children: /* @__PURE__ */ jsx("div", { id: "addJobForm", "hx-get": "/api/jobs/new", "hx-trigger": "load", children: /* @__PURE__ */ jsxs("div", { class: "p-6 bg-white rounded-lg shadow dark:bg-gray-800 animate-pulse", children: [
74
+ /* @__PURE__ */ jsx("div", { class: "h-6 bg-gray-200 rounded-full dark:bg-gray-700 w-1/3 mb-4" }),
75
+ /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5" }),
76
+ /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5" })
77
+ ] }) }) }),
78
+ /* @__PURE__ */ jsxs("div", { children: [
79
+ /* @__PURE__ */ jsx("h2", { class: "text-xl font-semibold mb-2 text-gray-900 dark:text-white", children: "Indexed Documentation" }),
80
+ /* @__PURE__ */ jsx(
81
+ "div",
82
+ {
83
+ id: "indexedDocs",
84
+ "hx-get": "/api/libraries",
85
+ "hx-trigger": "load, every 10s",
86
+ children: /* @__PURE__ */ jsxs("div", { class: "animate-pulse", children: [
87
+ /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4" }),
88
+ /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5" }),
89
+ /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 rounded-full dark:bg-gray-700 w-full mb-2.5" })
90
+ ] })
91
+ }
92
+ )
93
+ ] })
94
+ ] });
95
+ });
96
+ }
97
+ const VersionBadge = ({ version }) => {
98
+ if (!version) {
99
+ return null;
100
+ }
101
+ return /* @__PURE__ */ jsx("span", { class: "bg-purple-100 text-purple-800 text-xs font-medium me-2 px-1.5 py-0.5 rounded dark:bg-purple-900 dark:text-purple-300", children: /* @__PURE__ */ jsx("span", { safe: true, children: version }) });
102
+ };
103
+ const JobItem = ({ job }) => (
104
+ // Use Flowbite Card structure with reduced padding and added border
105
+ /* @__PURE__ */ jsx("div", { class: "block p-2 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600", children: /* @__PURE__ */ jsxs("div", { class: "flex items-center justify-between", children: [
106
+ /* @__PURE__ */ jsxs("div", { children: [
107
+ /* @__PURE__ */ jsxs("p", { class: "text-sm font-medium text-gray-900 dark:text-white", children: [
108
+ /* @__PURE__ */ jsx("span", { safe: true, children: job.library }),
109
+ " ",
110
+ /* @__PURE__ */ jsx(VersionBadge, { version: job.version })
111
+ ] }),
112
+ /* @__PURE__ */ jsxs("p", { class: "text-sm text-gray-500 dark:text-gray-400", children: [
113
+ "Indexed: ",
114
+ /* @__PURE__ */ jsx("span", { safe: true, children: new Date(job.createdAt).toLocaleString() })
115
+ ] })
116
+ ] }),
117
+ /* @__PURE__ */ jsxs("div", { class: "flex flex-col items-end gap-1", children: [
118
+ /* @__PURE__ */ jsx(
119
+ "span",
120
+ {
121
+ class: `px-1.5 py-0.5 text-xs font-medium me-2 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"}`,
122
+ children: job.status
123
+ }
124
+ ),
125
+ job.error && // Keep the error badge for clarity if an error occurred
126
+ /* @__PURE__ */ jsx("span", { class: "bg-red-100 text-red-800 text-xs font-medium me-2 px-1.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300", children: "Error" })
127
+ ] })
128
+ ] }) })
129
+ );
130
+ const JobList = ({ jobs }) => /* @__PURE__ */ jsx("div", { 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 })) });
131
+ function registerJobListRoutes(server, listJobsTool) {
132
+ server.get("/api/jobs", async () => {
133
+ const result = await listJobsTool.execute({});
134
+ return /* @__PURE__ */ jsx(JobList, { jobs: result.jobs });
135
+ });
136
+ }
137
+ const ScrapeFormContent = () => /* @__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", children: [
138
+ /* @__PURE__ */ jsx("h3", { class: "text-xl font-semibold text-gray-900 dark:text-white mb-2", children: "Queue New Scrape Job" }),
139
+ /* @__PURE__ */ jsxs(
140
+ "form",
141
+ {
142
+ "hx-post": "/api/jobs/scrape",
143
+ "hx-target": "#job-response",
144
+ "hx-swap": "innerHTML",
145
+ class: "space-y-2",
146
+ children: [
147
+ /* @__PURE__ */ jsxs("div", { children: [
148
+ /* @__PURE__ */ jsx(
149
+ "label",
150
+ {
151
+ for: "url",
152
+ class: "block text-sm font-medium text-gray-700 dark:text-gray-300",
153
+ children: "URL"
154
+ }
155
+ ),
156
+ /* @__PURE__ */ jsx(
157
+ "input",
158
+ {
159
+ type: "url",
160
+ name: "url",
161
+ id: "url",
162
+ required: true,
163
+ class: "mt-0.5 block w-full px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
164
+ }
165
+ )
166
+ ] }),
167
+ /* @__PURE__ */ jsxs("div", { children: [
168
+ /* @__PURE__ */ jsx(
169
+ "label",
170
+ {
171
+ for: "library",
172
+ class: "block text-sm font-medium text-gray-700 dark:text-gray-300",
173
+ children: "Library Name"
174
+ }
175
+ ),
176
+ /* @__PURE__ */ jsx(
177
+ "input",
178
+ {
179
+ type: "text",
180
+ name: "library",
181
+ id: "library",
182
+ required: true,
183
+ class: "mt-0.5 block w-full px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
184
+ }
185
+ )
186
+ ] }),
187
+ /* @__PURE__ */ jsxs("div", { children: [
188
+ /* @__PURE__ */ jsx(
189
+ "label",
190
+ {
191
+ for: "version",
192
+ class: "block text-sm font-medium text-gray-700 dark:text-gray-300",
193
+ children: "Version (optional)"
194
+ }
195
+ ),
196
+ /* @__PURE__ */ jsx(
197
+ "input",
198
+ {
199
+ type: "text",
200
+ name: "version",
201
+ id: "version",
202
+ class: "mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
203
+ }
204
+ )
205
+ ] }),
206
+ /* @__PURE__ */ jsxs("details", { class: "bg-gray-50 dark:bg-gray-900 p-2 rounded-md", children: [
207
+ /* @__PURE__ */ jsx("summary", { class: "cursor-pointer text-sm font-medium text-gray-600 dark:text-gray-400", children: "Advanced Options" }),
208
+ /* @__PURE__ */ jsxs("div", { class: "mt-2 space-y-2", children: [
209
+ /* @__PURE__ */ jsxs("div", { children: [
210
+ /* @__PURE__ */ jsx(
211
+ "label",
212
+ {
213
+ for: "maxPages",
214
+ class: "block text-sm font-medium text-gray-700 dark:text-gray-300",
215
+ children: "Max Pages"
216
+ }
217
+ ),
218
+ /* @__PURE__ */ jsx(
219
+ "input",
220
+ {
221
+ type: "number",
222
+ name: "maxPages",
223
+ id: "maxPages",
224
+ min: "1",
225
+ placeholder: "1000",
226
+ class: "mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
227
+ }
228
+ )
229
+ ] }),
230
+ /* @__PURE__ */ jsxs("div", { children: [
231
+ /* @__PURE__ */ jsx(
232
+ "label",
233
+ {
234
+ for: "maxDepth",
235
+ class: "block text-sm font-medium text-gray-700 dark:text-gray-300",
236
+ children: "Max Depth"
237
+ }
238
+ ),
239
+ /* @__PURE__ */ jsx(
240
+ "input",
241
+ {
242
+ type: "number",
243
+ name: "maxDepth",
244
+ id: "maxDepth",
245
+ min: "0",
246
+ placeholder: "3",
247
+ class: "mt-0.5 block w-full max-w-sm px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
248
+ }
249
+ )
250
+ ] }),
251
+ /* @__PURE__ */ jsxs("div", { children: [
252
+ /* @__PURE__ */ jsx(
253
+ "label",
254
+ {
255
+ for: "scope",
256
+ class: "block text-sm font-medium text-gray-700 dark:text-gray-300",
257
+ children: "Scope"
258
+ }
259
+ ),
260
+ /* @__PURE__ */ jsxs(
261
+ "select",
262
+ {
263
+ name: "scope",
264
+ id: "scope",
265
+ class: "mt-0.5 block w-full max-w-sm pl-2 pr-10 py-1 text-base border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",
266
+ children: [
267
+ /* @__PURE__ */ jsx("option", { value: "subpages", selected: true, children: "Subpages (Default)" }),
268
+ /* @__PURE__ */ jsx("option", { value: "hostname", children: "Hostname" }),
269
+ /* @__PURE__ */ jsx("option", { value: "domain", children: "Domain" })
270
+ ]
271
+ }
272
+ )
273
+ ] }),
274
+ /* @__PURE__ */ jsxs("div", { children: [
275
+ /* @__PURE__ */ jsx(
276
+ "label",
277
+ {
278
+ for: "scrapeMode",
279
+ class: "block text-sm font-medium text-gray-700 dark:text-gray-300",
280
+ children: "Scrape Mode"
281
+ }
282
+ ),
283
+ /* @__PURE__ */ jsxs(
284
+ "select",
285
+ {
286
+ name: "scrapeMode",
287
+ id: "scrapeMode",
288
+ class: "mt-0.5 block w-full max-w-sm pl-2 pr-10 py-1 text-base border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white",
289
+ children: [
290
+ /* @__PURE__ */ jsx("option", { value: ScrapeMode.Auto, selected: true, children: "Auto (Default)" }),
291
+ /* @__PURE__ */ jsx("option", { value: ScrapeMode.Fetch, children: "Fetch" }),
292
+ /* @__PURE__ */ jsx("option", { value: ScrapeMode.Playwright, children: "Playwright" })
293
+ ]
294
+ }
295
+ )
296
+ ] }),
297
+ /* @__PURE__ */ jsxs("div", { class: "flex items-center", children: [
298
+ /* @__PURE__ */ jsx(
299
+ "input",
300
+ {
301
+ id: "followRedirects",
302
+ name: "followRedirects",
303
+ type: "checkbox",
304
+ checked: true,
305
+ class: "h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700"
306
+ }
307
+ ),
308
+ /* @__PURE__ */ jsx(
309
+ "label",
310
+ {
311
+ for: "followRedirects",
312
+ class: "ml-1 block text-sm text-gray-900 dark:text-gray-300",
313
+ children: "Follow Redirects"
314
+ }
315
+ )
316
+ ] }),
317
+ /* @__PURE__ */ jsxs("div", { class: "flex items-center", children: [
318
+ /* @__PURE__ */ jsx(
319
+ "input",
320
+ {
321
+ id: "ignoreErrors",
322
+ name: "ignoreErrors",
323
+ type: "checkbox",
324
+ checked: true,
325
+ class: "h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700"
326
+ }
327
+ ),
328
+ /* @__PURE__ */ jsx(
329
+ "label",
330
+ {
331
+ for: "ignoreErrors",
332
+ class: "ml-1 block text-sm text-gray-900 dark:text-gray-300",
333
+ children: "Ignore Errors During Scraping"
334
+ }
335
+ )
336
+ ] })
337
+ ] })
338
+ ] }),
339
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
340
+ "button",
341
+ {
342
+ type: "submit",
343
+ class: "w-full flex justify-center py-1.5 px-3 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500",
344
+ children: "Queue Job"
345
+ }
346
+ ) })
347
+ ]
348
+ }
349
+ ),
350
+ /* @__PURE__ */ jsx("div", { id: "job-response", class: "mt-2 text-sm" })
351
+ ] });
352
+ const ScrapeForm = () => /* @__PURE__ */ jsx("div", { id: "scrape-form-container", children: /* @__PURE__ */ jsx(ScrapeFormContent, {}) });
353
+ const Alert = ({ type, title, message }) => {
354
+ let iconSvg;
355
+ let colorClasses;
356
+ let defaultTitle;
357
+ switch (type) {
358
+ case "success":
359
+ defaultTitle = "Success:";
360
+ colorClasses = "text-green-800 border-green-300 bg-green-50 dark:bg-gray-800 dark:text-green-400 dark:border-green-800";
361
+ iconSvg = /* @__PURE__ */ jsx(
362
+ "svg",
363
+ {
364
+ class: "flex-shrink-0 inline w-4 h-4 me-3",
365
+ "aria-hidden": "true",
366
+ xmlns: "http://www.w3.org/2000/svg",
367
+ fill: "currentColor",
368
+ viewBox: "0 0 20 20",
369
+ 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 9.5A9.5 9.5 0 0 1 10 19a9.46 9.46 0 0 1-1.671-.14c-.165-.05-.3-.19-.42-.335l-.165-.165c-.19-.2-.3-.425-.3-.655A4.2 4.2 0 0 1 4.5 10a4.25 4.25 0 0 1 7.462-2.882l1.217 1.217a3.175 3.175 0 0 0 4.5.01l.106-.106a.934.934 0 0 0 .1-.36ZM10 11a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z" })
370
+ }
371
+ );
372
+ break;
373
+ case "error":
374
+ defaultTitle = "Error:";
375
+ colorClasses = "text-red-800 border-red-300 bg-red-50 dark:bg-gray-800 dark:text-red-400 dark:border-red-800";
376
+ iconSvg = /* @__PURE__ */ jsx(
377
+ "svg",
378
+ {
379
+ class: "flex-shrink-0 inline w-4 h-4 me-3",
380
+ "aria-hidden": "true",
381
+ xmlns: "http://www.w3.org/2000/svg",
382
+ fill: "currentColor",
383
+ viewBox: "0 0 20 20",
384
+ 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-3h-1a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" })
385
+ }
386
+ );
387
+ break;
388
+ case "warning":
389
+ defaultTitle = "Warning:";
390
+ colorClasses = "text-yellow-800 border-yellow-300 bg-yellow-50 dark:bg-gray-800 dark:text-yellow-300 dark:border-yellow-800";
391
+ iconSvg = /* @__PURE__ */ jsx(
392
+ "svg",
393
+ {
394
+ class: "flex-shrink-0 inline w-4 h-4 me-3",
395
+ "aria-hidden": "true",
396
+ xmlns: "http://www.w3.org/2000/svg",
397
+ fill: "currentColor",
398
+ viewBox: "0 0 20 20",
399
+ 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-3h-1a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" })
400
+ }
401
+ );
402
+ break;
403
+ case "info":
404
+ default:
405
+ defaultTitle = "Info:";
406
+ colorClasses = "text-blue-800 border-blue-300 bg-blue-50 dark:bg-gray-800 dark:text-blue-400 dark:border-blue-800";
407
+ iconSvg = /* @__PURE__ */ jsx(
408
+ "svg",
409
+ {
410
+ class: "flex-shrink-0 inline w-4 h-4 me-3",
411
+ "aria-hidden": "true",
412
+ xmlns: "http://www.w3.org/2000/svg",
413
+ fill: "currentColor",
414
+ viewBox: "0 0 20 20",
415
+ 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" })
416
+ }
417
+ );
418
+ break;
419
+ }
420
+ const displayTitle = title ?? defaultTitle;
421
+ return /* @__PURE__ */ jsxs(
422
+ "div",
423
+ {
424
+ class: `flex items-center p-4 mb-4 text-sm border rounded-lg ${colorClasses}`,
425
+ role: "alert",
426
+ children: [
427
+ iconSvg,
428
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Info" }),
429
+ /* @__PURE__ */ jsxs("div", { children: [
430
+ displayTitle ? /* @__PURE__ */ jsx("span", { class: "font-medium", safe: true, children: displayTitle }) : null,
431
+ " ",
432
+ message
433
+ ] })
434
+ ]
435
+ }
436
+ );
437
+ };
438
+ function registerNewJobRoutes(server, scrapeTool) {
439
+ server.get("/api/jobs/new", async () => {
440
+ return /* @__PURE__ */ jsx(ScrapeForm, {});
441
+ });
442
+ server.post(
443
+ "/api/jobs/scrape",
444
+ async (request, reply) => {
445
+ const body = request.body;
446
+ reply.type("text/html");
447
+ try {
448
+ if (!body.url || !body.library) {
449
+ reply.status(400);
450
+ return /* @__PURE__ */ jsx(
451
+ Alert,
452
+ {
453
+ type: "error",
454
+ title: "Validation Error:",
455
+ message: "URL and Library Name are required."
456
+ }
457
+ );
458
+ }
459
+ const scrapeOptions = {
460
+ url: body.url,
461
+ library: body.library,
462
+ version: body.version || null,
463
+ // Handle empty string as null
464
+ waitForCompletion: false,
465
+ // Don't wait in UI
466
+ options: {
467
+ maxPages: body.maxPages ? Number.parseInt(body.maxPages, 10) : void 0,
468
+ maxDepth: body.maxDepth ? Number.parseInt(body.maxDepth, 10) : void 0,
469
+ scope: body.scope,
470
+ scrapeMode: body.scrapeMode,
471
+ // Checkboxes send 'on' when checked, otherwise undefined
472
+ followRedirects: body.followRedirects === "on",
473
+ ignoreErrors: body.ignoreErrors === "on"
474
+ }
475
+ };
476
+ const result = await scrapeTool.execute(scrapeOptions);
477
+ if ("jobId" in result) {
478
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
479
+ /* @__PURE__ */ jsx(
480
+ Alert,
481
+ {
482
+ type: "success",
483
+ message: /* @__PURE__ */ jsxs(Fragment, { children: [
484
+ "Job queued successfully! ID:",
485
+ " ",
486
+ /* @__PURE__ */ jsx("span", { safe: true, children: result.jobId })
487
+ ] })
488
+ }
489
+ ),
490
+ /* @__PURE__ */ jsx("div", { id: "scrape-form-container", "hx-swap-oob": "innerHTML", children: /* @__PURE__ */ jsx(ScrapeFormContent, {}) })
491
+ ] });
492
+ }
493
+ return /* @__PURE__ */ jsx(Alert, { type: "warning", message: "Job finished unexpectedly quickly." });
494
+ } catch (error) {
495
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
496
+ logger.error(`Scrape job submission failed: ${error}`);
497
+ reply.status(500);
498
+ return /* @__PURE__ */ jsx(
499
+ Alert,
500
+ {
501
+ type: "error",
502
+ message: /* @__PURE__ */ jsxs(Fragment, { children: [
503
+ "Failed to queue job: ",
504
+ errorMessage
505
+ ] })
506
+ }
507
+ );
508
+ }
509
+ }
510
+ );
511
+ }
512
+ const LoadingSpinner = () => /* @__PURE__ */ jsxs(
513
+ "svg",
514
+ {
515
+ class: "animate-spin h-4 w-4 text-white",
516
+ xmlns: "http://www.w3.org/2000/svg",
517
+ fill: "none",
518
+ viewBox: "0 0 24 24",
519
+ children: [
520
+ /* @__PURE__ */ jsx(
521
+ "circle",
522
+ {
523
+ class: "opacity-25",
524
+ cx: "12",
525
+ cy: "12",
526
+ r: "10",
527
+ stroke: "currentColor",
528
+ "stroke-width": "4"
529
+ }
530
+ ),
531
+ /* @__PURE__ */ jsx(
532
+ "path",
533
+ {
534
+ class: "opacity-75",
535
+ fill: "currentColor",
536
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
537
+ }
538
+ )
539
+ ]
540
+ }
541
+ );
542
+ const VersionDetailsRow = ({
543
+ version,
544
+ libraryName,
545
+ showDelete = true
546
+ // Default to true
547
+ }) => {
548
+ const indexedDate = version.indexedAt ? new Date(version.indexedAt).toLocaleDateString() : "N/A";
549
+ const versionLabel = version.version || "Unversioned";
550
+ const versionParam = version.version || "unversioned";
551
+ const sanitizedVersionParam = versionParam.replace(/[^a-zA-Z0-9-_]/g, "-");
552
+ const rowId = `row-${libraryName}-${sanitizedVersionParam}`;
553
+ 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";
554
+ 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";
555
+ return (
556
+ // Use flexbox for layout, add border between rows
557
+ /* @__PURE__ */ jsxs(
558
+ "div",
559
+ {
560
+ id: rowId,
561
+ class: "flex justify-between items-center py-1 border-b border-gray-200 dark:border-gray-600 last:border-b-0",
562
+ children: [
563
+ /* @__PURE__ */ jsx(
564
+ "span",
565
+ {
566
+ class: "text-sm text-gray-900 dark:text-white w-1/4 truncate",
567
+ title: versionLabel,
568
+ children: version.version ? /* @__PURE__ */ jsx(VersionBadge, { version: version.version }) : /* @__PURE__ */ jsx("span", { children: "Unversioned" })
569
+ }
570
+ ),
571
+ /* @__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: [
572
+ /* @__PURE__ */ jsxs("span", { title: "Number of unique pages indexed", children: [
573
+ "Pages:",
574
+ " ",
575
+ /* @__PURE__ */ jsx("span", { class: "font-semibold", safe: true, children: version.uniqueUrlCount.toLocaleString() })
576
+ ] }),
577
+ /* @__PURE__ */ jsxs("span", { title: "Number of indexed snippets", children: [
578
+ "Snippets:",
579
+ " ",
580
+ /* @__PURE__ */ jsx("span", { class: "font-semibold", safe: true, children: version.documentCount.toLocaleString() })
581
+ ] }),
582
+ /* @__PURE__ */ jsxs("span", { title: "Date last indexed", children: [
583
+ "Last Update:",
584
+ " ",
585
+ /* @__PURE__ */ jsx("span", { class: "font-semibold", safe: true, children: indexedDate })
586
+ ] })
587
+ ] }),
588
+ showDelete && /* @__PURE__ */ jsxs(
589
+ "button",
590
+ {
591
+ type: "button",
592
+ class: "ml-2 font-medium rounded-lg text-sm p-1 text-center inline-flex items-center transition-colors duration-150 ease-in-out",
593
+ title: "Remove this version",
594
+ "x-data": "{ confirming: false, isDeleting: false, timeoutId: null }",
595
+ "x-bind:class": `confirming ? "${confirmingStateClasses}" : "${defaultStateClasses}"`,
596
+ "x-bind:disabled": "isDeleting",
597
+ "x-on:click": "\n if (confirming) {\n clearTimeout(timeoutId);\n timeoutId = null;\n isDeleting = true; // Set deleting state directly\n // Dispatch a standard browser event instead of calling htmx directly\n $el.dispatchEvent(new CustomEvent('confirmed-delete', { bubbles: true }));\n } else {\n confirming = true;\n timeoutId = setTimeout(() => { confirming = false; timeoutId = null; }, 3000);\n }\n ",
598
+ "hx-delete": `/api/libraries/${libraryName}/versions/${versionParam}`,
599
+ "hx-target": `#${rowId}`,
600
+ "hx-swap": "outerHTML",
601
+ "hx-trigger": "confirmed-delete",
602
+ children: [
603
+ /* @__PURE__ */ jsxs("span", { "x-show": "!confirming && !isDeleting", children: [
604
+ /* @__PURE__ */ jsx(
605
+ "svg",
606
+ {
607
+ class: "w-4 h-4",
608
+ "aria-hidden": "true",
609
+ xmlns: "http://www.w3.org/2000/svg",
610
+ fill: "none",
611
+ viewBox: "0 0 18 20",
612
+ children: /* @__PURE__ */ jsx(
613
+ "path",
614
+ {
615
+ stroke: "currentColor",
616
+ "stroke-linecap": "round",
617
+ "stroke-linejoin": "round",
618
+ "stroke-width": "2",
619
+ 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"
620
+ }
621
+ )
622
+ }
623
+ ),
624
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Remove version" })
625
+ ] }),
626
+ /* @__PURE__ */ jsx("span", { "x-show": "confirming && !isDeleting", children: "Confirm?" }),
627
+ /* @__PURE__ */ jsxs("span", { "x-show": "isDeleting", children: [
628
+ /* @__PURE__ */ jsx(LoadingSpinner, {}),
629
+ /* @__PURE__ */ jsx("span", { class: "sr-only", children: "Loading..." })
630
+ ] })
631
+ ]
632
+ }
633
+ )
634
+ ]
635
+ }
636
+ )
637
+ );
638
+ };
639
+ const LibraryDetailCard = ({ library }) => (
640
+ // Use Flowbite Card structure with updated padding and border, and white background
641
+ /* @__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: [
642
+ /* @__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 }) }),
643
+ /* @__PURE__ */ jsx("div", { class: "mt-1", children: library.versions.length > 0 ? library.versions.sort((a, b) => {
644
+ if (!a.version) return -1;
645
+ if (!b.version) return 1;
646
+ return semver__default.compare(
647
+ semver__default.coerce(b.version)?.version ?? "0.0.0",
648
+ semver__default.coerce(a.version)?.version ?? "0.0.0"
649
+ );
650
+ }).map((version) => /* @__PURE__ */ jsx(
651
+ VersionDetailsRow,
652
+ {
653
+ libraryName: library.name,
654
+ version,
655
+ showDelete: false
656
+ }
657
+ )) : (
658
+ // Display message if no versions are indexed
659
+ /* @__PURE__ */ jsx("p", { class: "text-sm text-gray-500 dark:text-gray-400 italic", children: "No versions indexed." })
660
+ ) })
661
+ ] })
662
+ );
663
+ const LibrarySearchCard = ({ library }) => {
664
+ const sortedVersions = library.versions.sort((a, b) => {
665
+ if (!a.version) return -1;
666
+ if (!b.version) return 1;
667
+ return semver__default.compare(
668
+ semver__default.coerce(b.version)?.version ?? "0.0.0",
669
+ semver__default.coerce(a.version)?.version ?? "0.0.0"
670
+ );
671
+ });
672
+ 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: [
673
+ /* @__PURE__ */ jsxs("h2", { class: "text-xl font-semibold mb-2 text-gray-900 dark:text-white", safe: true, children: [
674
+ "Search ",
675
+ library.name,
676
+ " Documentation"
677
+ ] }),
678
+ /* @__PURE__ */ jsxs(
679
+ "form",
680
+ {
681
+ "hx-get": `/api/libraries/${encodeURIComponent(library.name)}/search`,
682
+ "hx-target": "#searchResultsContainer .search-results",
683
+ "hx-swap": "innerHTML",
684
+ "hx-indicator": "#searchResultsContainer",
685
+ class: "flex space-x-2",
686
+ children: [
687
+ /* @__PURE__ */ jsxs(
688
+ "select",
689
+ {
690
+ name: "version",
691
+ class: "w-40 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500",
692
+ children: [
693
+ /* @__PURE__ */ jsx("option", { value: "", children: "Latest" }),
694
+ " ",
695
+ sortedVersions.map((version) => /* @__PURE__ */ jsx("option", { value: version.version || "unversioned", safe: true, children: version.version || "Unversioned" }))
696
+ ]
697
+ }
698
+ ),
699
+ /* @__PURE__ */ jsx(
700
+ "input",
701
+ {
702
+ type: "text",
703
+ name: "query",
704
+ placeholder: "Search query...",
705
+ required: true,
706
+ class: "flex-grow bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
707
+ }
708
+ ),
709
+ /* @__PURE__ */ jsxs(
710
+ "button",
711
+ {
712
+ type: "submit",
713
+ class: "text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 relative",
714
+ children: [
715
+ /* @__PURE__ */ jsx("span", { class: "search-text", children: "Search" }),
716
+ /* @__PURE__ */ jsx("span", { class: "spinner absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(LoadingSpinner, {}) })
717
+ ]
718
+ }
719
+ )
720
+ ]
721
+ }
722
+ )
723
+ ] });
724
+ };
725
+ const SearchResultItem = async ({ result }) => {
726
+ const processor = unified().use(remarkParse).use(remarkGfm).use(remarkHtml);
727
+ const file = await processor.process(result.content);
728
+ const rawHtml = String(file);
729
+ const jsdom = createJSDOM("");
730
+ const purifier = DOMPurify(jsdom.window);
731
+ const safeHtml = purifier.sanitize(rawHtml);
732
+ return /* @__PURE__ */ jsxs("div", { class: "block px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-300 dark:border-gray-600 mb-2", children: [
733
+ /* @__PURE__ */ jsx("div", { class: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: /* @__PURE__ */ jsx(
734
+ "a",
735
+ {
736
+ href: result.url,
737
+ target: "_blank",
738
+ rel: "noopener noreferrer",
739
+ class: "underline underline-offset-4",
740
+ safe: true,
741
+ children: result.url
742
+ }
743
+ ) }),
744
+ /* @__PURE__ */ jsx("div", { class: "format dark:format-invert max-w-none", children: safeHtml })
745
+ ] });
746
+ };
747
+ const SearchResultList = ({ results }) => {
748
+ if (results.length === 0) {
749
+ return /* @__PURE__ */ jsx("p", { class: "text-gray-500 dark:text-gray-400 italic", children: "No results found." });
750
+ }
751
+ return /* @__PURE__ */ jsx("div", { class: "space-y-2", children: results.map((result) => /* @__PURE__ */ jsx(SearchResultItem, { result })) });
752
+ };
753
+ const SearchResultSkeletonItem = () => /* @__PURE__ */ jsxs("div", { class: "block px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm mb-2 animate-pulse", children: [
754
+ /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-3/4 mb-2" }),
755
+ /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-full mb-2" }),
756
+ /* @__PURE__ */ jsx("div", { class: "h-[0.8em] bg-gray-200 dark:bg-gray-700 rounded w-5/6" })
757
+ ] });
758
+ function registerLibraryDetailRoutes(server, listLibrariesTool, searchTool) {
759
+ server.get(
760
+ "/libraries/:libraryName",
761
+ async (request, reply) => {
762
+ const { libraryName } = request.params;
763
+ try {
764
+ const result = await listLibrariesTool.execute();
765
+ const libraryInfo = result.libraries.find(
766
+ (lib) => lib.name === libraryName
767
+ );
768
+ if (!libraryInfo) {
769
+ reply.status(404).send("Library not found");
770
+ return;
771
+ }
772
+ reply.type("text/html; charset=utf-8");
773
+ return "<!DOCTYPE html>" + /* @__PURE__ */ jsxs(Layout, { title: `MCP Docs - ${libraryInfo.name}`, children: [
774
+ /* @__PURE__ */ jsx(LibraryDetailCard, { library: libraryInfo }),
775
+ /* @__PURE__ */ jsx(LibrarySearchCard, { library: libraryInfo }),
776
+ /* @__PURE__ */ jsxs("div", { id: "searchResultsContainer", children: [
777
+ /* @__PURE__ */ jsxs("div", { class: "search-skeleton space-y-2", children: [
778
+ /* @__PURE__ */ jsx(SearchResultSkeletonItem, {}),
779
+ /* @__PURE__ */ jsx(SearchResultSkeletonItem, {}),
780
+ /* @__PURE__ */ jsx(SearchResultSkeletonItem, {})
781
+ ] }),
782
+ /* @__PURE__ */ jsx("div", { class: "search-results" })
783
+ ] })
784
+ ] });
785
+ } catch (error) {
786
+ server.log.error(
787
+ error,
788
+ `Failed to load library details for ${libraryName}`
789
+ );
790
+ reply.status(500).send("Internal Server Error");
791
+ }
792
+ }
793
+ );
794
+ server.get(
795
+ "/api/libraries/:libraryName/search",
796
+ async (request, reply) => {
797
+ const { libraryName } = request.params;
798
+ const { query, version } = request.query;
799
+ if (!query) {
800
+ reply.status(400).send("Search query is required.");
801
+ return;
802
+ }
803
+ const versionParam = version === "unversioned" ? void 0 : version;
804
+ try {
805
+ const searchResult = await searchTool.execute({
806
+ library: libraryName,
807
+ query,
808
+ version: versionParam,
809
+ limit: 10
810
+ // Limit search results
811
+ });
812
+ reply.type("text/html; charset=utf-8");
813
+ return /* @__PURE__ */ jsx(SearchResultList, { results: searchResult.results });
814
+ } catch (error) {
815
+ server.log.error(error, `Failed to search library ${libraryName}`);
816
+ reply.type("text/html; charset=utf-8");
817
+ return /* @__PURE__ */ jsx("p", { class: "text-red-500 dark:text-red-400 italic", children: "An unexpected error occurred during the search." });
818
+ }
819
+ }
820
+ );
821
+ }
822
+ const LibraryItem = ({ library }) => (
823
+ // Use Flowbite Card structure with updated padding and border, and white background
824
+ /* @__PURE__ */ jsxs("div", { class: "block px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-300 dark:border-gray-600", children: [
825
+ /* @__PURE__ */ jsx("h3", { class: "text-lg font-medium text-gray-900 dark:text-white mb-1", children: /* @__PURE__ */ jsx(
826
+ "a",
827
+ {
828
+ href: `/libraries/${encodeURIComponent(library.name)}`,
829
+ class: "hover:underline",
830
+ children: /* @__PURE__ */ jsx("span", { safe: true, children: library.name })
831
+ }
832
+ ) }),
833
+ /* @__PURE__ */ jsx("div", { class: "mt-1", children: library.versions.length > 0 ? library.versions.sort((a, b) => {
834
+ if (!a.version) return -1;
835
+ if (!b.version) return 1;
836
+ return semver__default.compare(
837
+ semver__default.coerce(b.version)?.version ?? "0.0.0",
838
+ semver__default.coerce(a.version)?.version ?? "0.0.0"
839
+ );
840
+ }).map((version) => /* @__PURE__ */ jsx(VersionDetailsRow, { libraryName: library.name, version })) : (
841
+ // Display message if no versions are indexed
842
+ /* @__PURE__ */ jsx("p", { class: "text-sm text-gray-500 dark:text-gray-400 italic", children: "No versions indexed." })
843
+ ) })
844
+ ] })
845
+ );
846
+ const LibraryList = ({ libraries }) => {
847
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", { class: "space-y-2", children: libraries.map((library) => /* @__PURE__ */ jsx(LibraryItem, { library })) }) });
848
+ };
849
+ function registerLibrariesRoutes(server, listLibrariesTool, removeTool) {
850
+ server.get("/api/libraries", async (_request, reply) => {
851
+ try {
852
+ const result = await listLibrariesTool.execute();
853
+ reply.type("text/html; charset=utf-8");
854
+ return /* @__PURE__ */ jsx(LibraryList, { libraries: result.libraries });
855
+ } catch (error) {
856
+ server.log.error(error, "Failed to list libraries");
857
+ reply.status(500).send("Internal Server Error");
858
+ }
859
+ });
860
+ server.delete(
861
+ "/api/libraries/:libraryName/versions/:versionParam",
862
+ async (request, reply) => {
863
+ const { libraryName, versionParam } = request.params;
864
+ const version = versionParam === "unversioned" ? void 0 : versionParam;
865
+ try {
866
+ await removeTool.execute({ library: libraryName, version });
867
+ reply.status(204).send();
868
+ } catch (error) {
869
+ server.log.error(
870
+ error,
871
+ `Failed to remove ${libraryName}@${versionParam}`
872
+ );
873
+ reply.status(500).send({ message: error.message || "Failed to remove version." });
874
+ }
875
+ }
876
+ );
877
+ }
878
+ async function startWebServer(port) {
879
+ const server = Fastify({
880
+ logger: false
881
+ // Use our own logger instead
882
+ });
883
+ await server.register(formBody);
884
+ const docService = new DocumentManagementService();
885
+ await docService.initialize();
886
+ const pipelineManager = new PipelineManager(docService);
887
+ await pipelineManager.start();
888
+ const listLibrariesTool = new ListLibrariesTool(docService);
889
+ const listJobsTool = new ListJobsTool(pipelineManager);
890
+ const scrapeTool = new ScrapeTool(docService, pipelineManager);
891
+ const removeTool = new RemoveTool(docService);
892
+ const searchTool = new SearchTool(docService);
893
+ await server.register(fastifyStatic, {
894
+ // Use project root to construct absolute path to public directory
895
+ root: path.join(getProjectRoot(), "public"),
896
+ prefix: "/",
897
+ index: false
898
+ // Disable automatic index.html serving
899
+ });
900
+ registerIndexRoute(server);
901
+ registerJobListRoutes(server, listJobsTool);
902
+ registerNewJobRoutes(server, scrapeTool);
903
+ registerLibrariesRoutes(server, listLibrariesTool, removeTool);
904
+ registerLibraryDetailRoutes(server, listLibrariesTool, searchTool);
905
+ server.addHook("onClose", async () => {
906
+ logger.info("Shutting down document service...");
907
+ await docService.shutdown();
908
+ logger.info("Document service shut down.");
909
+ });
910
+ try {
911
+ const address = await server.listen({ port, host: "0.0.0.0" });
912
+ logger.info(`🚀 Web UI available at ${address}`);
913
+ return server;
914
+ } catch (error) {
915
+ logger.error(`❌ Failed to start web UI: ${error}`);
916
+ await server.close();
917
+ throw error;
918
+ }
919
+ }
920
+ program.option(
921
+ "--port <number>",
922
+ "Port to listen on for the web interface",
923
+ `${DEFAULT_WEB_PORT}`
924
+ ).parse(process.argv);
925
+ const options = program.opts();
926
+ let currentServer = null;
927
+ async function main() {
928
+ try {
929
+ const port = process.env.WEB_PORT ? Number.parseInt(process.env.WEB_PORT, 10) : Number.parseInt(options.port, 10);
930
+ currentServer = await startWebServer(port);
931
+ } catch (error) {
932
+ logger.error(`❌ Fatal Error during startup: ${error}`);
933
+ process.exit(1);
934
+ }
935
+ }
936
+ main();
937
+ //# sourceMappingURL=web.js.map