@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 +733 -336
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-z505vakm.js → chunk-1m9g9ehr.js} +1 -1
- package/package.json +1 -1
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-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2299
|
-
if (!
|
|
2300
|
-
return
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
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
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
const
|
|
2312
|
-
|
|
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
|
|
2387
|
+
return `[${typeStr}]`;
|
|
2317
2388
|
}
|
|
2318
|
-
function formatEntry(entry,
|
|
2389
|
+
function formatEntry(entry, useColors) {
|
|
2319
2390
|
if (!entry)
|
|
2320
2391
|
return "";
|
|
2321
2392
|
const lines = [];
|
|
2322
|
-
const
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
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
|
-
|
|
2342
|
-
if (
|
|
2343
|
-
|
|
2344
|
-
lines.push(` ${highlighted}`);
|
|
2403
|
+
} else {
|
|
2404
|
+
lines.push(`${badge} ${title}`);
|
|
2405
|
+
if (location) {
|
|
2406
|
+
lines.push(` ${location}${lang}`);
|
|
2345
2407
|
}
|
|
2346
2408
|
}
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
2509
|
+
const counts = new Map;
|
|
2510
|
+
for (const entry of entries) {
|
|
2376
2511
|
if (!entry)
|
|
2377
|
-
|
|
2378
|
-
const
|
|
2379
|
-
|
|
2380
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
}
|
|
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
|
|
2406
|
-
const
|
|
2407
|
-
|
|
2408
|
-
|
|
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
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
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
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
2710
|
+
var SEARCH_DESCRIPTION = `Search code and documentation across packages.
|
|
2476
2711
|
|
|
2477
|
-
Searches
|
|
2478
|
-
|
|
2479
|
-
|
|
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
|
|
2487
|
-
pkgseer docs search "error handling"
|
|
2488
|
-
|
|
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
|
-
#
|
|
2491
|
-
pkgseer docs search
|
|
2726
|
+
# Search multiple packages
|
|
2727
|
+
pkgseer docs search "auth" -P express,passport
|
|
2492
2728
|
|
|
2493
|
-
#
|
|
2494
|
-
pkgseer docs search
|
|
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
|
|
2498
|
-
xargs -I{} pkgseer docs get express {}
|
|
2736
|
+
pkgseer docs search "routing" -P express --refs-only
|
|
2499
2737
|
|
|
2500
|
-
#
|
|
2501
|
-
pkgseer docs search error
|
|
2738
|
+
# JSON output
|
|
2739
|
+
pkgseer docs search "error" -P express --json`;
|
|
2502
2740
|
function addSearchOptions(cmd) {
|
|
2503
|
-
return cmd.option("-
|
|
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 [
|
|
2507
|
-
addSearchOptions(cmd).action(async (
|
|
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(
|
|
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, ".
|
|
2590
|
-
const backupPath2 = fs.joinPath(cwd, ".
|
|
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
|
|
2594
|
-
|
|
2595
|
-
|
|
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"
|
|
2689
|
-
const dirName = tool === "cursor" ? ".cursor" :
|
|
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" : ".
|
|
2747
|
-
const globalPath = tool === "cursor" ? "~/.cursor/mcp.json" : tool === "codex" ? "~/.codex/config.toml" : "~/.claude
|
|
2748
|
-
if (
|
|
2749
|
-
|
|
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: "
|
|
2752
|
-
name: `
|
|
2753
|
-
description: "
|
|
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: "
|
|
2757
|
-
name: `
|
|
2758
|
-
description: "
|
|
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
|
-
|
|
3049
|
+
const { shellService } = deps;
|
|
3050
|
+
if (tool === "claude-code") {
|
|
2771
3051
|
if (!scope) {
|
|
2772
3052
|
scope = "global";
|
|
2773
3053
|
}
|
|
2774
|
-
const
|
|
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 =
|
|
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 === "
|
|
3100
|
+
} else if (tool === "cursor") {
|
|
2785
3101
|
if (!scope) {
|
|
2786
3102
|
scope = "global";
|
|
2787
3103
|
}
|
|
2788
|
-
const paths =
|
|
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
|
-
|
|
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
|
|
2884
|
-
•
|
|
3187
|
+
• Choosing installation scope (user-level or project-level)
|
|
3188
|
+
• Setting up via CLI commands or config file editing
|
|
2885
3189
|
|
|
2886
|
-
|
|
2887
|
-
•
|
|
2888
|
-
•
|
|
2889
|
-
•
|
|
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:
|
|
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-
|
|
4251
|
+
// src/tools/fetch-code-context.ts
|
|
3946
4252
|
import { z as z4 } from "zod";
|
|
3947
4253
|
var argsSchema2 = {
|
|
3948
|
-
|
|
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: "
|
|
3954
|
-
schema:
|
|
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
|
|
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: "
|
|
3979
|
-
schema:
|
|
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
|
|
3996
|
-
var
|
|
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:
|
|
4001
|
-
max_depth:
|
|
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: "
|
|
4079
|
-
schema:
|
|
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
|
|
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: "
|
|
4126
|
-
schema:
|
|
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
|
|
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: "
|
|
4150
|
-
schema:
|
|
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
|
|
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: "
|
|
4175
|
-
schema:
|
|
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
|
|
4191
|
-
import { z as
|
|
4192
|
-
var
|
|
4527
|
+
// src/tools/search.ts
|
|
4528
|
+
import { z as z7 } from "zod";
|
|
4529
|
+
var packageInputSchema2 = z7.object({
|
|
4193
4530
|
registry: schemas.registry,
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
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
|
|
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: "
|
|
4204
|
-
description: "
|
|
4205
|
-
schema:
|
|
4206
|
-
handler: async ({
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
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
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
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.
|
|
4233
|
-
return errorResult(
|
|
4596
|
+
if (!result.data.combinedSearch) {
|
|
4597
|
+
return errorResult("No search results returned. Check package names and registries.");
|
|
4234
4598
|
}
|
|
4235
|
-
|
|
4236
|
-
|
|
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
|
-
|
|
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
|
|
4245
|
-
var
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
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,
|
|
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.
|
|
4257
|
-
schema:
|
|
4258
|
-
handler: async ({
|
|
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
|
|
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(
|
|
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
|
-
|
|
4300
|
-
|
|
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
|
-
"
|
|
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(
|
|
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;
|