claude-local-docs 1.0.2 → 1.0.13

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.
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Self-contained doc discovery: npm registry lookup, llms.txt probing,
3
+ * index detection & expansion, HTML-to-markdown conversion.
4
+ */
5
+ interface NpmInfo {
6
+ name: string;
7
+ version: string;
8
+ homepage?: string;
9
+ repoUrl?: string;
10
+ repoOrg?: string;
11
+ repoName?: string;
12
+ /** URL from package.json "llms" field (Colin Hacks convention) */
13
+ llmsUrl?: string;
14
+ /** URL from package.json "llmsFull" field (Colin Hacks convention) */
15
+ llmsFullUrl?: string;
16
+ }
17
+ interface IndexDetection {
18
+ isIndex: boolean;
19
+ links: {
20
+ text: string;
21
+ url: string;
22
+ }[];
23
+ }
24
+ export interface DiscoveryResult {
25
+ url: string;
26
+ content: string;
27
+ byteLength: number;
28
+ source: "npm-llms-field" | "llms-full.txt" | "llms.txt" | "llms.txt-index" | "homepage-html" | "github-raw" | "readme";
29
+ expandedUrls?: string[];
30
+ failedUrls?: string[];
31
+ warning?: string;
32
+ }
33
+ export declare function queryNpmRegistry(library: string): Promise<NpmInfo>;
34
+ export declare function generateCandidateUrls(info: NpmInfo): string[];
35
+ export declare function detectIndex(content: string, url: string): IndexDetection;
36
+ export declare function expandIndex(links: {
37
+ text: string;
38
+ url: string;
39
+ }[], baseUrl: string): Promise<{
40
+ content: string;
41
+ expandedUrls: string[];
42
+ failedUrls: string[];
43
+ }>;
44
+ export declare function htmlToMarkdown(html: string): string;
45
+ export declare function resolveDocsUrl(library: string): Promise<DiscoveryResult>;
46
+ export {};
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Self-contained doc discovery: npm registry lookup, llms.txt probing,
3
+ * index detection & expansion, HTML-to-markdown conversion.
4
+ */
5
+ import TurndownService from "turndown";
6
+ import { gfm } from "turndown-plugin-gfm";
7
+ import { fetchDocContent } from "./fetcher.js";
8
+ // ── npm registry ───────────────────────────────────────────────────────
9
+ export async function queryNpmRegistry(library) {
10
+ const url = `https://registry.npmjs.org/${encodeURIComponent(library)}/latest`;
11
+ const res = await fetch(url, {
12
+ headers: { Accept: "application/json", "User-Agent": "claude-local-docs/1.0" },
13
+ signal: AbortSignal.timeout(15_000),
14
+ });
15
+ if (!res.ok) {
16
+ throw new Error(`npm registry returned ${res.status} for ${library}`);
17
+ }
18
+ const pkg = (await res.json());
19
+ let repoUrl;
20
+ let repoOrg;
21
+ let repoName;
22
+ const rawRepo = typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url;
23
+ if (rawRepo) {
24
+ repoUrl = normalizeRepoUrl(rawRepo);
25
+ const m = repoUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
26
+ if (m) {
27
+ repoOrg = m[1];
28
+ repoName = m[2];
29
+ }
30
+ }
31
+ // Extract llms/llmsFull fields (Colin Hacks convention for AI autodiscovery)
32
+ const llmsUrl = typeof pkg.llms === "string" ? pkg.llms : undefined;
33
+ const llmsFullUrl = typeof pkg.llmsFull === "string" ? pkg.llmsFull : undefined;
34
+ return {
35
+ name: pkg.name,
36
+ version: pkg.version,
37
+ homepage: pkg.homepage || undefined,
38
+ repoUrl,
39
+ repoOrg,
40
+ repoName,
41
+ llmsUrl,
42
+ llmsFullUrl,
43
+ };
44
+ }
45
+ function normalizeRepoUrl(raw) {
46
+ return raw
47
+ .replace(/^git\+/, "")
48
+ .replace(/^git:\/\//, "https://")
49
+ .replace(/^ssh:\/\/git@github\.com/, "https://github.com")
50
+ .replace(/\.git$/, "");
51
+ }
52
+ // ── Candidate URL generation ───────────────────────────────────────────
53
+ export function generateCandidateUrls(info) {
54
+ const urls = [];
55
+ const seen = new Set();
56
+ const add = (url) => {
57
+ if (!seen.has(url)) {
58
+ seen.add(url);
59
+ urls.push(url);
60
+ }
61
+ };
62
+ // ── Priority 1: package.json llms/llmsFull fields (most reliable) ──
63
+ if (info.llmsFullUrl)
64
+ add(info.llmsFullUrl);
65
+ if (info.llmsUrl)
66
+ add(info.llmsUrl);
67
+ // ── Priority 2: homepage-based probing ──
68
+ if (info.homepage) {
69
+ const hp = info.homepage.replace(/\/$/, "");
70
+ add(`${hp}/llms-full.txt`);
71
+ add(`${hp}/llms.txt`);
72
+ // docs.{domain} variant (Mintlify/GitBook auto-generate pattern)
73
+ try {
74
+ const u = new URL(hp);
75
+ if (!u.hostname.startsWith("docs.")) {
76
+ const docsHost = `docs.${u.hostname}`;
77
+ add(`${u.protocol}//${docsHost}/llms-full.txt`);
78
+ add(`${u.protocol}//${docsHost}/llms.txt`);
79
+ }
80
+ }
81
+ catch {
82
+ // invalid URL — skip
83
+ }
84
+ // /docs/ subpath variant
85
+ try {
86
+ const u = new URL(hp);
87
+ add(`${u.origin}/docs/llms-full.txt`);
88
+ add(`${u.origin}/docs/llms.txt`);
89
+ }
90
+ catch {
91
+ // invalid URL — skip
92
+ }
93
+ // llms.{domain} subdomain (Motion-style pattern)
94
+ try {
95
+ const u = new URL(hp);
96
+ if (!u.hostname.startsWith("llms.")) {
97
+ add(`${u.protocol}//llms.${u.hostname}/llms-full.txt`);
98
+ add(`${u.protocol}//llms.${u.hostname}/llms.txt`);
99
+ }
100
+ }
101
+ catch {
102
+ // invalid URL — skip
103
+ }
104
+ }
105
+ // ── Priority 3: GitHub raw ──
106
+ if (info.repoOrg && info.repoName) {
107
+ for (const branch of ["main", "master"]) {
108
+ add(`https://raw.githubusercontent.com/${info.repoOrg}/${info.repoName}/${branch}/llms-full.txt`);
109
+ add(`https://raw.githubusercontent.com/${info.repoOrg}/${info.repoName}/${branch}/llms.txt`);
110
+ }
111
+ // README.md fallback (before homepage HTML)
112
+ for (const branch of ["main", "master"]) {
113
+ add(`https://raw.githubusercontent.com/${info.repoOrg}/${info.repoName}/${branch}/README.md`);
114
+ }
115
+ }
116
+ // ── Priority 4: Homepage HTML fallback (last) ──
117
+ if (info.homepage) {
118
+ add(info.homepage);
119
+ }
120
+ return urls;
121
+ }
122
+ // ── Index detection ────────────────────────────────────────────────────
123
+ const MD_LINK_RE = /\[([^\]]*)\]\(([^)]+)\)/g;
124
+ export function detectIndex(content, url) {
125
+ // Large files are content, not an index
126
+ if (content.length > 100_000) {
127
+ return { isIndex: false, links: [] };
128
+ }
129
+ const links = [];
130
+ for (const m of content.matchAll(MD_LINK_RE)) {
131
+ const href = m[2].trim();
132
+ // Only keep http(s) links and relative paths that look like docs
133
+ if (href.startsWith("http") || href.startsWith("/") || href.endsWith(".md") || href.endsWith(".txt")) {
134
+ links.push({ text: m[1], url: href });
135
+ }
136
+ }
137
+ if (links.length < 5) {
138
+ return { isIndex: false, links: [] };
139
+ }
140
+ // Count lines that are mostly links vs prose
141
+ const lines = content.split("\n").filter((l) => l.trim().length > 0);
142
+ const linkTestRe = /\[[^\]]*\]\([^)]+\)/;
143
+ let linkLines = 0;
144
+ for (const line of lines) {
145
+ if (linkTestRe.test(line))
146
+ linkLines++;
147
+ }
148
+ const linkRatio = linkLines / (lines.length || 1);
149
+ const isIndex = linkRatio > 0.5 && links.length > 5;
150
+ return { isIndex, links };
151
+ }
152
+ // ── Index expansion ────────────────────────────────────────────────────
153
+ const MAX_EXPAND_LINKS = 100;
154
+ const CONCURRENCY = 5;
155
+ const INTER_REQUEST_DELAY_MS = 200;
156
+ export async function expandIndex(links, baseUrl) {
157
+ const expandedUrls = [];
158
+ const failedUrls = [];
159
+ const parts = [];
160
+ // Resolve and dedupe URLs
161
+ const resolved = [];
162
+ const seen = new Set();
163
+ for (const link of links.slice(0, MAX_EXPAND_LINKS)) {
164
+ const absolute = resolveUrl(link.url, baseUrl);
165
+ if (!absolute || seen.has(absolute))
166
+ continue;
167
+ // Skip binary / non-doc URLs
168
+ if (/\.(png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|zip|tar|gz|mp4|mp3|pdf)$/i.test(absolute)) {
169
+ continue;
170
+ }
171
+ seen.add(absolute);
172
+ resolved.push({ text: link.text, absolute });
173
+ }
174
+ // Fetch with concurrency limit
175
+ let i = 0;
176
+ while (i < resolved.length) {
177
+ const batch = resolved.slice(i, i + CONCURRENCY);
178
+ const results = await Promise.allSettled(batch.map(async (item) => {
179
+ const result = await fetchDocContent(item.absolute, { timeoutMs: 30_000 });
180
+ return { item, result };
181
+ }));
182
+ for (let j = 0; j < results.length; j++) {
183
+ const r = results[j];
184
+ if (r.status === "fulfilled" && r.value.result.ok) {
185
+ const { item, result } = r.value;
186
+ const ct = result.contentType.toLowerCase();
187
+ const md = ct.includes("html") ? htmlToMarkdown(result.content) : result.content;
188
+ parts.push(`## ${item.text}\n\n${md}`);
189
+ expandedUrls.push(item.absolute);
190
+ }
191
+ else if (r.status === "fulfilled") {
192
+ failedUrls.push(r.value.item.absolute);
193
+ }
194
+ else {
195
+ failedUrls.push(batch[j].absolute);
196
+ }
197
+ }
198
+ i += CONCURRENCY;
199
+ if (i < resolved.length) {
200
+ await sleep(INTER_REQUEST_DELAY_MS);
201
+ }
202
+ }
203
+ return {
204
+ content: parts.join("\n\n---\n\n"),
205
+ expandedUrls,
206
+ failedUrls,
207
+ };
208
+ }
209
+ function resolveUrl(href, base) {
210
+ try {
211
+ return new URL(href, base).toString();
212
+ }
213
+ catch {
214
+ return null;
215
+ }
216
+ }
217
+ function sleep(ms) {
218
+ return new Promise((resolve) => setTimeout(resolve, ms));
219
+ }
220
+ // ── HTML → Markdown ────────────────────────────────────────────────────
221
+ // Simple tag stripper for specific elements (works on raw HTML before turndown)
222
+ function stripTags(html, tags) {
223
+ let result = html;
224
+ for (const tag of tags) {
225
+ // Remove opening and closing tags and everything between them
226
+ const re = new RegExp(`<${tag}[^>]*>[\\s\\S]*?</${tag}>`, "gi");
227
+ result = result.replace(re, "");
228
+ // Also remove self-closing variants
229
+ const selfRe = new RegExp(`<${tag}[^/>]*/?>`, "gi");
230
+ result = result.replace(selfRe, "");
231
+ }
232
+ return result;
233
+ }
234
+ function extractMainContent(html) {
235
+ // Try <main>, then <article>, then <body>, else full HTML
236
+ for (const tag of ["main", "article"]) {
237
+ const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`, "i");
238
+ const m = html.match(re);
239
+ if (m)
240
+ return m[1];
241
+ }
242
+ const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
243
+ if (bodyMatch)
244
+ return bodyMatch[1];
245
+ return html;
246
+ }
247
+ let turndownInstance = null;
248
+ function getTurndown() {
249
+ if (turndownInstance)
250
+ return turndownInstance;
251
+ turndownInstance = new TurndownService({
252
+ headingStyle: "atx",
253
+ codeBlockStyle: "fenced",
254
+ bulletListMarker: "-",
255
+ });
256
+ turndownInstance.use(gfm);
257
+ turndownInstance.addRule("removeImages", {
258
+ filter: "img",
259
+ replacement: () => "",
260
+ });
261
+ return turndownInstance;
262
+ }
263
+ export function htmlToMarkdown(html) {
264
+ let content = extractMainContent(html);
265
+ content = stripTags(content, ["nav", "footer", "header", "script", "style", "aside", "noscript"]);
266
+ return getTurndown().turndown(content);
267
+ }
268
+ function classifySource(url, npmInfo) {
269
+ // Check if URL was from package.json llms/llmsFull fields
270
+ if (npmInfo && (url === npmInfo.llmsFullUrl || url === npmInfo.llmsUrl))
271
+ return "npm-llms-field";
272
+ if (url.endsWith("/llms-full.txt"))
273
+ return "llms-full.txt";
274
+ if (url.endsWith("/llms.txt"))
275
+ return "llms.txt";
276
+ if (url.includes("raw.githubusercontent.com") && url.endsWith("/README.md"))
277
+ return "readme";
278
+ if (url.includes("raw.githubusercontent.com"))
279
+ return "github-raw";
280
+ return "homepage-html";
281
+ }
282
+ // ── Orchestrator ───────────────────────────────────────────────────────
283
+ export async function resolveDocsUrl(library) {
284
+ // 1. Query npm registry (also extracts llms/llmsFull package.json fields)
285
+ const npmInfo = await queryNpmRegistry(library);
286
+ // 2. Generate candidate URLs (npm fields first, then homepage, docs subdomain, etc.)
287
+ const candidates = generateCandidateUrls(npmInfo);
288
+ if (candidates.length === 0) {
289
+ throw new Error(`No candidate documentation URLs found for ${library}`);
290
+ }
291
+ // 3. Probe each candidate
292
+ for (const candidateUrl of candidates) {
293
+ const result = await fetchDocContent(candidateUrl, { timeoutMs: 15_000 });
294
+ if (!result.ok)
295
+ continue;
296
+ const ct = result.contentType.toLowerCase();
297
+ const isBinary = ct.includes("image") ||
298
+ ct.includes("font") ||
299
+ ct.includes("octet-stream") ||
300
+ ct.includes("pdf") ||
301
+ ct.includes("zip");
302
+ if (isBinary)
303
+ continue;
304
+ // Determine source label
305
+ const source = classifySource(candidateUrl, npmInfo);
306
+ const isHtml = ct.includes("html");
307
+ // 4. HTML → convert to markdown
308
+ if (isHtml) {
309
+ const md = htmlToMarkdown(result.content);
310
+ if (md.trim().length > 100) {
311
+ const discoveryResult = {
312
+ url: candidateUrl,
313
+ content: md,
314
+ byteLength: Buffer.byteLength(md),
315
+ source: "homepage-html",
316
+ };
317
+ if (md.length < 5_000) {
318
+ discoveryResult.warning = "Content is very small, index may be thin";
319
+ }
320
+ return discoveryResult;
321
+ }
322
+ continue;
323
+ }
324
+ // 5. Text docs — check if it's an index that needs expansion
325
+ const detection = detectIndex(result.content, candidateUrl);
326
+ if (detection.isIndex) {
327
+ const expanded = await expandIndex(detection.links, candidateUrl);
328
+ if (expanded.content.length > 0) {
329
+ const discoveryResult = {
330
+ url: candidateUrl,
331
+ content: expanded.content,
332
+ byteLength: Buffer.byteLength(expanded.content),
333
+ source: "llms.txt-index",
334
+ expandedUrls: expanded.expandedUrls,
335
+ failedUrls: expanded.failedUrls.length > 0 ? expanded.failedUrls : undefined,
336
+ };
337
+ if (expanded.content.length < 5_000) {
338
+ discoveryResult.warning = "Content is very small, index may be thin";
339
+ }
340
+ return discoveryResult;
341
+ }
342
+ }
343
+ // Full content doc (npm-llms-field, llms-full.txt, non-index llms.txt, github-raw, or readme)
344
+ const discoveryResult = {
345
+ url: candidateUrl,
346
+ content: result.content,
347
+ byteLength: result.byteLength,
348
+ source,
349
+ };
350
+ if (result.content.length < 5_000) {
351
+ discoveryResult.warning = "Content is very small, index may be thin";
352
+ }
353
+ return discoveryResult;
354
+ }
355
+ throw new Error(`No documentation found for ${library}. Tried ${candidates.length} candidate URLs.`);
356
+ }
357
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
package/dist/fetcher.d.ts CHANGED
@@ -7,8 +7,13 @@ export type FetchResult = {
7
7
  content: string;
8
8
  byteLength: number;
9
9
  contentType: string;
10
+ finalUrl: string;
10
11
  } | {
11
12
  ok: false;
12
13
  error: string;
13
14
  };
14
- export declare function fetchDocContent(url: string): Promise<FetchResult>;
15
+ export interface FetchOptions {
16
+ /** Timeout in milliseconds (default 120s). Use 15s for URL probing. */
17
+ timeoutMs?: number;
18
+ }
19
+ export declare function fetchDocContent(url: string, options?: FetchOptions): Promise<FetchResult>;
package/dist/fetcher.js CHANGED
@@ -2,17 +2,19 @@
2
2
  * Raw HTTP fetch for documentation content.
3
3
  * No AI processing, no truncation — returns full text as-is.
4
4
  */
5
- const TIMEOUT_MS = 30_000;
6
- const MAX_SIZE = 5 * 1024 * 1024; // 5MB
7
- export async function fetchDocContent(url) {
5
+ const DEFAULT_TIMEOUT_MS = 120_000;
6
+ const MAX_SIZE = 200 * 1024 * 1024; // 200MB
7
+ export async function fetchDocContent(url, options) {
8
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
8
9
  try {
9
10
  const controller = new AbortController();
10
- const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
11
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
11
12
  const res = await fetch(url, {
12
13
  signal: controller.signal,
13
14
  headers: {
14
15
  "User-Agent": "claude-local-docs/1.0",
15
16
  "Accept": "text/plain, text/markdown, text/html, */*",
17
+ "Accept-Language": "en-US,en;q=0.9",
16
18
  },
17
19
  redirect: "follow",
18
20
  });
@@ -37,11 +39,12 @@ export async function fetchDocContent(url) {
37
39
  content,
38
40
  byteLength: buffer.byteLength,
39
41
  contentType,
42
+ finalUrl: res.url,
40
43
  };
41
44
  }
42
45
  catch (err) {
43
46
  if (err.name === "AbortError") {
44
- return { ok: false, error: `Request timed out after ${TIMEOUT_MS / 1000}s` };
47
+ return { ok: false, error: `Request timed out after ${timeoutMs / 1000}s` };
45
48
  }
46
49
  return { ok: false, error: err.message ?? String(err) };
47
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../src/fetcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,UAAU,GAAG,MAAM,CAAC;AAC1B,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,MAAM;AAMxC,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW;IAC/C,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,UAAU,CAAC,CAAC;QAE/D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,OAAO,EAAE;gBACP,YAAY,EAAE,uBAAuB;gBACrC,QAAQ,EAAE,2CAA2C;aACtD;YACD,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAC;QAEH,YAAY,CAAC,KAAK,CAAC,CAAC;QAEpB,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,EAAE,CAAC;QACtE,CAAC;QAED,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,YAAY,CAAC;QAEpE,2CAA2C;QAC3C,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACxD,IAAI,aAAa,IAAI,QAAQ,CAAC,aAAa,EAAE,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC;YAC5D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,sBAAsB,aAAa,kBAAkB,QAAQ,GAAG,EAAE,CAAC;QAChG,CAAC;QAED,4BAA4B;QAC5B,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;QACvC,IAAI,MAAM,CAAC,UAAU,GAAG,QAAQ,EAAE,CAAC;YACjC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,sBAAsB,MAAM,CAAC,UAAU,kBAAkB,QAAQ,GAAG,EAAE,CAAC;QACpG,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAEjD,OAAO;YACL,EAAE,EAAE,IAAI;YACR,OAAO;YACP,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,WAAW;SACZ,CAAC;IACJ,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAC9B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,2BAA2B,UAAU,GAAG,IAAI,GAAG,EAAE,CAAC;QAC/E,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAC1D,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../src/fetcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,kBAAkB,GAAG,OAAO,CAAC;AACnC,MAAM,QAAQ,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,QAAQ;AAW5C,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAW,EACX,OAAsB;IAEtB,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,kBAAkB,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;QAE9D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,OAAO,EAAE;gBACP,YAAY,EAAE,uBAAuB;gBACrC,QAAQ,EAAE,2CAA2C;gBACrD,iBAAiB,EAAE,gBAAgB;aACpC;YACD,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAC;QAEH,YAAY,CAAC,KAAK,CAAC,CAAC;QAEpB,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,EAAE,CAAC;QACtE,CAAC;QAED,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,YAAY,CAAC;QAEpE,2CAA2C;QAC3C,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACxD,IAAI,aAAa,IAAI,QAAQ,CAAC,aAAa,EAAE,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC;YAC5D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,sBAAsB,aAAa,kBAAkB,QAAQ,GAAG,EAAE,CAAC;QAChG,CAAC;QAED,4BAA4B;QAC5B,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;QACvC,IAAI,MAAM,CAAC,UAAU,GAAG,QAAQ,EAAE,CAAC;YACjC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,sBAAsB,MAAM,CAAC,UAAU,kBAAkB,QAAQ,GAAG,EAAE,CAAC;QACpG,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAEjD,OAAO;YACL,EAAE,EAAE,IAAI;YACR,OAAO;YACP,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,WAAW;YACX,QAAQ,EAAE,GAAG,CAAC,GAAG;SAClB,CAAC;IACJ,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAC9B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,2BAA2B,SAAS,GAAG,IAAI,GAAG,EAAE,CAAC;QAC9E,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAC1D,CAAC;AACH,CAAC"}
package/dist/index.js CHANGED
@@ -8,7 +8,42 @@ import { indexDocument } from "./indexer.js";
8
8
  import { searchDocs } from "./search.js";
9
9
  import { analyzeDependencies } from "./workspace.js";
10
10
  import { fetchDocContent } from "./fetcher.js";
11
+ import { resolveDocsUrl } from "./discovery.js";
11
12
  const projectRoot = resolveProjectRoot();
13
+ /**
14
+ * Expand search results with adjacent chunks (id-1 and id+1) from the same library/section.
15
+ * This recovers context when code examples are split across chunk boundaries.
16
+ */
17
+ async function expandWithNeighbors(results, store) {
18
+ const resultIds = new Set(results.map((r) => r.chunkId));
19
+ const expanded = [];
20
+ for (const result of results) {
21
+ const headingJson = JSON.stringify(result.headingPath);
22
+ const parts = [];
23
+ // Try previous chunk
24
+ const prevId = result.chunkId - 1;
25
+ if (!resultIds.has(prevId)) {
26
+ const prev = await store.getChunkById(prevId);
27
+ if (prev && prev.library === result.library && prev.headingPath === headingJson) {
28
+ parts.push(prev.text);
29
+ }
30
+ }
31
+ parts.push(result.content);
32
+ // Try next chunk
33
+ const nextId = result.chunkId + 1;
34
+ if (!resultIds.has(nextId)) {
35
+ const next = await store.getChunkById(nextId);
36
+ if (next && next.library === result.library && next.headingPath === headingJson) {
37
+ parts.push(next.text);
38
+ }
39
+ }
40
+ expanded.push({
41
+ ...result,
42
+ content: parts.length > 1 ? parts.join("\n\n") : result.content,
43
+ });
44
+ }
45
+ return expanded;
46
+ }
12
47
  const store = new DocStore(projectRoot);
13
48
  const server = new McpServer({
14
49
  name: "local-docs",
@@ -104,14 +139,14 @@ server.registerTool("search_docs", {
104
139
  };
105
140
  }
106
141
  const results = await searchDocs(query, store, { library, topK });
107
- const formatted = results.map((r) => ({
142
+ // Expand results with adjacent chunks for fuller context
143
+ const expanded = await expandWithNeighbors(results, store);
144
+ const formatted = expanded.map((r) => ({
108
145
  score: r.score,
109
146
  library: r.library,
110
147
  heading: r.headingPath.join(" > "),
111
148
  chunkId: r.chunkId,
112
- content: r.content.length > 500
113
- ? r.content.slice(0, 500) + "..."
114
- : r.content,
149
+ content: r.content,
115
150
  }));
116
151
  return {
117
152
  content: [{ type: "text", text: JSON.stringify(formatted, null, 2) }],
@@ -237,7 +272,7 @@ server.registerTool("get_doc_section", {
237
272
  });
238
273
  // --- Tool 6: fetch_and_store_doc ---
239
274
  server.registerTool("fetch_and_store_doc", {
240
- description: "Fetch documentation from a URL (raw HTTP, no AI processing or truncation) and index it. Use this for llms.txt and llms-full.txt URLs to preserve full content. Handles up to 5MB, 30s timeout.",
275
+ description: "Fetch documentation from a URL (raw HTTP, no AI processing or truncation) and index it. Use this for llms.txt and llms-full.txt URLs to preserve full content. Handles up to 200MB, 120s timeout.",
241
276
  inputSchema: {
242
277
  library: z.string().describe("Library name (e.g. 'react', '@tanstack/query')"),
243
278
  version: z.string().describe("Library version"),
@@ -292,6 +327,51 @@ server.registerTool("fetch_and_store_doc", {
292
327
  };
293
328
  }
294
329
  });
330
+ // --- Tool 7: discover_and_fetch_docs ---
331
+ server.registerTool("discover_and_fetch_docs", {
332
+ description: "Discover, fetch, and index documentation for a library automatically. Checks package.json llms/llmsFull fields first, then probes homepage, docs.{domain}, llms.{domain}, /docs/ subpath, and GitHub raw for llms-full.txt/llms.txt. Detects index files and expands them. Falls back to homepage HTML → markdown conversion. Self-contained — no WebSearch needed.",
333
+ inputSchema: {
334
+ library: z.string().describe("npm package name (e.g. 'react', '@tanstack/query')"),
335
+ version: z.string().optional().describe("Library version (optional, auto-detected from npm)"),
336
+ },
337
+ }, async ({ library, version }) => {
338
+ try {
339
+ const discovery = await resolveDocsUrl(library);
340
+ await store.saveRawDoc(library, discovery.content);
341
+ const chunks = await indexDocument(discovery.content, library);
342
+ const result = await store.addLibrary(library, version ?? "latest", discovery.url, chunks);
343
+ return {
344
+ content: [
345
+ {
346
+ type: "text",
347
+ text: JSON.stringify({
348
+ success: true,
349
+ library,
350
+ source: discovery.source,
351
+ url: discovery.url,
352
+ chunkCount: result.chunkCount,
353
+ byteLength: discovery.byteLength,
354
+ totalIndexSize: result.indexSize,
355
+ expandedUrls: discovery.expandedUrls,
356
+ failedUrls: discovery.failedUrls,
357
+ warning: discovery.warning,
358
+ }),
359
+ },
360
+ ],
361
+ };
362
+ }
363
+ catch (err) {
364
+ return {
365
+ content: [
366
+ {
367
+ type: "text",
368
+ text: JSON.stringify({ success: false, library, error: err.message }),
369
+ },
370
+ ],
371
+ isError: true,
372
+ };
373
+ }
374
+ });
295
375
  // --- Start server ---
296
376
  async function main() {
297
377
  const transport = new StdioServerTransport();