@pkgseer/cli 0.1.4 → 0.2.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/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  version
4
- } from "./shared/chunk-z505vakm.js";
4
+ } from "./shared/chunk-1m9g9ehr.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { Command } from "commander";
@@ -205,11 +205,65 @@ var CliDocsListDocument = gql`
205
205
  pages {
206
206
  id
207
207
  title
208
- words
209
208
  }
210
209
  }
211
210
  }
212
211
  `;
212
+ var CombinedSearchDocument = gql`
213
+ query CombinedSearch($packages: [SearchPackageInput!]!, $query: String!, $mode: SearchMode, $limit: Int) {
214
+ combinedSearch(packages: $packages, query: $query, mode: $mode, limit: $limit) {
215
+ query
216
+ mode
217
+ totalResults
218
+ entries {
219
+ id
220
+ type
221
+ title
222
+ subtitle
223
+ packageName
224
+ registry
225
+ score
226
+ snippet
227
+ filePath
228
+ startLine
229
+ endLine
230
+ language
231
+ chunkType
232
+ repoUrl
233
+ gitRef
234
+ pageId
235
+ sourceUrl
236
+ }
237
+ indexingStatus {
238
+ registry
239
+ packageName
240
+ version
241
+ codeStatus
242
+ docsStatus
243
+ }
244
+ }
245
+ }
246
+ `;
247
+ var FetchCodeContextDocument = gql`
248
+ query FetchCodeContext($repoUrl: String!, $gitRef: String!, $filePath: String!, $startLine: Int, $endLine: Int) {
249
+ fetchCodeContext(
250
+ repoUrl: $repoUrl
251
+ gitRef: $gitRef
252
+ filePath: $filePath
253
+ startLine: $startLine
254
+ endLine: $endLine
255
+ ) {
256
+ content
257
+ filePath
258
+ language
259
+ totalLines
260
+ startLine
261
+ endLine
262
+ repoUrl
263
+ gitRef
264
+ }
265
+ }
266
+ `;
213
267
  var CliDocsGetDocument = gql`
214
268
  query CliDocsGet($registry: Registry!, $packageName: String!, $pageId: String!, $version: String) {
215
269
  fetchPackageDoc(
@@ -428,7 +482,6 @@ var ListPackageDocsDocument = gql`
428
482
  slug
429
483
  order
430
484
  linkName
431
- words
432
485
  lastUpdatedAt
433
486
  sourceUrl
434
487
  }
@@ -509,7 +562,6 @@ var SearchPackageDocsDocument = gql`
509
562
  slug
510
563
  order
511
564
  linkName
512
- words
513
565
  lastUpdatedAt
514
566
  sourceUrl
515
567
  matchCount
@@ -540,7 +592,6 @@ var SearchProjectDocsDocument = gql`
540
592
  registry
541
593
  packageName
542
594
  version
543
- words
544
595
  matchCount
545
596
  titleHit
546
597
  score
@@ -560,6 +611,8 @@ var CliComparePackagesDocumentString = print(CliComparePackagesDocument);
560
611
  var CliDocsSearchDocumentString = print(CliDocsSearchDocument);
561
612
  var CliProjectDocsSearchDocumentString = print(CliProjectDocsSearchDocument);
562
613
  var CliDocsListDocumentString = print(CliDocsListDocument);
614
+ var CombinedSearchDocumentString = print(CombinedSearchDocument);
615
+ var FetchCodeContextDocumentString = print(FetchCodeContextDocument);
563
616
  var CliDocsGetDocumentString = print(CliDocsGetDocument);
564
617
  var CreateProjectDocumentString = print(CreateProjectDocument);
565
618
  var PackageSummaryDocumentString = print(PackageSummaryDocument);
@@ -598,6 +651,12 @@ function getSdk(client, withWrapper = defaultWrapper) {
598
651
  CliDocsList(variables, requestHeaders) {
599
652
  return withWrapper((wrappedRequestHeaders) => client.rawRequest(CliDocsListDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "CliDocsList", "query", variables);
600
653
  },
654
+ CombinedSearch(variables, requestHeaders) {
655
+ return withWrapper((wrappedRequestHeaders) => client.rawRequest(CombinedSearchDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "CombinedSearch", "query", variables);
656
+ },
657
+ FetchCodeContext(variables, requestHeaders) {
658
+ return withWrapper((wrappedRequestHeaders) => client.rawRequest(FetchCodeContextDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "FetchCodeContext", "query", variables);
659
+ },
601
660
  CliDocsGet(variables, requestHeaders) {
602
661
  return withWrapper((wrappedRequestHeaders) => client.rawRequest(CliDocsGetDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "CliDocsGet", "query", variables);
603
662
  },
@@ -960,7 +1019,8 @@ var TOOL_NAMES = [
960
1019
  "compare_packages",
961
1020
  "list_package_docs",
962
1021
  "fetch_package_doc",
963
- "search_package_docs",
1022
+ "search",
1023
+ "fetch_code_context",
964
1024
  "search_project_docs"
965
1025
  ];
966
1026
  var ToolNameSchema = z.enum(TOOL_NAMES);
@@ -1002,7 +1062,10 @@ class ConfigServiceImpl {
1002
1062
  return this.loadAndParseConfig(configPath, GlobalConfigSchema);
1003
1063
  }
1004
1064
  async loadProjectConfig() {
1005
- let currentDir = this.fs.getCwd();
1065
+ return this.loadProjectConfigFrom(this.fs.getCwd());
1066
+ }
1067
+ async loadProjectConfigFrom(startDir) {
1068
+ let currentDir = startDir;
1006
1069
  while (true) {
1007
1070
  const configPath = this.fs.joinPath(currentDir, PROJECT_CONFIG_FILE);
1008
1071
  const config = await this.loadAndParseConfig(configPath, ProjectConfigSchema);
@@ -1350,6 +1413,25 @@ class PkgseerServiceImpl {
1350
1413
  });
1351
1414
  return { data: result.data, errors: result.errors };
1352
1415
  }
1416
+ async combinedSearch(packages, query, options) {
1417
+ const result = await this.client.CombinedSearch({
1418
+ packages,
1419
+ query,
1420
+ mode: options?.mode,
1421
+ limit: options?.limit
1422
+ });
1423
+ return { data: result.data, errors: result.errors };
1424
+ }
1425
+ async fetchCodeContext(repoUrl, gitRef, filePath, options) {
1426
+ const result = await this.client.FetchCodeContext({
1427
+ repoUrl,
1428
+ gitRef,
1429
+ filePath,
1430
+ startLine: options?.startLine,
1431
+ endLine: options?.endLine
1432
+ });
1433
+ return { data: result.data, errors: result.errors };
1434
+ }
1353
1435
  }
1354
1436
  // src/services/project-service.ts
1355
1437
  var PROJECT_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
@@ -2182,8 +2264,7 @@ function formatDocsList(docs) {
2182
2264
  for (const page of docs.pages) {
2183
2265
  if (!page || !page.id)
2184
2266
  continue;
2185
- const words = page.words ? ` (${formatNumber(page.words)} words)` : "";
2186
- lines.push(` ${page.title}${words}`);
2267
+ lines.push(` ${page.title}`);
2187
2268
  lines.push(` ID: ${page.id}`);
2188
2269
  lines.push("");
2189
2270
  }
@@ -2234,51 +2315,6 @@ function registerDocsListCommand(program) {
2234
2315
  });
2235
2316
  }
2236
2317
  // src/commands/docs/search.ts
2237
- function summarizeSearchResults(results) {
2238
- const entries = results.entries ?? [];
2239
- const count = entries.length;
2240
- const totalMatches = entries.reduce((acc, e) => acc + (e?.matchCount ?? 0), 0);
2241
- const lines = [];
2242
- lines.push(`Results: ${count} page${count === 1 ? "" : "s"} | matches: ${totalMatches}`);
2243
- lines.push("");
2244
- for (const entry of entries.filter((e) => !!e)) {
2245
- lines.push(`- ${entry.title ?? entry.slug ?? "untitled"} (${entry.matchCount ?? 0} matches)`);
2246
- }
2247
- if (count === 0) {
2248
- lines.push("No matches. Try broader terms or fewer constraints.");
2249
- }
2250
- return lines.join(`
2251
- `);
2252
- }
2253
- function buildDocRef(entry, searchResult) {
2254
- if (!entry?.slug)
2255
- return "";
2256
- let registry;
2257
- let packageName;
2258
- let version2;
2259
- if ("packageName" in entry && "registry" in entry && "version" in entry) {
2260
- registry = entry.registry;
2261
- packageName = entry.packageName;
2262
- version2 = entry.version;
2263
- } else {
2264
- const rawRegistry = "registry" in searchResult ? searchResult.registry : null;
2265
- registry = rawRegistry ? rawRegistry.toLowerCase() : null;
2266
- packageName = "packageName" in searchResult ? searchResult.packageName : null;
2267
- version2 = "version" in searchResult ? searchResult.version : null;
2268
- }
2269
- const parts = [];
2270
- if (registry)
2271
- parts.push(registry.toLowerCase());
2272
- if (packageName)
2273
- parts.push(packageName);
2274
- if (version2)
2275
- parts.push(version2);
2276
- else if (registry && packageName)
2277
- parts.push("latest");
2278
- if (entry.slug)
2279
- parts.push(entry.slug);
2280
- return parts.join("/");
2281
- }
2282
2318
  var colors = {
2283
2319
  reset: "\x1B[0m",
2284
2320
  bold: "\x1B[1m",
@@ -2286,7 +2322,8 @@ var colors = {
2286
2322
  magenta: "\x1B[35m",
2287
2323
  cyan: "\x1B[36m",
2288
2324
  yellow: "\x1B[33m",
2289
- green: "\x1B[32m"
2325
+ green: "\x1B[32m",
2326
+ blue: "\x1B[34m"
2290
2327
  };
2291
2328
  function shouldUseColors(noColor) {
2292
2329
  if (noColor)
@@ -2295,219 +2332,420 @@ function shouldUseColors(noColor) {
2295
2332
  return false;
2296
2333
  return process.stdout.isTTY ?? false;
2297
2334
  }
2298
- function applyHighlights(line, highlights, useColors) {
2299
- if (!highlights || highlights.length === 0 || !useColors) {
2300
- return line;
2301
- }
2302
- const sorted = [...highlights].filter((h) => h && h.start != null && h.end != null).sort((a, b) => {
2303
- const aStart = a?.start ?? 0;
2304
- const bStart = b?.start ?? 0;
2305
- return bStart - aStart;
2335
+ function toSearchMode(mode) {
2336
+ if (!mode)
2337
+ return "ALL";
2338
+ const map = {
2339
+ all: "ALL",
2340
+ code: "CODE",
2341
+ docs: "DOCS"
2342
+ };
2343
+ return map[mode.toLowerCase()] ?? "ALL";
2344
+ }
2345
+ function parsePackageList(input2) {
2346
+ return input2.split(",").map((spec) => {
2347
+ const trimmed = spec.trim();
2348
+ const atIndex = trimmed.lastIndexOf("@");
2349
+ let regPkg;
2350
+ let version2;
2351
+ if (atIndex > 0) {
2352
+ regPkg = trimmed.slice(0, atIndex);
2353
+ version2 = trimmed.slice(atIndex + 1);
2354
+ } else {
2355
+ regPkg = trimmed;
2356
+ }
2357
+ const slashIndex = regPkg.indexOf("/");
2358
+ if (slashIndex === -1) {
2359
+ return {
2360
+ registry: "NPM",
2361
+ name: regPkg,
2362
+ version: version2
2363
+ };
2364
+ }
2365
+ const beforeSlash = regPkg.slice(0, slashIndex);
2366
+ const afterSlash = regPkg.slice(slashIndex + 1);
2367
+ if (beforeSlash.startsWith("@")) {
2368
+ return {
2369
+ registry: "NPM",
2370
+ name: regPkg,
2371
+ version: version2
2372
+ };
2373
+ }
2374
+ return {
2375
+ registry: toGraphQLRegistry(beforeSlash),
2376
+ name: afterSlash,
2377
+ version: version2
2378
+ };
2306
2379
  });
2307
- let result = line;
2308
- for (const h of sorted) {
2309
- if (!h || h.start == null || h.end == null)
2310
- continue;
2311
- const before = result.slice(0, h.start);
2312
- const match = result.slice(h.start, h.end);
2313
- const after = result.slice(h.end);
2314
- result = `${before}${colors.bold}${colors.magenta}${match}${colors.reset}${after}`;
2380
+ }
2381
+ function formatTypeBadge(type, useColors) {
2382
+ const typeStr = type ?? "DOC";
2383
+ if (useColors) {
2384
+ const color = typeStr === "CODE" ? colors.magenta : colors.cyan;
2385
+ return `${color}[${typeStr}]${colors.reset}`;
2315
2386
  }
2316
- return result;
2387
+ return `[${typeStr}]`;
2317
2388
  }
2318
- function formatEntry(entry, searchResult, useColors) {
2389
+ function formatEntry(entry, useColors) {
2319
2390
  if (!entry)
2320
2391
  return "";
2321
2392
  const lines = [];
2322
- const ref = buildDocRef(entry, searchResult);
2323
- const matchInfo = entry.matchCount && entry.matchCount > 0 ? ` (${entry.matchCount} match${entry.matchCount > 1 ? "es" : ""})` : "";
2324
- if (useColors) {
2325
- lines.push(`${colors.cyan}${ref}${colors.reset}: ${colors.bold}${entry.title}${colors.reset}${colors.dim}${matchInfo}${colors.reset}`);
2326
- } else {
2327
- lines.push(`${ref}: ${entry.title}${matchInfo}`);
2328
- }
2329
- const matches = entry.matches ?? [];
2330
- for (const match of matches) {
2331
- if (!match?.context)
2332
- continue;
2333
- const ctx = match.context;
2334
- for (const beforeLine of ctx.before ?? []) {
2335
- if (beforeLine) {
2336
- const prefix = useColors ? colors.dim : "";
2337
- const suffix = useColors ? colors.reset : "";
2338
- lines.push(` ${prefix}${beforeLine}${suffix}`);
2393
+ const badge = formatTypeBadge(entry.type, useColors);
2394
+ if (entry.type === "CODE") {
2395
+ const title = entry.title ?? "Unknown";
2396
+ const location = entry.filePath ? `${entry.filePath}:${entry.startLine ?? "?"}-${entry.endLine ?? "?"}` : "";
2397
+ const lang = entry.language ? ` • ${entry.language}` : "";
2398
+ if (useColors) {
2399
+ lines.push(`${badge} ${colors.bold}${title}${colors.reset}`);
2400
+ if (location) {
2401
+ lines.push(` ${colors.dim}${location}${lang}${colors.reset}`);
2339
2402
  }
2340
- }
2341
- for (const matchedLine of ctx.matchedLines ?? []) {
2342
- if (matchedLine?.content) {
2343
- const highlighted = applyHighlights(matchedLine.content, matchedLine.highlights, useColors);
2344
- lines.push(` ${highlighted}`);
2403
+ } else {
2404
+ lines.push(`${badge} ${title}`);
2405
+ if (location) {
2406
+ lines.push(` ${location}${lang}`);
2345
2407
  }
2346
2408
  }
2347
- for (const afterLine of ctx.after ?? []) {
2348
- if (afterLine) {
2349
- const prefix = useColors ? colors.dim : "";
2350
- const suffix = useColors ? colors.reset : "";
2351
- lines.push(` ${prefix}${afterLine}${suffix}`);
2409
+ } else {
2410
+ const title = entry.title ?? "Untitled";
2411
+ const ref = entry.pageId ?? entry.sourceUrl ?? "";
2412
+ if (useColors) {
2413
+ lines.push(`${badge} ${colors.bold}${title}${colors.reset}`);
2414
+ if (ref) {
2415
+ lines.push(` ${colors.dim}${ref}${colors.reset}`);
2416
+ }
2417
+ } else {
2418
+ lines.push(`${badge} ${title}`);
2419
+ if (ref) {
2420
+ lines.push(` ${ref}`);
2352
2421
  }
2353
2422
  }
2423
+ }
2424
+ if (entry.snippet) {
2354
2425
  lines.push("");
2426
+ const snippetLines = entry.snippet.split(`
2427
+ `).slice(0, 5);
2428
+ for (const line of snippetLines) {
2429
+ if (useColors) {
2430
+ lines.push(` ${colors.dim}${line}${colors.reset}`);
2431
+ } else {
2432
+ lines.push(` ${line}`);
2433
+ }
2434
+ }
2355
2435
  }
2436
+ lines.push("");
2356
2437
  return lines.join(`
2357
2438
  `);
2358
2439
  }
2440
+ function formatPackageHeader(entry, prevEntry, useColors) {
2441
+ const pkg = entry?.packageName ?? "unknown";
2442
+ const registry = entry?.registry?.toLowerCase() ?? "npm";
2443
+ const prevPkg = prevEntry?.packageName;
2444
+ const prevRegistry = prevEntry?.registry?.toLowerCase();
2445
+ if (pkg === prevPkg && registry === prevRegistry) {
2446
+ return "";
2447
+ }
2448
+ const header = `${pkg} (${registry})`;
2449
+ const separator = "═".repeat(Math.min(50, header.length + 10));
2450
+ if (useColors) {
2451
+ return `
2452
+ ${colors.yellow}${header}${colors.reset}
2453
+ ${colors.dim}${separator}${colors.reset}
2454
+ `;
2455
+ }
2456
+ return `
2457
+ ${header}
2458
+ ${separator}
2459
+ `;
2460
+ }
2359
2461
  function formatSearchResults(results, useColors) {
2360
2462
  const entries = results.entries ?? [];
2361
2463
  if (entries.length === 0) {
2362
- return "No matching documentation found. Try broader terms or fewer constraints.";
2464
+ return "No results found. Try broader terms or different packages.";
2465
+ }
2466
+ const lines = [];
2467
+ let prevEntry = null;
2468
+ for (const entry of entries) {
2469
+ if (!entry)
2470
+ continue;
2471
+ const header = formatPackageHeader(entry, prevEntry, useColors);
2472
+ if (header) {
2473
+ lines.push(header);
2474
+ }
2475
+ lines.push(formatEntry(entry, useColors));
2476
+ prevEntry = entry;
2477
+ }
2478
+ const codeCount = entries.filter((e) => e?.type === "CODE").length;
2479
+ const docCount = entries.filter((e) => e?.type === "DOC" || e?.type === "REPO_DOC").length;
2480
+ if (useColors) {
2481
+ lines.push(`${colors.dim}───────────────────────────────────────────────────${colors.reset}`);
2482
+ lines.push(`${colors.dim}Results: ${entries.length} total (${codeCount} code, ${docCount} docs)${colors.reset}`);
2483
+ } else {
2484
+ lines.push("───────────────────────────────────────────────────");
2485
+ lines.push(`Results: ${entries.length} total (${codeCount} code, ${docCount} docs)`);
2486
+ }
2487
+ const indexingWarnings = formatIndexingWarnings(results.indexingStatus);
2488
+ if (indexingWarnings) {
2489
+ lines.push("");
2490
+ lines.push(indexingWarnings);
2363
2491
  }
2364
- const formatted = entries.filter((e) => e != null).map((entry) => formatEntry(entry, results, useColors));
2365
- return formatted.join(`
2492
+ return lines.join(`
2366
2493
  `);
2367
2494
  }
2368
2495
  function formatRefsOnly(results) {
2369
2496
  const entries = results.entries ?? [];
2370
- return entries.filter((e) => e != null).map((entry) => buildDocRef(entry, results)).join(`
2497
+ return entries.filter((e) => e != null).map((entry) => {
2498
+ if (!entry)
2499
+ return "";
2500
+ if (entry.type === "CODE") {
2501
+ return `${entry.repoUrl ?? ""}:${entry.filePath ?? ""}:${entry.startLine ?? ""}`;
2502
+ }
2503
+ return entry.pageId ?? entry.id ?? "";
2504
+ }).filter((s) => s !== "").join(`
2371
2505
  `);
2372
2506
  }
2373
2507
  function formatCount(results) {
2374
2508
  const entries = results.entries ?? [];
2375
- return entries.filter((e) => e != null).map((entry) => {
2509
+ const counts = new Map;
2510
+ for (const entry of entries) {
2376
2511
  if (!entry)
2377
- return "";
2378
- const ref = buildDocRef(entry, results);
2379
- return `${ref}: ${entry.matchCount ?? 0}`;
2380
- }).filter((s) => s !== "").join(`
2512
+ continue;
2513
+ const key = `${entry.packageName ?? "unknown"}@${entry.registry?.toLowerCase() ?? "npm"}`;
2514
+ const current = counts.get(key) ?? { code: 0, docs: 0 };
2515
+ if (entry.type === "CODE") {
2516
+ current.code++;
2517
+ } else {
2518
+ current.docs++;
2519
+ }
2520
+ counts.set(key, current);
2521
+ }
2522
+ return Array.from(counts.entries()).map(([pkg, { code, docs }]) => `${pkg}: ${code} code, ${docs} docs`).join(`
2381
2523
  `);
2382
2524
  }
2383
- function slimSearchResults(results) {
2525
+ function formatIndexingWarnings(indexingStatus) {
2526
+ if (!indexingStatus || indexingStatus.length === 0)
2527
+ return null;
2528
+ const warnings = [];
2529
+ for (const status of indexingStatus) {
2530
+ if (!status)
2531
+ continue;
2532
+ const pkg = `${status.packageName}@${status.version ?? "latest"}`;
2533
+ if (status.codeStatus && status.codeStatus !== "INDEXED") {
2534
+ warnings.push(` - ${pkg}: code ${status.codeStatus.toLowerCase()}`);
2535
+ }
2536
+ if (status.docsStatus && status.docsStatus !== "INDEXED") {
2537
+ warnings.push(` - ${pkg}: docs ${status.docsStatus.toLowerCase()}`);
2538
+ }
2539
+ }
2540
+ if (warnings.length === 0)
2541
+ return null;
2542
+ return `Note: Some packages are not fully indexed:
2543
+ ` + warnings.join(`
2544
+ `) + `
2545
+ Results may be incomplete.`;
2546
+ }
2547
+ var LANG_ABBREV = {
2548
+ javascript: "js",
2549
+ typescript: "ts",
2550
+ python: "py",
2551
+ markdown: "md"
2552
+ };
2553
+ function abbrevLang(lang) {
2554
+ if (!lang)
2555
+ return "";
2556
+ const lower = lang.toLowerCase();
2557
+ return LANG_ABBREV[lower] ?? lower;
2558
+ }
2559
+ function truncate(text, maxLen) {
2560
+ if (text.length <= maxLen)
2561
+ return text;
2562
+ return text.slice(0, maxLen - 3) + "...";
2563
+ }
2564
+ function formatEntryCompact(entry, useColors) {
2565
+ if (!entry)
2566
+ return "";
2567
+ const badge = formatTypeBadge(entry.type, useColors);
2568
+ let header;
2569
+ if (entry.type === "CODE") {
2570
+ const loc = entry.filePath ? `${entry.filePath}:${entry.startLine ?? "?"}-${entry.endLine ?? "?"}` : "";
2571
+ const lang = abbrevLang(entry.language);
2572
+ const title = entry.title && entry.title !== "Unnamed" ? ` ${entry.title}` : "";
2573
+ header = `${badge} ${loc}${lang ? ` ${lang}` : ""}${title}`;
2574
+ } else {
2575
+ const title = entry.title ?? "Untitled";
2576
+ const ref = entry.pageId ? ` (${entry.pageId})` : "";
2577
+ header = `${badge} ${title}${ref}`;
2578
+ }
2579
+ let snippet = "";
2580
+ if (entry.snippet) {
2581
+ const firstLine = entry.snippet.split(`
2582
+ `).map((l) => l.trim()).find((l) => l.length > 0);
2583
+ if (firstLine) {
2584
+ snippet = ` ${truncate(firstLine, 80)}`;
2585
+ if (useColors) {
2586
+ snippet = ` ${colors.dim}${truncate(firstLine, 80)}${colors.reset}`;
2587
+ }
2588
+ }
2589
+ }
2590
+ return snippet ? `${header}
2591
+ ${snippet}` : header;
2592
+ }
2593
+ function formatPackageHeaderCompact(entry, prevEntry, useColors) {
2594
+ const pkg = entry?.packageName ?? "unknown";
2595
+ const registry = entry?.registry?.toLowerCase() ?? "npm";
2596
+ const prevPkg = prevEntry?.packageName;
2597
+ const prevRegistry = prevEntry?.registry?.toLowerCase();
2598
+ if (pkg === prevPkg && registry === prevRegistry) {
2599
+ return "";
2600
+ }
2601
+ const header = `${pkg} (${registry})`;
2602
+ if (useColors) {
2603
+ return `
2604
+ ${colors.yellow}${header}${colors.reset}`;
2605
+ }
2606
+ return `
2607
+ ${header}`;
2608
+ }
2609
+ function formatSearchResultsCompact(results, useColors) {
2384
2610
  const entries = results.entries ?? [];
2385
- return entries.filter((e) => e != null).map((entry) => {
2611
+ if (entries.length === 0) {
2612
+ return "No results. Try broader terms or different packages.";
2613
+ }
2614
+ const lines = [];
2615
+ let prevEntry = null;
2616
+ for (const entry of entries) {
2386
2617
  if (!entry)
2387
- return null;
2388
- return {
2389
- ref: buildDocRef(entry, results),
2390
- title: entry.title ?? "",
2391
- matchCount: entry.matchCount ?? 0,
2392
- matches: entry.matches?.filter((m) => m?.context).map((m) => {
2393
- if (!m?.context)
2394
- return null;
2395
- return {
2396
- before: (m.context.before ?? []).filter((l) => l != null),
2397
- lines: (m.context.matchedLines ?? []).filter((l) => l?.content).map((l) => l?.content ?? ""),
2398
- after: (m.context.after ?? []).filter((l) => l != null)
2399
- };
2400
- }).filter((m) => m != null)
2401
- };
2402
- }).filter((e) => e != null);
2618
+ continue;
2619
+ const header = formatPackageHeaderCompact(entry, prevEntry, useColors);
2620
+ if (header) {
2621
+ lines.push(header);
2622
+ }
2623
+ lines.push(formatEntryCompact(entry, useColors));
2624
+ prevEntry = entry;
2625
+ }
2626
+ const codeCount = entries.filter((e) => e?.type === "CODE").length;
2627
+ const docCount = entries.filter((e) => e?.type === "DOC" || e?.type === "REPO_DOC").length;
2628
+ lines.push(`
2629
+ ${entries.length} results (${codeCount} code, ${docCount} docs)`);
2630
+ const indexingWarnings = formatIndexingWarnings(results.indexingStatus);
2631
+ if (indexingWarnings) {
2632
+ lines.push(indexingWarnings);
2633
+ }
2634
+ return lines.join(`
2635
+ `);
2636
+ }
2637
+ function summarizeSearchResults(results) {
2638
+ const entries = results.entries ?? [];
2639
+ const codeCount = entries.filter((e) => e?.type === "CODE").length;
2640
+ const docCount = entries.filter((e) => e?.type === "DOC" || e?.type === "REPO_DOC").length;
2641
+ const lines = [];
2642
+ lines.push(`Results: ${entries.length} total | ${codeCount} code | ${docCount} docs`);
2643
+ lines.push("");
2644
+ const byPackage = new Map;
2645
+ for (const entry of entries) {
2646
+ if (!entry)
2647
+ continue;
2648
+ const key = `${entry.packageName ?? "unknown"} (${entry.registry?.toLowerCase() ?? "npm"})`;
2649
+ const list = byPackage.get(key) ?? [];
2650
+ list.push(entry);
2651
+ byPackage.set(key, list);
2652
+ }
2653
+ for (const [pkg, pkgEntries] of byPackage) {
2654
+ const pkgCode = pkgEntries.filter((e) => e?.type === "CODE").length;
2655
+ const pkgDocs = pkgEntries.filter((e) => e?.type === "DOC" || e?.type === "REPO_DOC").length;
2656
+ lines.push(`- ${pkg}: ${pkgCode} code, ${pkgDocs} docs`);
2657
+ }
2658
+ if (entries.length === 0) {
2659
+ lines.push("No results. Try broader terms or different packages.");
2660
+ }
2661
+ return lines.join(`
2662
+ `);
2403
2663
  }
2404
2664
  async function docsSearchAction(queryArg, options, deps) {
2405
- const { pkgseerService, config } = deps;
2406
- const providedTerms = Array.isArray(queryArg) ? queryArg : queryArg ? [queryArg] : [];
2407
- const terms = providedTerms.concat(options.terms ?? []);
2408
- if (!terms.length) {
2409
- outputError("Search terms required. Provide terms as argument or with --terms", options.json ?? false);
2665
+ const { pkgseerService } = deps;
2666
+ const query = Array.isArray(queryArg) ? queryArg.join(" ") : queryArg ?? "";
2667
+ if (!query.trim()) {
2668
+ outputError("Search query required. Provide query as argument.", options.json ?? false);
2410
2669
  return;
2411
2670
  }
2412
- const contextBefore = options.context ? Number.parseInt(options.context, 10) : options.before ? Number.parseInt(options.before, 10) : 2;
2413
- const contextAfter = options.context ? Number.parseInt(options.context, 10) : options.after ? Number.parseInt(options.after, 10) : 2;
2414
- const maxMatches = options.maxMatches ? Number.parseInt(options.maxMatches, 10) : 5;
2415
- const limit = options.limit ? Number.parseInt(options.limit, 10) : 25;
2416
- const matchMode = options.match_mode?.toUpperCase() || options.mode?.toUpperCase() || undefined;
2417
- const useColors = shouldUseColors(options.noColor);
2418
- const project = options.project ?? config.project;
2419
- const packageName = options.package;
2420
- if (packageName) {
2421
- const registry = toGraphQLRegistry(options.registry ?? "npm");
2422
- const result = await pkgseerService.cliDocsSearch(registry, packageName, {
2423
- keywords: terms,
2424
- matchMode,
2425
- limit,
2426
- version: options.pkgVersion,
2427
- contextLinesBefore: contextBefore,
2428
- contextLinesAfter: contextAfter,
2429
- maxMatches
2430
- });
2431
- handleErrors(result.errors, options.json ?? false);
2432
- if (!result.data.searchPackageDocs) {
2433
- outputError(`No documentation found for ${packageName} in ${options.registry ?? "npm"}`, options.json ?? false);
2434
- return;
2435
- }
2436
- outputResults(result.data.searchPackageDocs, options, useColors);
2671
+ if (!options.packages) {
2672
+ outputError(`Package(s) required. Use -P or --packages:
2673
+ ` + ` -P express Single package (assumes npm)
2674
+ ` + ` -P express,lodash Multiple packages
2675
+ ` + ` -P pypi/django With registry prefix
2676
+ ` + " -P express@4.18.2 With version", options.json ?? false);
2437
2677
  return;
2438
2678
  }
2439
- if (project) {
2440
- const result = await pkgseerService.cliProjectDocsSearch(project, {
2441
- keywords: terms,
2442
- matchMode,
2443
- limit,
2444
- contextLinesBefore: contextBefore,
2445
- contextLinesAfter: contextAfter,
2446
- maxMatches
2447
- });
2448
- handleErrors(result.errors, options.json ?? false);
2449
- if (!result.data.searchProjectDocs) {
2450
- outputError(`Project not found: ${project}`, options.json ?? false);
2451
- return;
2452
- }
2453
- outputResults(result.data.searchProjectDocs, options, useColors);
2679
+ const packages = parsePackageList(options.packages);
2680
+ const limit = options.limit ? Number.parseInt(options.limit, 10) : 25;
2681
+ const mode = toSearchMode(options.mode);
2682
+ const useColors = shouldUseColors(options.noColor);
2683
+ const result = await pkgseerService.combinedSearch(packages, query, {
2684
+ mode,
2685
+ limit
2686
+ });
2687
+ handleErrors(result.errors, options.json ?? false);
2688
+ if (!result.data.combinedSearch) {
2689
+ outputError("No results returned. Check package names and registries.", options.json ?? false);
2454
2690
  return;
2455
2691
  }
2456
- outputError(`No project configured. Either:
2457
- ` + ` - Add project to pkgseer.yml
2458
- ` + ` - Use --project <name> to specify a project
2459
- ` + " - Use --package <name> to search a specific package", options.json ?? false);
2692
+ outputResults(result.data.combinedSearch, options, useColors);
2460
2693
  }
2461
2694
  function outputResults(results, options, useColors) {
2462
2695
  const format = options.json ? "json" : options.format ?? "human";
2463
2696
  if (format === "json") {
2464
- output(slimSearchResults(results), true);
2697
+ output(results, true);
2465
2698
  } else if (options.refsOnly) {
2466
2699
  console.log(formatRefsOnly(results));
2467
2700
  } else if (options.count) {
2468
2701
  console.log(formatCount(results));
2469
2702
  } else if (format === "summary") {
2470
2703
  console.log(summarizeSearchResults(results));
2471
- } else {
2704
+ } else if (options.verbose) {
2472
2705
  console.log(formatSearchResults(results, useColors));
2706
+ } else {
2707
+ console.log(formatSearchResultsCompact(results, useColors));
2473
2708
  }
2474
2709
  }
2475
- var SEARCH_DESCRIPTION = `Search documentation with grep-like output.
2710
+ var SEARCH_DESCRIPTION = `Search code and documentation across packages.
2476
2711
 
2477
- Searches across package or project documentation with keyword
2478
- or freeform query support. Shows matching lines with context
2479
- and highlights matched terms.
2480
-
2481
- By default, searches project documentation if project is
2482
- configured in pkgseer.yml. Use --package to search a specific
2483
- package instead.
2712
+ Searches both code (functions, classes) and documentation pages
2713
+ using the combined search endpoint. Returns results with snippets
2714
+ showing matches.
2484
2715
 
2485
2716
  Examples:
2486
- # Search with context (like grep)
2487
- pkgseer docs search "error handling" --package express
2488
- pkgseer docs search log --package express -C 3
2717
+ # Search a single package (code + docs)
2718
+ pkgseer docs search "error handling" -P express
2719
+
2720
+ # Search code only
2721
+ pkgseer docs search "Router.use" -P express --mode code
2722
+
2723
+ # Search docs only
2724
+ pkgseer docs search "middleware" -P express --mode docs
2489
2725
 
2490
- # Multiple terms (OR by default)
2491
- pkgseer docs search -t "middleware,routing" --package express
2726
+ # Search multiple packages
2727
+ pkgseer docs search "auth" -P express,passport
2492
2728
 
2493
- # Strict matching (AND mode)
2494
- pkgseer docs search -t "error,middleware" --match-mode all --package express
2729
+ # With registry prefix (for non-npm packages)
2730
+ pkgseer docs search "orm" -P pypi/django,pypi/sqlalchemy
2731
+
2732
+ # With specific version
2733
+ pkgseer docs search "routing" -P express@4.18.2
2495
2734
 
2496
2735
  # Output for piping
2497
- pkgseer docs search log --package express --refs-only | \\
2498
- xargs -I{} pkgseer docs get express {}
2736
+ pkgseer docs search "routing" -P express --refs-only
2499
2737
 
2500
- # Count matches
2501
- pkgseer docs search error --package express --count`;
2738
+ # JSON output
2739
+ pkgseer docs search "error" -P express --json`;
2502
2740
  function addSearchOptions(cmd) {
2503
- return cmd.option("-p, --package <name>", "Search specific package (overrides project)").option("-r, --registry <registry>", "Package registry (with --package)", "npm").option("-v, --pkg-version <version>", "Package version (with --package)").option("--project <name>", "Project name (overrides config)").option("-t, --terms <words>", "Comma-separated search terms", (val) => val.split(",").map((s) => s.trim())).option("-l, --limit <n>", "Max results (default: 25)").option("-A, --after <n>", "Lines of context after match (default: 2)").option("-B, --before <n>", "Lines of context before match (default: 2)").option("-C, --context <n>", "Lines of context before and after match").option("--max-matches <n>", "Max matches per page (default: 5)").option("--match-mode <mode>", "How to combine terms: any/or (default) or all/and").option("--refs-only", "Output only page references (for piping)").option("--count", "Output only match counts per page").option("--format <format>", "Output format: human|summary|json", "human").option("--no-color", "Disable colored output").option("--json", "Output as JSON");
2741
+ return cmd.option("-P, --packages <packages>", "Packages to search (comma-separated). Format: name or registry/name[@version]. Examples: express | express,lodash | pypi/django@4.2").option("-m, --mode <mode>", "Search mode: all (default), code, docs").option("-l, --limit <n>", "Max results (default: 25)").option("-v, --verbose", "Expanded output with more details").option("--refs-only", "Output only references (for piping)").option("--count", "Output result counts by package").option("--format <format>", "Output format: human|summary|json", "human").option("--no-color", "Disable colored output").option("--json", "Output as JSON");
2504
2742
  }
2505
2743
  function registerDocsSearchCommand(program) {
2506
- const cmd = program.command("search [terms...]").summary("Search documentation").description(SEARCH_DESCRIPTION);
2507
- addSearchOptions(cmd).action(async (terms, options) => {
2744
+ const cmd = program.command("search [query...]").summary("Search code and documentation").description(SEARCH_DESCRIPTION);
2745
+ addSearchOptions(cmd).action(async (query, options) => {
2508
2746
  await withCliErrorHandling(options.json ?? false, async () => {
2509
2747
  const deps = await createContainer();
2510
- await docsSearchAction(terms, options, deps);
2748
+ await docsSearchAction(query, options, deps);
2511
2749
  });
2512
2750
  });
2513
2751
  }
@@ -2586,22 +2824,13 @@ function getCodexConfigPaths(fs, scope) {
2586
2824
  function getClaudeCodeConfigPaths(fs, scope) {
2587
2825
  if (scope === "project") {
2588
2826
  const cwd = fs.getCwd();
2589
- const configPath2 = fs.joinPath(cwd, ".claude-code", "mcp.json");
2590
- const backupPath2 = fs.joinPath(cwd, ".claude-code", "mcp.json.bak");
2827
+ const configPath2 = fs.joinPath(cwd, ".mcp.json");
2828
+ const backupPath2 = fs.joinPath(cwd, ".mcp.json.bak");
2591
2829
  return { configPath: configPath2, backupPath: backupPath2 };
2592
2830
  }
2593
- const platform = process.platform;
2594
- let configPath;
2595
- let backupPath;
2596
- if (platform === "win32") {
2597
- const appData = process.env.APPDATA || fs.joinPath(fs.getHomeDir(), "AppData", "Roaming");
2598
- configPath = fs.joinPath(appData, "Claude Code", "mcp.json");
2599
- backupPath = fs.joinPath(appData, "Claude Code", "mcp.json.bak");
2600
- } else {
2601
- const home = fs.getHomeDir();
2602
- configPath = fs.joinPath(home, ".claude-code", "mcp.json");
2603
- backupPath = fs.joinPath(home, ".claude-code", "mcp.json.bak");
2604
- }
2831
+ const home = fs.getHomeDir();
2832
+ const configPath = fs.joinPath(home, ".claude.json");
2833
+ const backupPath = fs.joinPath(home, ".claude.json.bak");
2605
2834
  return { configPath, backupPath };
2606
2835
  }
2607
2836
  async function parseConfigFile(fs, path) {
@@ -2685,14 +2914,64 @@ args = ["-y", "@pkgseer/cli", "mcp", "start"]`;
2685
2914
  };
2686
2915
  console.log(JSON.stringify(configExample, null, 2));
2687
2916
  }
2688
- if ((tool === "cursor" || tool === "codex" || tool === "claude-code") && scope === "project") {
2689
- const dirName = tool === "cursor" ? ".cursor" : tool === "codex" ? ".codex" : ".claude-code";
2917
+ if ((tool === "cursor" || tool === "codex") && scope === "project") {
2918
+ const dirName = tool === "cursor" ? ".cursor" : ".codex";
2690
2919
  console.log(dim(`
2691
2920
  Note: Create the ${dirName} directory if it doesn't exist.`, useColors));
2692
2921
  }
2693
2922
  console.log(dim(`
2694
2923
  After editing, restart your AI assistant to activate the MCP server.`, useColors));
2695
2924
  }
2925
+ async function isCliAvailable(shellService, command) {
2926
+ try {
2927
+ const checkCommand = process.platform === "win32" ? `where ${command}` : `which ${command}`;
2928
+ await shellService.execute(checkCommand, process.cwd());
2929
+ return true;
2930
+ } catch {
2931
+ return false;
2932
+ }
2933
+ }
2934
+ async function setupClaudeCodeViaCli(shellService, scope) {
2935
+ try {
2936
+ const command = `claude mcp add --transport stdio --scope ${scope} pkgseer -- npx -y @pkgseer/cli mcp start`;
2937
+ await shellService.execute(command, process.cwd());
2938
+ return { success: true };
2939
+ } catch (err) {
2940
+ return {
2941
+ success: false,
2942
+ error: err instanceof Error ? err.message : String(err)
2943
+ };
2944
+ }
2945
+ }
2946
+ async function setupCodexViaCli(shellService) {
2947
+ try {
2948
+ const command = "codex mcp add pkgseer -- npx -y @pkgseer/cli mcp start";
2949
+ await shellService.execute(command, process.cwd());
2950
+ return { success: true };
2951
+ } catch (err) {
2952
+ return {
2953
+ success: false,
2954
+ error: err instanceof Error ? err.message : String(err)
2955
+ };
2956
+ }
2957
+ }
2958
+ function showAvailableTools(hasProject, useColors) {
2959
+ console.log(`
2960
+ Available MCP tools:`);
2961
+ console.log(" • package_summary - Get package overview");
2962
+ console.log(" • package_vulnerabilities - Check for security issues");
2963
+ console.log(" • package_quality - Get quality score");
2964
+ console.log(" • package_dependencies - List dependencies");
2965
+ console.log(" • compare_packages - Compare multiple packages");
2966
+ console.log(" • list_package_docs - List documentation pages");
2967
+ console.log(" • fetch_package_doc - Fetch documentation content");
2968
+ console.log(" • search_package_docs - Search package documentation");
2969
+ console.log(" • search_project_docs - Search docs across project dependencies");
2970
+ if (!hasProject) {
2971
+ console.log(dim(`
2972
+ Tip: Create pkgseer.yml to enable automatic project detection for search_project_docs.`, useColors));
2973
+ }
2974
+ }
2696
2975
  async function mcpInitAction(deps) {
2697
2976
  const { fileSystemService: fs, promptService, hasProject } = deps;
2698
2977
  const useColors = shouldUseColors2();
@@ -2743,49 +3022,86 @@ Run ${highlight("pkgseer project init", useColors)} first, then ${highlight("pkg
2743
3022
  }
2744
3023
  let scope;
2745
3024
  if (tool === "cursor" || tool === "codex" || tool === "claude-code") {
2746
- const projectPath = tool === "cursor" ? ".cursor/mcp.json" : tool === "codex" ? ".codex/config.toml" : ".claude-code/mcp.json";
2747
- const globalPath = tool === "cursor" ? "~/.cursor/mcp.json" : tool === "codex" ? "~/.codex/config.toml" : "~/.claude-code/mcp.json";
2748
- if (hasProject) {
2749
- scope = await promptService.select("Where should the MCP config be created?", [
3025
+ const projectPath = tool === "cursor" ? ".cursor/mcp.json" : tool === "codex" ? ".codex/config.toml" : ".mcp.json";
3026
+ const globalPath = tool === "cursor" ? "~/.cursor/mcp.json" : tool === "codex" ? "~/.codex/config.toml" : "~/.claude.json";
3027
+ if (tool === "codex") {
3028
+ console.log(dim(`
3029
+ Codex uses user-level configuration.
3030
+ `, useColors));
3031
+ scope = "global";
3032
+ } else {
3033
+ scope = await promptService.select("Where should the MCP server be installed?", [
2750
3034
  {
2751
- value: "project",
2752
- name: `Project (${projectPath}) – recommended`,
2753
- description: "Uses project token; enables docs search for your packages"
3035
+ value: "global",
3036
+ name: `User-level (${globalPath}) – recommended`,
3037
+ description: "Works across all projects. Use project_directory param for project features."
2754
3038
  },
2755
3039
  {
2756
- value: "global",
2757
- name: `Global (${globalPath})`,
2758
- description: "Works everywhere but without project-specific features"
3040
+ value: "project",
3041
+ name: `Project-level (${projectPath})`,
3042
+ description: "Scoped to this project only."
2759
3043
  }
2760
3044
  ]);
2761
- } else {
2762
- console.log(dim(`
2763
- Using global config (no pkgseer.yml found).
2764
- `, useColors));
2765
- scope = "global";
2766
3045
  }
2767
3046
  }
2768
3047
  let configPath;
2769
3048
  let backupPath;
2770
- if (tool === "cursor") {
3049
+ const { shellService } = deps;
3050
+ if (tool === "claude-code") {
2771
3051
  if (!scope) {
2772
3052
  scope = "global";
2773
3053
  }
2774
- const paths = getCursorConfigPaths(fs, scope);
3054
+ const claudeAvailable = await isCliAvailable(shellService, "claude");
3055
+ if (claudeAvailable) {
3056
+ console.log(dim(`
3057
+ Using Claude Code CLI to add MCP server...
3058
+ `, useColors));
3059
+ const cliScope = scope === "global" ? "user" : "project";
3060
+ const result = await setupClaudeCodeViaCli(shellService, cliScope);
3061
+ if (result.success) {
3062
+ console.log(success("PkgSeer MCP server configured for Claude Code!", useColors));
3063
+ console.log(dim(`
3064
+ The MCP server has been registered. It should be available immediately.`, useColors));
3065
+ showAvailableTools(hasProject, useColors);
3066
+ return;
3067
+ }
3068
+ console.log(dim(`
3069
+ CLI setup failed: ${result.error}
3070
+ Falling back to file editing...
3071
+ `, useColors));
3072
+ }
3073
+ const paths = getClaudeCodeConfigPaths(fs, scope);
2775
3074
  configPath = paths.configPath;
2776
3075
  backupPath = paths.backupPath;
2777
3076
  } else if (tool === "codex") {
2778
3077
  if (!scope) {
2779
- scope = hasProject ? "project" : "global";
3078
+ scope = "global";
3079
+ }
3080
+ const codexAvailable = await isCliAvailable(shellService, "codex");
3081
+ if (codexAvailable) {
3082
+ console.log(dim(`
3083
+ Using Codex CLI to add MCP server...
3084
+ `, useColors));
3085
+ const result = await setupCodexViaCli(shellService);
3086
+ if (result.success) {
3087
+ console.log(success("PkgSeer MCP server configured for Codex CLI!", useColors));
3088
+ console.log(dim(`
3089
+ The MCP server has been registered. It should be available immediately.`, useColors));
3090
+ showAvailableTools(hasProject, useColors);
3091
+ return;
3092
+ }
3093
+ console.log(dim(`
3094
+ CLI setup failed: ${result.error}
3095
+ `, useColors));
2780
3096
  }
2781
3097
  const paths = getCodexConfigPaths(fs, scope);
2782
3098
  showManualInstructions("codex", scope, paths.configPath, useColors);
2783
3099
  return;
2784
- } else if (tool === "claude-code") {
3100
+ } else if (tool === "cursor") {
2785
3101
  if (!scope) {
2786
3102
  scope = "global";
2787
3103
  }
2788
- const paths = getClaudeCodeConfigPaths(fs, scope);
3104
+ const paths = getCursorConfigPaths(fs, scope);
2789
3105
  configPath = paths.configPath;
2790
3106
  backupPath = paths.backupPath;
2791
3107
  } else {
@@ -2862,31 +3178,19 @@ Config file: ${highlight(configPath, useColors)}`);
2862
3178
  Next steps:
2863
3179
  1. Restart your AI assistant to activate the MCP server
2864
3180
  2. Test by asking your assistant about packages`, useColors));
2865
- console.log(`
2866
- Available MCP tools:`);
2867
- console.log(" • package_summary - Get package overview");
2868
- console.log(" • package_vulnerabilities - Check for security issues");
2869
- console.log(" • package_quality - Get quality score");
2870
- console.log(" • package_dependencies - List dependencies");
2871
- console.log(" • compare_packages - Compare multiple packages");
2872
- console.log(" • list_package_docs - List documentation pages");
2873
- console.log(" • fetch_package_doc - Fetch documentation content");
2874
- console.log(" • search_package_docs - Search package documentation");
2875
- if (hasProject) {
2876
- console.log(" • search_project_docs - Search your project's docs");
2877
- }
3181
+ showAvailableTools(hasProject, useColors);
2878
3182
  }
2879
3183
  var MCP_INIT_DESCRIPTION = `Configure PkgSeer's MCP server for your AI assistant.
2880
3184
 
2881
3185
  Guides you through:
2882
3186
  • Selecting your AI tool (Cursor IDE, Codex CLI, Claude Code)
2883
- • Choosing configuration location (project or global)
2884
- Safely editing config files with automatic backup
3187
+ • Choosing installation scope (user-level or project-level)
3188
+ Setting up via CLI commands or config file editing
2885
3189
 
2886
- Project-level config (recommended):
2887
- Uses your project token for authenticated features
2888
- Enables search_project_docs (search your project's packages)
2889
- Portable with your project`;
3190
+ User-level (recommended):
3191
+ Works across all your projects
3192
+ Use project_directory parameter for project-specific features
3193
+ Automatically updated when using npx`;
2890
3194
  function registerMcpInitCommand(mcpCommand) {
2891
3195
  mcpCommand.command("init").summary("Configure MCP server for AI assistants").description(MCP_INIT_DESCRIPTION).action(async () => {
2892
3196
  const deps = await createContainer();
@@ -2895,6 +3199,7 @@ function registerMcpInitCommand(mcpCommand) {
2895
3199
  fileSystemService: deps.fileSystemService,
2896
3200
  promptService: deps.promptService,
2897
3201
  configService: deps.configService,
3202
+ shellService: deps.shellService,
2898
3203
  baseUrl: deps.baseUrl,
2899
3204
  hasProject
2900
3205
  });
@@ -3627,6 +3932,7 @@ ${highlight("✓", useColors)} Project setup complete!
3627
3932
  fileSystemService: deps.fileSystemService,
3628
3933
  promptService: deps.promptService,
3629
3934
  configService: deps.configService,
3935
+ shellService: deps.shellService,
3630
3936
  baseUrl: deps.baseUrl,
3631
3937
  hasProject: hasProjectNow
3632
3938
  });
@@ -3921,7 +4227,7 @@ var argsSchema = {
3921
4227
  function createComparePackagesTool(pkgseerService) {
3922
4228
  return {
3923
4229
  name: "compare_packages",
3924
- description: 'Compares 2-10 packages across metadata, quality, and security. Example: [{"registry":"npm","name":"react","version":"18.2.0"},{"registry":"pypi","name":"requests"}].',
4230
+ description: "Compare 2-10 packages side-by-side. Use this when evaluating alternatives (e.g., react vs preact vs solid-js). " + "Returns for each package: quality score, download counts, vulnerability count, license, and latest version. " + "Supports cross-registry comparison (npm, pypi, hex). " + 'Format: [{"registry":"npm","name":"lodash"},{"registry":"npm","name":"underscore"}]',
3925
4231
  schema: argsSchema,
3926
4232
  handler: async ({ packages }, _extra) => {
3927
4233
  return withErrorHandling("compare packages", async () => {
@@ -3942,16 +4248,47 @@ function createComparePackagesTool(pkgseerService) {
3942
4248
  }
3943
4249
  };
3944
4250
  }
3945
- // src/tools/fetch-package-doc.ts
4251
+ // src/tools/fetch-code-context.ts
3946
4252
  import { z as z4 } from "zod";
3947
4253
  var argsSchema2 = {
3948
- page_id: z4.string().max(500).describe("Globally unique documentation page identifier (from list_package_docs)")
4254
+ repo_url: z4.string().min(1).describe("Repository URL (GitHub). Example: https://github.com/expressjs/express"),
4255
+ git_ref: z4.string().min(1).describe("Git reference (tag, commit, or branch). Example: v4.18.2"),
4256
+ file_path: z4.string().min(1).describe("Path to file in repository. Example: src/router/index.js"),
4257
+ start_line: z4.number().int().positive().optional().describe("Starting line (1-indexed). If omitted, starts from line 1."),
4258
+ end_line: z4.number().int().positive().optional().describe("Ending line. If omitted, returns to end of file.")
4259
+ };
4260
+ function createFetchCodeContextTool(pkgseerService) {
4261
+ return {
4262
+ name: "fetch_code_context",
4263
+ description: "Fetch code content from a repository. Use this to expand code search results and see " + "more context around a match. Returns full file by default, or a specific line range. " + "Requires repo_url, git_ref (tag/commit/branch), and file_path from search results.",
4264
+ schema: argsSchema2,
4265
+ handler: async ({ repo_url, git_ref, file_path, start_line, end_line }, _extra) => {
4266
+ return withErrorHandling("fetch code context", async () => {
4267
+ const result = await pkgseerService.fetchCodeContext(repo_url, git_ref, file_path, {
4268
+ startLine: start_line,
4269
+ endLine: end_line
4270
+ });
4271
+ const graphqlError = handleGraphQLErrors(result.errors);
4272
+ if (graphqlError)
4273
+ return graphqlError;
4274
+ if (!result.data.fetchCodeContext) {
4275
+ return errorResult(`Code not found: ${file_path} at ${git_ref} in ${repo_url}. ` + "Check that the repository, file path, and git ref are correct.");
4276
+ }
4277
+ return textResult(JSON.stringify(result.data.fetchCodeContext, null, 2));
4278
+ });
4279
+ }
4280
+ };
4281
+ }
4282
+ // src/tools/fetch-package-doc.ts
4283
+ import { z as z5 } from "zod";
4284
+ var argsSchema3 = {
4285
+ page_id: z5.string().max(500).describe("Globally unique documentation page identifier (from list_package_docs)")
3949
4286
  };
3950
4287
  function createFetchPackageDocTool(pkgseerService) {
3951
4288
  return {
3952
4289
  name: "fetch_package_doc",
3953
- description: "Fetches the full content of a documentation page by globally unique page ID (from list_package_docs). Returns full metadata (title, content, format, breadcrumbs, source, base URL, last updated).",
3954
- schema: argsSchema2,
4290
+ description: "Get full content of a documentation page. Requires page_id from list_package_docs. " + "Returns: title, full content (markdown/HTML), format type, navigation breadcrumbs, " + "source URL, and last updated timestamp. Use this when you need complete documentation, " + "not just search snippets.",
4291
+ schema: argsSchema3,
3955
4292
  handler: async ({ page_id }, _extra) => {
3956
4293
  return withErrorHandling("fetch documentation page", async () => {
3957
4294
  const result = await pkgseerService.getDocPage(page_id);
@@ -3967,7 +4304,7 @@ function createFetchPackageDocTool(pkgseerService) {
3967
4304
  };
3968
4305
  }
3969
4306
  // src/tools/list-package-docs.ts
3970
- var argsSchema3 = {
4307
+ var argsSchema4 = {
3971
4308
  registry: schemas.registry,
3972
4309
  package_name: schemas.packageName.describe("Name of the package to list documentation for"),
3973
4310
  version: schemas.version
@@ -3975,8 +4312,8 @@ var argsSchema3 = {
3975
4312
  function createListPackageDocsTool(pkgseerService) {
3976
4313
  return {
3977
4314
  name: "list_package_docs",
3978
- description: "Lists documentation pages for a package version. Returns page titles, globally unique IDs, and metadata. Use this to discover available documentation before fetching specific pages.",
3979
- schema: argsSchema3,
4315
+ description: "Discover available documentation pages for a package. Start here before fetching or searching docs. " + "Returns: page titles, unique IDs (needed for fetch_package_doc), word counts, and update timestamps. " + "Workflow: list_package_docs fetch_package_doc for full content, or search_package_docs to find specific topics.",
4316
+ schema: argsSchema4,
3980
4317
  handler: async ({ registry, package_name, version: version2 }, _extra) => {
3981
4318
  return withErrorHandling("list package documentation", async () => {
3982
4319
  const result = await pkgseerService.listPackageDocs(toGraphQLRegistry2(registry), package_name, version2);
@@ -3992,13 +4329,13 @@ function createListPackageDocsTool(pkgseerService) {
3992
4329
  };
3993
4330
  }
3994
4331
  // src/tools/package-dependencies.ts
3995
- import { z as z5 } from "zod";
3996
- var argsSchema4 = {
4332
+ import { z as z6 } from "zod";
4333
+ var argsSchema5 = {
3997
4334
  registry: schemas.registry,
3998
4335
  package_name: schemas.packageName.describe("Name of the package to retrieve dependencies for"),
3999
4336
  version: schemas.version,
4000
- include_transitive: z5.boolean().optional().describe("Whether to include transitive dependency DAG"),
4001
- max_depth: z5.number().int().min(1).max(10).optional().describe("Maximum depth for transitive traversal (1-10)")
4337
+ include_transitive: z6.boolean().optional().describe("Whether to include transitive dependency DAG"),
4338
+ max_depth: z6.number().int().min(1).max(10).optional().describe("Maximum depth for transitive traversal (1-10)")
4002
4339
  };
4003
4340
  function decodeDag(rawDag) {
4004
4341
  if (!rawDag || typeof rawDag !== "object")
@@ -4075,8 +4412,8 @@ function buildEdgeDepths(decodedDag, rootId) {
4075
4412
  function createPackageDependenciesTool(pkgseerService) {
4076
4413
  return {
4077
4414
  name: "package_dependencies",
4078
- description: "Retrieves direct and transitive dependencies for a package version. Options: include_transitive (bool) and max_depth (1-10). Transitive output includes a DAG (`n/e/v` keys) decoded into readable nodes/edges with depth and counts; raw DAG is preserved alongside decoded form.",
4079
- schema: argsSchema4,
4415
+ description: "Analyze package dependencies. By default returns direct dependencies only. " + "Set include_transitive=true to get the full dependency tree (use max_depth to limit large trees). " + "Returns: dependency names, version constraints, and for transitive deps, a graph with depth levels. " + "Use this to understand complexity before adding a package, or to find nested dependencies.",
4416
+ schema: argsSchema5,
4080
4417
  handler: async ({ registry, package_name, version: version2, include_transitive, max_depth }, _extra) => {
4081
4418
  return withErrorHandling("fetch package dependencies", async () => {
4082
4419
  const result = await pkgseerService.getPackageDependencies(toGraphQLRegistry2(registry), package_name, version2, include_transitive, max_depth);
@@ -4114,7 +4451,7 @@ function createPackageDependenciesTool(pkgseerService) {
4114
4451
  };
4115
4452
  }
4116
4453
  // src/tools/package-quality.ts
4117
- var argsSchema5 = {
4454
+ var argsSchema6 = {
4118
4455
  registry: schemas.registry,
4119
4456
  package_name: schemas.packageName.describe("Name of the package to analyze"),
4120
4457
  version: schemas.version
@@ -4122,8 +4459,8 @@ var argsSchema5 = {
4122
4459
  function createPackageQualityTool(pkgseerService) {
4123
4460
  return {
4124
4461
  name: "package_quality",
4125
- description: "Retrieves quality score and rule-level breakdown for a package",
4126
- schema: argsSchema5,
4462
+ description: "Evaluate package maintenance health and code quality. Use this to assess if a package is well-maintained " + "before adding it as a dependency. Returns: overall score (0-100), category scores (documentation, " + "testing, community, maintenance), and individual rule results with pass/fail status. " + "Useful for comparing quality between alternative packages.",
4463
+ schema: argsSchema6,
4127
4464
  handler: async ({ registry, package_name, version: version2 }, _extra) => {
4128
4465
  return withErrorHandling("fetch package quality", async () => {
4129
4466
  const result = await pkgseerService.getPackageQuality(toGraphQLRegistry2(registry), package_name, version2);
@@ -4139,15 +4476,15 @@ function createPackageQualityTool(pkgseerService) {
4139
4476
  };
4140
4477
  }
4141
4478
  // src/tools/package-summary.ts
4142
- var argsSchema6 = {
4479
+ var argsSchema7 = {
4143
4480
  registry: schemas.registry,
4144
4481
  package_name: schemas.packageName.describe("Name of the package to retrieve summary for")
4145
4482
  };
4146
4483
  function createPackageSummaryTool(pkgseerService) {
4147
4484
  return {
4148
4485
  name: "package_summary",
4149
- description: "Retrieves comprehensive package summary including metadata, versions, security advisories, and quickstart information",
4150
- schema: argsSchema6,
4486
+ description: "Get a comprehensive overview of a package. Use this as your first tool when researching a package. " + "Returns: description, latest version, license, repository URL, download stats, " + "active security advisories count, and quickstart/installation instructions. " + "For deeper analysis, follow up with package_quality (maintenance health) or " + "package_vulnerabilities (security details).",
4487
+ schema: argsSchema7,
4151
4488
  handler: async ({ registry, package_name }, _extra) => {
4152
4489
  return withErrorHandling("fetch package summary", async () => {
4153
4490
  const result = await pkgseerService.getPackageSummary(toGraphQLRegistry2(registry), package_name);
@@ -4163,7 +4500,7 @@ function createPackageSummaryTool(pkgseerService) {
4163
4500
  };
4164
4501
  }
4165
4502
  // src/tools/package-vulnerabilities.ts
4166
- var argsSchema7 = {
4503
+ var argsSchema8 = {
4167
4504
  registry: schemas.registry,
4168
4505
  package_name: schemas.packageName.describe("Name of the package to inspect for vulnerabilities"),
4169
4506
  version: schemas.version
@@ -4171,8 +4508,8 @@ var argsSchema7 = {
4171
4508
  function createPackageVulnerabilitiesTool(pkgseerService) {
4172
4509
  return {
4173
4510
  name: "package_vulnerabilities",
4174
- description: "Retrieves vulnerability details for a package, including affected version ranges and upgrade guidance",
4175
- schema: argsSchema7,
4511
+ description: "Check security vulnerabilities for a package. Use this before adding dependencies or when auditing existing ones. " + "Returns: list of CVEs/advisories with severity levels, affected version ranges, fixed versions, " + "and upgrade recommendations. If version is specified, shows only vulnerabilities affecting that version.",
4512
+ schema: argsSchema8,
4176
4513
  handler: async ({ registry, package_name, version: version2 }, _extra) => {
4177
4514
  return withErrorHandling("fetch package vulnerabilities", async () => {
4178
4515
  const result = await pkgseerService.getPackageVulnerabilities(toGraphQLRegistry2(registry), package_name, version2);
@@ -4187,79 +4524,126 @@ function createPackageVulnerabilitiesTool(pkgseerService) {
4187
4524
  }
4188
4525
  };
4189
4526
  }
4190
- // src/tools/search-package-docs.ts
4191
- import { z as z6 } from "zod";
4192
- var argsSchema8 = {
4527
+ // src/tools/search.ts
4528
+ import { z as z7 } from "zod";
4529
+ var packageInputSchema2 = z7.object({
4193
4530
  registry: schemas.registry,
4194
- package_name: schemas.packageName.describe("Name of the package to search documentation for"),
4195
- terms: z6.array(z6.string()).optional().describe("Search terms to match. Provide a few key words or phrases that should appear in results."),
4196
- match_mode: z6.enum(["any", "or", "all", "and"]).optional().describe('How to combine terms: "any" (OR) or "all" (AND).'),
4197
- include_snippets: z6.boolean().optional().describe("Include content excerpts around matches"),
4198
- limit: z6.number().int().min(1).max(100).optional().describe("Maximum number of results to return"),
4199
- version: schemas.version
4531
+ name: z7.string().min(1).describe("Package name"),
4532
+ version: z7.string().optional().describe("Specific version (defaults to latest)")
4533
+ });
4534
+ var argsSchema9 = {
4535
+ packages: z7.array(packageInputSchema2).min(1).max(20).describe("Packages to search (1-20). Each package needs registry and name."),
4536
+ query: z7.string().min(1).describe("Search query - natural language or keywords"),
4537
+ mode: z7.enum(["all", "code", "docs"]).optional().describe('Search mode: "all" (default), "code" only, or "docs" only'),
4538
+ limit: z7.number().int().min(1).max(100).optional().describe("Maximum results (default: 20)")
4200
4539
  };
4201
- function createSearchPackageDocsTool(pkgseerService) {
4540
+ function toSearchMode2(mode) {
4541
+ if (!mode)
4542
+ return;
4543
+ const map = {
4544
+ all: "ALL",
4545
+ code: "CODE",
4546
+ docs: "DOCS"
4547
+ };
4548
+ return map[mode.toLowerCase()];
4549
+ }
4550
+ function formatIndexingWarnings2(indexingStatus) {
4551
+ if (!indexingStatus || indexingStatus.length === 0)
4552
+ return null;
4553
+ const warnings = [];
4554
+ for (const status of indexingStatus) {
4555
+ if (!status)
4556
+ continue;
4557
+ const pkg = `${status.packageName}@${status.version ?? "latest"} (${status.registry?.toLowerCase() ?? "unknown"})`;
4558
+ if (status.codeStatus && status.codeStatus !== "INDEXED") {
4559
+ warnings.push(` - ${pkg}: code ${status.codeStatus.toLowerCase()}`);
4560
+ }
4561
+ if (status.docsStatus && status.docsStatus !== "INDEXED") {
4562
+ warnings.push(` - ${pkg}: docs ${status.docsStatus.toLowerCase()}`);
4563
+ }
4564
+ }
4565
+ if (warnings.length === 0)
4566
+ return null;
4567
+ return `
4568
+ Note: Some packages are not fully indexed:
4569
+ ` + warnings.join(`
4570
+ `) + `
4571
+ Results may be incomplete. Retry later for more complete results.`;
4572
+ }
4573
+ function createSearchTool(pkgseerService) {
4202
4574
  return {
4203
- name: "search_package_docs",
4204
- description: "Searches package documentation using search terms and match modes (any/all). Returns ranked results with match context. Defaults to include_snippets=true.",
4205
- schema: argsSchema8,
4206
- handler: async ({
4207
- registry,
4208
- package_name,
4209
- terms,
4210
- match_mode,
4211
- include_snippets,
4212
- limit,
4213
- version: version2
4214
- }, _extra) => {
4215
- return withErrorHandling("search package documentation", async () => {
4216
- const normalizedTerms = terms?.map((term) => term.trim()).filter((term) => term.length > 0) ?? [];
4217
- if (normalizedTerms.length === 0) {
4218
- return errorResult("Search terms are required to run documentation search.");
4575
+ name: "search",
4576
+ description: "Search code and documentation across packages. Returns functions, classes, and documentation pages " + "matching your query. Use mode='code' for code only, mode='docs' for documentation only, or " + "mode='all' (default) for both. Provide 1-20 packages to search. Results include relevance scores " + "and snippets showing matches.",
4577
+ schema: argsSchema9,
4578
+ handler: async ({ packages, query, mode, limit }, _extra) => {
4579
+ return withErrorHandling("search packages", async () => {
4580
+ const normalizedQuery = query.trim();
4581
+ if (normalizedQuery.length === 0) {
4582
+ return errorResult("Search query is required.");
4219
4583
  }
4220
- const matchMode = match_mode === "all" || match_mode === "and" ? "AND" : match_mode === "any" || match_mode === "or" ? "OR" : undefined;
4221
- const includeSnippets = include_snippets ?? true;
4222
- const result = await pkgseerService.searchPackageDocs(toGraphQLRegistry2(registry), package_name, {
4223
- keywords: normalizedTerms,
4224
- matchMode,
4225
- includeSnippets,
4226
- limit,
4227
- version: version2
4584
+ const graphqlPackages = packages.map((pkg) => ({
4585
+ registry: toGraphQLRegistry2(pkg.registry),
4586
+ name: pkg.name,
4587
+ version: pkg.version
4588
+ }));
4589
+ const result = await pkgseerService.combinedSearch(graphqlPackages, normalizedQuery, {
4590
+ mode: toSearchMode2(mode),
4591
+ limit
4228
4592
  });
4229
4593
  const graphqlError = handleGraphQLErrors(result.errors);
4230
4594
  if (graphqlError)
4231
4595
  return graphqlError;
4232
- if (!result.data.searchPackageDocs) {
4233
- return errorResult(`No documentation found for ${package_name} in ${registry}`);
4596
+ if (!result.data.combinedSearch) {
4597
+ return errorResult("No search results returned. Check package names and registries.");
4234
4598
  }
4235
- if ((result.data.searchPackageDocs.entries ?? []).length === 0 && normalizedTerms.length > 0) {
4236
- return errorResult(`No documentation matched: ${normalizedTerms.join(", ")}. ` + "Try fewer or broader terms, or reduce match constraints.");
4599
+ const searchResult = result.data.combinedSearch;
4600
+ const entries = searchResult.entries ?? [];
4601
+ if (entries.length === 0) {
4602
+ return errorResult(`No results found for "${normalizedQuery}". ` + "Try broader terms or different packages.");
4237
4603
  }
4238
- return textResult(JSON.stringify(result.data.searchPackageDocs, null, 2));
4604
+ let response = JSON.stringify(searchResult, null, 2);
4605
+ const indexingWarning = formatIndexingWarnings2(searchResult.indexingStatus);
4606
+ if (indexingWarning) {
4607
+ response += indexingWarning;
4608
+ }
4609
+ return textResult(response);
4239
4610
  });
4240
4611
  }
4241
4612
  };
4242
4613
  }
4243
4614
  // src/tools/search-project-docs.ts
4244
- import { z as z7 } from "zod";
4245
- var argsSchema9 = {
4246
- project: z7.string().optional().describe("Project name to search. Optional if configured in pkgseer.yml; only needed to search a different project."),
4247
- terms: z7.array(z7.string()).optional().describe("Search terms to match. Provide a few key words or phrases that should appear in results."),
4248
- match_mode: z7.enum(["any", "or", "all", "and"]).optional().describe('How to combine terms: "any" (OR) or "all" (AND).'),
4249
- include_snippets: z7.boolean().optional().describe("Include content excerpts around matches"),
4250
- limit: z7.number().int().min(1).max(100).optional().describe("Maximum number of results to return")
4615
+ import { z as z8 } from "zod";
4616
+ var argsSchema10 = {
4617
+ project_directory: z8.string().optional().describe("Directory to search for pkgseer.yml configuration. " + "Use this to specify which project's context to use when the MCP server " + "is installed at user level. Defaults to MCP server's working directory."),
4618
+ project: z8.string().optional().describe("Project name to search. Overrides value from pkgseer.yml if provided."),
4619
+ terms: z8.array(z8.string()).optional().describe("Search terms to match. Provide a few key words or phrases that should appear in results."),
4620
+ match_mode: z8.enum(["any", "or", "all", "and"]).optional().describe('How to combine terms: "any" (OR) or "all" (AND).'),
4621
+ include_snippets: z8.boolean().optional().describe("Include content excerpts around matches"),
4622
+ limit: z8.number().int().min(1).max(100).optional().describe("Maximum number of results to return")
4251
4623
  };
4252
4624
  function createSearchProjectDocsTool(deps) {
4253
- const { pkgseerService, config } = deps;
4625
+ const { pkgseerService, configService, defaultProjectDir } = deps;
4254
4626
  return {
4255
4627
  name: "search_project_docs",
4256
- description: "Searches documentation across all dependencies in a PkgSeer project using search terms and match modes (any/all). Returns ranked results from multiple packages. Uses project from pkgseer.yml config by default.",
4257
- schema: argsSchema9,
4258
- handler: async ({ project, terms, match_mode, include_snippets, limit }, _extra) => {
4628
+ description: "Searches documentation across all dependencies in a PkgSeer project using search terms and match modes (any/all). Returns ranked results from multiple packages. Use project_directory to specify which project's context to use, or project to search a specific project directly.",
4629
+ schema: argsSchema10,
4630
+ handler: async ({
4631
+ project_directory,
4632
+ project,
4633
+ terms,
4634
+ match_mode,
4635
+ include_snippets,
4636
+ limit
4637
+ }, _extra) => {
4259
4638
  return withErrorHandling("search project documentation", async () => {
4260
- const resolvedProject = project ?? config.project;
4639
+ const targetDir = project_directory ?? defaultProjectDir;
4640
+ const projectConfig = await configService.loadProjectConfigFrom(targetDir);
4641
+ const resolvedProject = project ?? projectConfig?.config.project;
4261
4642
  if (!resolvedProject) {
4262
- return errorResult("No project provided and none configured in pkgseer.yml. " + "Either pass project parameter or add project to your config.");
4643
+ return errorResult(`No project specified and no pkgseer.yml found. To fix:
4644
+ ` + ` 1. Pass project_directory: '/path/to/project' (folder containing pkgseer.yml)
4645
+ ` + ` 2. Pass project: 'project-name' directly if you know the project name
4646
+ ` + " 3. Run 'pkgseer project init' to create pkgseer.yml in your project");
4263
4647
  }
4264
4648
  const normalizedTerms = terms?.map((term) => term.trim()).filter((term) => term.length > 0) ?? [];
4265
4649
  if (normalizedTerms.length === 0) {
@@ -4296,8 +4680,13 @@ var TOOL_FACTORIES = {
4296
4680
  compare_packages: ({ pkgseerService }) => createComparePackagesTool(pkgseerService),
4297
4681
  list_package_docs: ({ pkgseerService }) => createListPackageDocsTool(pkgseerService),
4298
4682
  fetch_package_doc: ({ pkgseerService }) => createFetchPackageDocTool(pkgseerService),
4299
- search_package_docs: ({ pkgseerService }) => createSearchPackageDocsTool(pkgseerService),
4300
- search_project_docs: ({ pkgseerService, config }) => createSearchProjectDocsTool({ pkgseerService, config })
4683
+ search: ({ pkgseerService }) => createSearchTool(pkgseerService),
4684
+ fetch_code_context: ({ pkgseerService }) => createFetchCodeContextTool(pkgseerService),
4685
+ search_project_docs: ({ pkgseerService, configService, defaultProjectDir }) => createSearchProjectDocsTool({
4686
+ pkgseerService,
4687
+ configService,
4688
+ defaultProjectDir
4689
+ })
4301
4690
  };
4302
4691
  var PUBLIC_READ_TOOLS = [
4303
4692
  "package_summary",
@@ -4307,21 +4696,29 @@ var PUBLIC_READ_TOOLS = [
4307
4696
  "compare_packages",
4308
4697
  "list_package_docs",
4309
4698
  "fetch_package_doc",
4310
- "search_package_docs"
4699
+ "search",
4700
+ "fetch_code_context"
4311
4701
  ];
4312
4702
  var PROJECT_READ_TOOLS = ["search_project_docs"];
4313
4703
  var ALL_TOOLS = [...PUBLIC_READ_TOOLS, ...PROJECT_READ_TOOLS];
4314
4704
  function createMcpServer(deps) {
4315
- const { pkgseerService, config } = deps;
4705
+ const { pkgseerService, configService, config, fileSystemService } = deps;
4316
4706
  const server = new McpServer({
4317
4707
  name: "pkgseer",
4318
4708
  version: "0.1.0"
4319
4709
  });
4710
+ const defaultProjectDir = fileSystemService.getCwd();
4320
4711
  const enabledToolNames = config.enabled_tools ?? ALL_TOOLS;
4321
4712
  const toolsToRegister = enabledToolNames.filter((name) => ALL_TOOLS.includes(name));
4713
+ const factoryDeps = {
4714
+ pkgseerService,
4715
+ configService,
4716
+ config,
4717
+ defaultProjectDir
4718
+ };
4322
4719
  for (const toolName of toolsToRegister) {
4323
4720
  const factory = TOOL_FACTORIES[toolName];
4324
- const tool = factory({ pkgseerService, config });
4721
+ const tool = factory(factoryDeps);
4325
4722
  server.registerTool(tool.name, { description: tool.description, inputSchema: tool.schema }, tool.handler);
4326
4723
  }
4327
4724
  return server;