@pkgseer/cli 0.1.5 → 0.2.1
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 +1112 -441
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-0fnprry7.js → chunk-9016p6c6.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-9016p6c6.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(`
|
|
2523
|
+
`);
|
|
2524
|
+
}
|
|
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) {
|
|
2610
|
+
const entries = results.entries ?? [];
|
|
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) {
|
|
2617
|
+
if (!entry)
|
|
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(`
|
|
2381
2635
|
`);
|
|
2382
2636
|
}
|
|
2383
|
-
function
|
|
2637
|
+
function summarizeSearchResults(results) {
|
|
2384
2638
|
const entries = results.entries ?? [];
|
|
2385
|
-
|
|
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) {
|
|
2386
2646
|
if (!entry)
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
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
|
|
2476
|
-
|
|
2477
|
-
Searches across package or project documentation with keyword
|
|
2478
|
-
or freeform query support. Shows matching lines with context
|
|
2479
|
-
and highlights matched terms.
|
|
2710
|
+
var SEARCH_DESCRIPTION = `Search code and documentation across packages.
|
|
2480
2711
|
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
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
|
-
pkgseer docs search log --package express -C 3
|
|
2717
|
+
# Search a single package (code + docs)
|
|
2718
|
+
pkgseer docs search "error handling" -P express
|
|
2489
2719
|
|
|
2490
|
-
#
|
|
2491
|
-
pkgseer docs search
|
|
2720
|
+
# Search code only
|
|
2721
|
+
pkgseer docs search "Router.use" -P express --mode code
|
|
2492
2722
|
|
|
2493
|
-
#
|
|
2494
|
-
pkgseer docs search
|
|
2723
|
+
# Search docs only
|
|
2724
|
+
pkgseer docs search "middleware" -P express --mode docs
|
|
2725
|
+
|
|
2726
|
+
# Search multiple packages
|
|
2727
|
+
pkgseer docs search "auth" -P express,passport
|
|
2728
|
+
|
|
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
|
}
|
|
@@ -2684,6 +2922,56 @@ Note: Create the ${dirName} directory if it doesn't exist.`, useColors));
|
|
|
2684
2922
|
console.log(dim(`
|
|
2685
2923
|
After editing, restart your AI assistant to activate the MCP server.`, useColors));
|
|
2686
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
|
+
}
|
|
2687
2975
|
async function mcpInitAction(deps) {
|
|
2688
2976
|
const { fileSystemService: fs, promptService, hasProject } = deps;
|
|
2689
2977
|
const useColors = shouldUseColors2();
|
|
@@ -2736,47 +3024,84 @@ Run ${highlight("pkgseer project init", useColors)} first, then ${highlight("pkg
|
|
|
2736
3024
|
if (tool === "cursor" || tool === "codex" || tool === "claude-code") {
|
|
2737
3025
|
const projectPath = tool === "cursor" ? ".cursor/mcp.json" : tool === "codex" ? ".codex/config.toml" : ".mcp.json";
|
|
2738
3026
|
const globalPath = tool === "cursor" ? "~/.cursor/mcp.json" : tool === "codex" ? "~/.codex/config.toml" : "~/.claude.json";
|
|
2739
|
-
if (
|
|
2740
|
-
|
|
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?", [
|
|
2741
3034
|
{
|
|
2742
|
-
value: "
|
|
2743
|
-
name: `
|
|
2744
|
-
description: "
|
|
3035
|
+
value: "global",
|
|
3036
|
+
name: `User-level (${globalPath}) – recommended`,
|
|
3037
|
+
description: "Works across all projects. Use project_directory param for project features."
|
|
2745
3038
|
},
|
|
2746
3039
|
{
|
|
2747
|
-
value: "
|
|
2748
|
-
name: `
|
|
2749
|
-
description: "
|
|
3040
|
+
value: "project",
|
|
3041
|
+
name: `Project-level (${projectPath})`,
|
|
3042
|
+
description: "Scoped to this project only."
|
|
2750
3043
|
}
|
|
2751
3044
|
]);
|
|
2752
|
-
} else {
|
|
2753
|
-
console.log(dim(`
|
|
2754
|
-
Using global config (no pkgseer.yml found).
|
|
2755
|
-
`, useColors));
|
|
2756
|
-
scope = "global";
|
|
2757
3045
|
}
|
|
2758
3046
|
}
|
|
2759
3047
|
let configPath;
|
|
2760
3048
|
let backupPath;
|
|
2761
|
-
|
|
3049
|
+
const { shellService } = deps;
|
|
3050
|
+
if (tool === "claude-code") {
|
|
2762
3051
|
if (!scope) {
|
|
2763
3052
|
scope = "global";
|
|
2764
3053
|
}
|
|
2765
|
-
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);
|
|
2766
3074
|
configPath = paths.configPath;
|
|
2767
3075
|
backupPath = paths.backupPath;
|
|
2768
3076
|
} else if (tool === "codex") {
|
|
2769
3077
|
if (!scope) {
|
|
2770
|
-
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));
|
|
2771
3096
|
}
|
|
2772
3097
|
const paths = getCodexConfigPaths(fs, scope);
|
|
2773
3098
|
showManualInstructions("codex", scope, paths.configPath, useColors);
|
|
2774
3099
|
return;
|
|
2775
|
-
} else if (tool === "
|
|
3100
|
+
} else if (tool === "cursor") {
|
|
2776
3101
|
if (!scope) {
|
|
2777
3102
|
scope = "global";
|
|
2778
3103
|
}
|
|
2779
|
-
const paths =
|
|
3104
|
+
const paths = getCursorConfigPaths(fs, scope);
|
|
2780
3105
|
configPath = paths.configPath;
|
|
2781
3106
|
backupPath = paths.backupPath;
|
|
2782
3107
|
} else {
|
|
@@ -2853,31 +3178,19 @@ Config file: ${highlight(configPath, useColors)}`);
|
|
|
2853
3178
|
Next steps:
|
|
2854
3179
|
1. Restart your AI assistant to activate the MCP server
|
|
2855
3180
|
2. Test by asking your assistant about packages`, useColors));
|
|
2856
|
-
|
|
2857
|
-
Available MCP tools:`);
|
|
2858
|
-
console.log(" • package_summary - Get package overview");
|
|
2859
|
-
console.log(" • package_vulnerabilities - Check for security issues");
|
|
2860
|
-
console.log(" • package_quality - Get quality score");
|
|
2861
|
-
console.log(" • package_dependencies - List dependencies");
|
|
2862
|
-
console.log(" • compare_packages - Compare multiple packages");
|
|
2863
|
-
console.log(" • list_package_docs - List documentation pages");
|
|
2864
|
-
console.log(" • fetch_package_doc - Fetch documentation content");
|
|
2865
|
-
console.log(" • search_package_docs - Search package documentation");
|
|
2866
|
-
if (hasProject) {
|
|
2867
|
-
console.log(" • search_project_docs - Search your project's docs");
|
|
2868
|
-
}
|
|
3181
|
+
showAvailableTools(hasProject, useColors);
|
|
2869
3182
|
}
|
|
2870
3183
|
var MCP_INIT_DESCRIPTION = `Configure PkgSeer's MCP server for your AI assistant.
|
|
2871
3184
|
|
|
2872
3185
|
Guides you through:
|
|
2873
3186
|
• Selecting your AI tool (Cursor IDE, Codex CLI, Claude Code)
|
|
2874
|
-
• Choosing
|
|
2875
|
-
•
|
|
3187
|
+
• Choosing installation scope (user-level or project-level)
|
|
3188
|
+
• Setting up via CLI commands or config file editing
|
|
2876
3189
|
|
|
2877
|
-
|
|
2878
|
-
•
|
|
2879
|
-
•
|
|
2880
|
-
•
|
|
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`;
|
|
2881
3194
|
function registerMcpInitCommand(mcpCommand) {
|
|
2882
3195
|
mcpCommand.command("init").summary("Configure MCP server for AI assistants").description(MCP_INIT_DESCRIPTION).action(async () => {
|
|
2883
3196
|
const deps = await createContainer();
|
|
@@ -2886,6 +3199,7 @@ function registerMcpInitCommand(mcpCommand) {
|
|
|
2886
3199
|
fileSystemService: deps.fileSystemService,
|
|
2887
3200
|
promptService: deps.promptService,
|
|
2888
3201
|
configService: deps.configService,
|
|
3202
|
+
shellService: deps.shellService,
|
|
2889
3203
|
baseUrl: deps.baseUrl,
|
|
2890
3204
|
hasProject
|
|
2891
3205
|
});
|
|
@@ -3525,30 +3839,362 @@ function registerProjectInitCommand(program) {
|
|
|
3525
3839
|
});
|
|
3526
3840
|
}
|
|
3527
3841
|
|
|
3528
|
-
// src/commands/init.ts
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3842
|
+
// src/commands/skill-init.ts
|
|
3843
|
+
function generateSkillContent() {
|
|
3844
|
+
return `---
|
|
3845
|
+
name: pkgseer
|
|
3846
|
+
version: ${version}
|
|
3847
|
+
description: >-
|
|
3848
|
+
Search code and documentation across npm, PyPI, and Hex packages.
|
|
3849
|
+
Find functions, classes, APIs, and usage examples. Also provides
|
|
3850
|
+
quality scores, security vulnerabilities, dependencies, and comparisons.
|
|
3851
|
+
Triggers on: "how does X work", "find examples of", "search for",
|
|
3852
|
+
"is X secure", "compare X vs Y", package evaluation, dependency decisions,
|
|
3853
|
+
"what package should I use for", API lookup, security audits.
|
|
3854
|
+
---
|
|
3855
|
+
|
|
3856
|
+
# PkgSeer - Package Intelligence
|
|
3857
|
+
|
|
3858
|
+
Search and analyze packages across npm, PyPI, and Hex. All commands support \`--json\`.
|
|
3859
|
+
|
|
3860
|
+
## Search (Primary Use Case)
|
|
3861
|
+
|
|
3862
|
+
\`\`\`bash
|
|
3863
|
+
# Search code and docs across packages
|
|
3864
|
+
pkgseer search "<query>" -P lodash,express # npm packages
|
|
3865
|
+
pkgseer search "<query>" -P requests -r pypi # PyPI packages
|
|
3866
|
+
pkgseer search "authentication" -P phoenix,plug -r hex
|
|
3867
|
+
|
|
3868
|
+
# Search modes
|
|
3869
|
+
pkgseer search "<query>" -P <packages> --code # Code only
|
|
3870
|
+
pkgseer search "<query>" -P <packages> --docs # Docs only
|
|
3871
|
+
|
|
3872
|
+
# Search project dependencies (requires pkgseer.yml)
|
|
3873
|
+
pkgseer docs search "<query>"
|
|
3874
|
+
\`\`\`
|
|
3875
|
+
|
|
3876
|
+
## Package Analysis
|
|
3877
|
+
|
|
3878
|
+
\`\`\`bash
|
|
3879
|
+
# Overview: metadata, versions, quickstart
|
|
3880
|
+
pkgseer pkg info <package> [-r npm|pypi|hex]
|
|
3881
|
+
|
|
3882
|
+
# Quality score (0-100) with category breakdown
|
|
3883
|
+
pkgseer pkg quality <package> [-r registry] [-v version]
|
|
3884
|
+
|
|
3885
|
+
# Security: CVEs, severity, upgrade paths
|
|
3886
|
+
pkgseer pkg vulns <package> [-r registry] [-v version]
|
|
3887
|
+
|
|
3888
|
+
# Dependencies: direct, transitive, tree view
|
|
3889
|
+
pkgseer pkg deps <package> [-r registry] [-t] [-d depth]
|
|
3890
|
+
|
|
3891
|
+
# Compare up to 10 packages
|
|
3892
|
+
pkgseer pkg compare lodash underscore ramda
|
|
3893
|
+
pkgseer pkg compare npm:axios pypi:httpx # cross-registry
|
|
3894
|
+
\`\`\`
|
|
3895
|
+
|
|
3896
|
+
## Documentation
|
|
3897
|
+
|
|
3898
|
+
\`\`\`bash
|
|
3899
|
+
pkgseer docs list <package> [-r registry] # List pages
|
|
3900
|
+
pkgseer docs get <package>/<page-id> # Fetch content
|
|
3901
|
+
\`\`\`
|
|
3902
|
+
|
|
3903
|
+
## Tips
|
|
3904
|
+
|
|
3905
|
+
- Default registry: npm. Use \`-r pypi\` or \`-r hex\` for others
|
|
3906
|
+
- Use \`--json\` for structured output when parsing
|
|
3907
|
+
- Version defaults to latest; use \`-v\` for specific
|
|
3908
|
+
`;
|
|
3909
|
+
}
|
|
3910
|
+
function extractSkillVersion(content) {
|
|
3911
|
+
const match = content.match(/^version:\s*["']?([^"'\n]+)["']?/m);
|
|
3912
|
+
return match?.[1]?.trim() ?? null;
|
|
3913
|
+
}
|
|
3914
|
+
function getClaudeCodeSkillPaths(fs, scope) {
|
|
3915
|
+
const baseDir = scope === "user" ? fs.joinPath(fs.getHomeDir(), ".claude", "skills", "pkgseer") : fs.joinPath(fs.getCwd(), ".claude", "skills", "pkgseer");
|
|
3916
|
+
return {
|
|
3917
|
+
skillDir: baseDir,
|
|
3918
|
+
skillPath: fs.joinPath(baseDir, "SKILL.md")
|
|
3919
|
+
};
|
|
3920
|
+
}
|
|
3921
|
+
function getCodexSkillPaths(fs, scope) {
|
|
3922
|
+
const baseDir = scope === "user" ? fs.joinPath(fs.getHomeDir(), ".codex", "skills", "pkgseer") : fs.joinPath(fs.getCwd(), ".codex", "skills", "pkgseer");
|
|
3923
|
+
return {
|
|
3924
|
+
skillDir: baseDir,
|
|
3925
|
+
skillPath: fs.joinPath(baseDir, "SKILL.md")
|
|
3926
|
+
};
|
|
3927
|
+
}
|
|
3928
|
+
async function installSkill(fs, skillDir, skillPath) {
|
|
3929
|
+
try {
|
|
3930
|
+
const exists = await fs.exists(skillPath);
|
|
3931
|
+
let updated = false;
|
|
3932
|
+
if (exists) {
|
|
3933
|
+
const existingContent = await fs.readFile(skillPath);
|
|
3934
|
+
const existingVersion = extractSkillVersion(existingContent);
|
|
3935
|
+
if (existingVersion === version) {
|
|
3936
|
+
return {
|
|
3937
|
+
success: true,
|
|
3938
|
+
path: skillPath,
|
|
3939
|
+
updated: false
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
updated = true;
|
|
3943
|
+
}
|
|
3944
|
+
await fs.ensureDir(skillDir);
|
|
3945
|
+
const content = generateSkillContent();
|
|
3946
|
+
await fs.writeFile(skillPath, content);
|
|
3947
|
+
return {
|
|
3948
|
+
success: true,
|
|
3949
|
+
path: skillPath,
|
|
3950
|
+
updated
|
|
3951
|
+
};
|
|
3952
|
+
} catch (err) {
|
|
3953
|
+
return {
|
|
3954
|
+
success: false,
|
|
3955
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3956
|
+
};
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
async function skillInitAction(deps) {
|
|
3960
|
+
const { fileSystemService: fs, promptService } = deps;
|
|
3961
|
+
const useColors = shouldUseColors2();
|
|
3962
|
+
console.log("Skill Setup");
|
|
3963
|
+
console.log(`───────────
|
|
3964
|
+
`);
|
|
3965
|
+
console.log(`Install PkgSeer as an agent skill for your AI assistant.
|
|
3966
|
+
`);
|
|
3967
|
+
console.log(dim(`Skills provide Claude/Codex with package intelligence capabilities
|
|
3968
|
+
` + `through natural language - no MCP server required.
|
|
3969
|
+
`, useColors));
|
|
3970
|
+
const tool = await promptService.select("Which AI tool would you like to configure?", [
|
|
3971
|
+
{
|
|
3972
|
+
value: "claude-code",
|
|
3973
|
+
name: "Claude Code",
|
|
3974
|
+
description: "Install skill for Claude Code CLI"
|
|
3975
|
+
},
|
|
3976
|
+
{
|
|
3977
|
+
value: "codex",
|
|
3978
|
+
name: "Codex CLI",
|
|
3979
|
+
description: "Install skill for OpenAI Codex CLI"
|
|
3980
|
+
},
|
|
3981
|
+
{
|
|
3982
|
+
value: "other",
|
|
3983
|
+
name: "Other / Manual",
|
|
3984
|
+
description: "Show SKILL.md content for manual installation"
|
|
3985
|
+
}
|
|
3986
|
+
]);
|
|
3987
|
+
if (tool === "other") {
|
|
3988
|
+
showManualInstructions2(useColors);
|
|
3989
|
+
return;
|
|
3990
|
+
}
|
|
3991
|
+
const scope = await promptService.select("Where should the skill be installed?", [
|
|
3992
|
+
{
|
|
3993
|
+
value: "user",
|
|
3994
|
+
name: "User-level (recommended)",
|
|
3995
|
+
description: "Available in all your projects"
|
|
3996
|
+
},
|
|
3997
|
+
{
|
|
3998
|
+
value: "project",
|
|
3999
|
+
name: "Project-level",
|
|
4000
|
+
description: "Only available in this project"
|
|
4001
|
+
}
|
|
4002
|
+
]);
|
|
4003
|
+
const paths = tool === "claude-code" ? getClaudeCodeSkillPaths(fs, scope) : getCodexSkillPaths(fs, scope);
|
|
4004
|
+
const exists = await fs.exists(paths.skillPath);
|
|
4005
|
+
if (exists) {
|
|
4006
|
+
const existingContent = await fs.readFile(paths.skillPath);
|
|
4007
|
+
const existingVersion = extractSkillVersion(existingContent);
|
|
4008
|
+
if (existingVersion === version) {
|
|
4009
|
+
console.log(success(`
|
|
4010
|
+
PkgSeer skill already installed (v${version})`, useColors));
|
|
4011
|
+
console.log(`Location: ${highlight(paths.skillPath, useColors)}`);
|
|
4012
|
+
return;
|
|
4013
|
+
}
|
|
4014
|
+
console.log(`
|
|
4015
|
+
Existing skill found: v${existingVersion ?? "unknown"} → v${version}`);
|
|
4016
|
+
const proceed = await promptService.confirm("Update to latest version?", true);
|
|
4017
|
+
if (!proceed) {
|
|
4018
|
+
console.log(dim(`
|
|
4019
|
+
Skip update.`, useColors));
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
console.log(dim(`
|
|
4024
|
+
Installing skill...`, useColors));
|
|
4025
|
+
const result = await installSkill(fs, paths.skillDir, paths.skillPath);
|
|
4026
|
+
if (!result.success) {
|
|
4027
|
+
console.log(error(`
|
|
4028
|
+
Failed to install skill: ${result.error}`, useColors));
|
|
4029
|
+
showManualInstructions2(useColors);
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
4032
|
+
const toolName = tool === "claude-code" ? "Claude Code" : "Codex";
|
|
4033
|
+
const action = result.updated ? "updated" : "installed";
|
|
4034
|
+
console.log(success(`
|
|
4035
|
+
PkgSeer skill ${action} for ${toolName}!`, useColors));
|
|
4036
|
+
console.log(`Location: ${highlight(paths.skillPath, useColors)}`);
|
|
4037
|
+
console.log(dim(`
|
|
4038
|
+
The skill is now available. Try asking:
|
|
4039
|
+
` + ` "Search for authentication examples in express"
|
|
4040
|
+
` + ` "Is lodash secure? Check for vulnerabilities"
|
|
4041
|
+
` + ' "Compare axios vs fetch vs got"', useColors));
|
|
4042
|
+
}
|
|
4043
|
+
function showManualInstructions2(useColors) {
|
|
4044
|
+
console.log(`
|
|
4045
|
+
Manual Installation`);
|
|
4046
|
+
console.log(`───────────────────
|
|
4047
|
+
`);
|
|
4048
|
+
console.log(`Create a SKILL.md file in your skills directory:
|
|
4049
|
+
`);
|
|
4050
|
+
console.log("Claude Code:");
|
|
4051
|
+
console.log(` User: ${highlight("~/.claude/skills/pkgseer/SKILL.md", useColors)}`);
|
|
4052
|
+
console.log(` Project: ${highlight(".claude/skills/pkgseer/SKILL.md", useColors)}
|
|
4053
|
+
`);
|
|
4054
|
+
console.log("Codex:");
|
|
4055
|
+
console.log(` User: ${highlight("~/.codex/skills/pkgseer/SKILL.md", useColors)}`);
|
|
4056
|
+
console.log(` Project: ${highlight(".codex/skills/pkgseer/SKILL.md", useColors)}
|
|
4057
|
+
`);
|
|
4058
|
+
console.log(`Content:
|
|
4059
|
+
`);
|
|
4060
|
+
console.log("─".repeat(60));
|
|
4061
|
+
console.log(generateSkillContent());
|
|
4062
|
+
console.log("─".repeat(60));
|
|
4063
|
+
}
|
|
4064
|
+
async function skillUpdateAction(deps) {
|
|
4065
|
+
const { fileSystemService: fs } = deps;
|
|
4066
|
+
const useColors = shouldUseColors2();
|
|
4067
|
+
console.log(`Checking for skill updates...
|
|
4068
|
+
`);
|
|
4069
|
+
const locations = [
|
|
4070
|
+
{
|
|
4071
|
+
name: "Claude Code (user)",
|
|
4072
|
+
...getClaudeCodeSkillPaths(fs, "user")
|
|
4073
|
+
},
|
|
4074
|
+
{
|
|
4075
|
+
name: "Claude Code (project)",
|
|
4076
|
+
...getClaudeCodeSkillPaths(fs, "project")
|
|
4077
|
+
},
|
|
4078
|
+
{
|
|
4079
|
+
name: "Codex (user)",
|
|
4080
|
+
...getCodexSkillPaths(fs, "user")
|
|
4081
|
+
},
|
|
4082
|
+
{
|
|
4083
|
+
name: "Codex (project)",
|
|
4084
|
+
...getCodexSkillPaths(fs, "project")
|
|
4085
|
+
}
|
|
4086
|
+
];
|
|
4087
|
+
let foundAny = false;
|
|
4088
|
+
let updatedAny = false;
|
|
4089
|
+
for (const loc of locations) {
|
|
4090
|
+
const exists = await fs.exists(loc.skillPath);
|
|
4091
|
+
if (!exists)
|
|
4092
|
+
continue;
|
|
4093
|
+
foundAny = true;
|
|
4094
|
+
const content = await fs.readFile(loc.skillPath);
|
|
4095
|
+
const existingVersion = extractSkillVersion(content);
|
|
4096
|
+
if (existingVersion === version) {
|
|
4097
|
+
console.log(`${loc.name}: ${highlight(`v${version}`, useColors)} (current)`);
|
|
4098
|
+
continue;
|
|
4099
|
+
}
|
|
4100
|
+
const result = await installSkill(fs, loc.skillDir, loc.skillPath);
|
|
4101
|
+
if (result.success) {
|
|
4102
|
+
console.log(`${loc.name}: ${dim(`v${existingVersion ?? "unknown"}`, useColors)} → ${highlight(`v${version}`, useColors)}`);
|
|
4103
|
+
updatedAny = true;
|
|
4104
|
+
} else {
|
|
4105
|
+
console.log(error(`${loc.name}: Failed to update - ${result.error}`, useColors));
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
if (!foundAny) {
|
|
4109
|
+
console.log(dim("No installed skills found.", useColors));
|
|
4110
|
+
console.log(dim(`Run 'pkgseer skill init' to install the skill.
|
|
4111
|
+
`, useColors));
|
|
4112
|
+
return;
|
|
4113
|
+
}
|
|
4114
|
+
if (updatedAny) {
|
|
4115
|
+
console.log(success(`
|
|
4116
|
+
Skills updated successfully!`, useColors));
|
|
4117
|
+
} else {
|
|
4118
|
+
console.log(dim(`
|
|
4119
|
+
All skills are up to date.`, useColors));
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
var SKILL_INIT_DESCRIPTION = `Install PkgSeer as an agent skill for AI assistants.
|
|
4123
|
+
|
|
4124
|
+
Skills provide package intelligence through natural language:
|
|
4125
|
+
• Search code and documentation across packages
|
|
4126
|
+
• Check security vulnerabilities and quality scores
|
|
4127
|
+
• Compare packages and analyze dependencies
|
|
4128
|
+
|
|
4129
|
+
Supports Claude Code and Codex CLI with user or project scope.`;
|
|
4130
|
+
var SKILL_UPDATE_DESCRIPTION = `Update installed PkgSeer skills to the latest version.
|
|
4131
|
+
|
|
4132
|
+
Checks all installed skill locations and updates any that are outdated.`;
|
|
4133
|
+
function registerSkillCommand(program) {
|
|
4134
|
+
const skillCmd = program.command("skill").summary("Manage PkgSeer agent skill").description("Install and manage PkgSeer as an agent skill for AI assistants.");
|
|
4135
|
+
skillCmd.command("init").summary("Install skill for AI assistants").description(SKILL_INIT_DESCRIPTION).action(async () => {
|
|
4136
|
+
const deps = await createContainer();
|
|
4137
|
+
await skillInitAction({
|
|
4138
|
+
fileSystemService: deps.fileSystemService,
|
|
4139
|
+
promptService: deps.promptService,
|
|
4140
|
+
shellService: deps.shellService
|
|
4141
|
+
});
|
|
4142
|
+
});
|
|
4143
|
+
skillCmd.command("update").summary("Update installed skills").description(SKILL_UPDATE_DESCRIPTION).action(async () => {
|
|
4144
|
+
const deps = await createContainer();
|
|
4145
|
+
await skillUpdateAction({
|
|
4146
|
+
fileSystemService: deps.fileSystemService,
|
|
4147
|
+
promptService: deps.promptService,
|
|
4148
|
+
shellService: deps.shellService
|
|
4149
|
+
});
|
|
4150
|
+
});
|
|
4151
|
+
}
|
|
4152
|
+
|
|
4153
|
+
// src/commands/init.ts
|
|
4154
|
+
async function initAction(options, deps) {
|
|
4155
|
+
const useColors = shouldUseColors2();
|
|
4156
|
+
console.log("Welcome to PkgSeer!");
|
|
4157
|
+
console.log(`───────────────────
|
|
4158
|
+
`);
|
|
4159
|
+
console.log(`Set up PkgSeer for your project:
|
|
3535
4160
|
` + ` 1. Project configuration – track dependencies and monitor vulnerabilities
|
|
3536
|
-
` + ` 2.
|
|
4161
|
+
` + ` 2. AI integration – enable AI assistant capabilities
|
|
3537
4162
|
`);
|
|
3538
|
-
console.log(dim(`Or run separately: pkgseer project init, pkgseer mcp init
|
|
4163
|
+
console.log(dim(`Or run separately: pkgseer project init, pkgseer skill init, pkgseer mcp init
|
|
3539
4164
|
`, useColors));
|
|
3540
4165
|
let setupProject = !options.skipProject;
|
|
3541
|
-
let
|
|
3542
|
-
if (options.skipProject && options.skipMcp) {
|
|
4166
|
+
let aiIntegration = "none";
|
|
4167
|
+
if (options.skipProject && options.skipMcp && options.skipSkill) {
|
|
3543
4168
|
showCliUsage(useColors);
|
|
3544
4169
|
return;
|
|
3545
4170
|
}
|
|
3546
|
-
|
|
3547
|
-
|
|
4171
|
+
const promptForAiIntegration = async () => {
|
|
4172
|
+
return deps.promptService.select("Which AI integration would you like?", [
|
|
4173
|
+
{
|
|
4174
|
+
value: "skill",
|
|
4175
|
+
name: "Skill (recommended for Claude Code / Codex)",
|
|
4176
|
+
description: "Lightweight file-based integration, auto-triggers on package queries"
|
|
4177
|
+
},
|
|
4178
|
+
{
|
|
4179
|
+
value: "mcp",
|
|
4180
|
+
name: "MCP server (for Cursor / advanced use)",
|
|
4181
|
+
description: "Structured tool integration via Model Context Protocol"
|
|
4182
|
+
}
|
|
4183
|
+
]);
|
|
4184
|
+
};
|
|
4185
|
+
if (options.skipMcp && !options.skipSkill) {
|
|
4186
|
+
aiIntegration = "skill";
|
|
4187
|
+
} else if (!options.skipMcp && options.skipSkill) {
|
|
4188
|
+
aiIntegration = "mcp";
|
|
4189
|
+
} else if (options.skipMcp && options.skipSkill) {
|
|
4190
|
+
aiIntegration = "none";
|
|
4191
|
+
}
|
|
4192
|
+
if (!options.skipProject && !options.skipMcp && !options.skipSkill) {
|
|
4193
|
+
const setupChoice = await deps.promptService.select("What would you like to set up?", [
|
|
3548
4194
|
{
|
|
3549
|
-
value: "
|
|
3550
|
-
name: "
|
|
3551
|
-
description: "Full setup: dependency tracking + AI assistant
|
|
4195
|
+
value: "full",
|
|
4196
|
+
name: "Project + AI integration (recommended)",
|
|
4197
|
+
description: "Full setup: dependency tracking + AI assistant capabilities"
|
|
3552
4198
|
},
|
|
3553
4199
|
{
|
|
3554
4200
|
value: "project",
|
|
@@ -3556,9 +4202,9 @@ async function initAction(options, deps) {
|
|
|
3556
4202
|
description: "Track dependencies and monitor for vulnerabilities"
|
|
3557
4203
|
},
|
|
3558
4204
|
{
|
|
3559
|
-
value: "
|
|
3560
|
-
name: "
|
|
3561
|
-
description: "Enable AI assistant
|
|
4205
|
+
value: "ai",
|
|
4206
|
+
name: "AI integration only",
|
|
4207
|
+
description: "Enable AI assistant capabilities"
|
|
3562
4208
|
},
|
|
3563
4209
|
{
|
|
3564
4210
|
value: "cli",
|
|
@@ -3566,15 +4212,21 @@ async function initAction(options, deps) {
|
|
|
3566
4212
|
description: "Use PkgSeer as a command-line tool"
|
|
3567
4213
|
}
|
|
3568
4214
|
]);
|
|
3569
|
-
if (
|
|
4215
|
+
if (setupChoice === "cli") {
|
|
3570
4216
|
showCliUsage(useColors);
|
|
3571
4217
|
return;
|
|
3572
4218
|
}
|
|
3573
|
-
setupProject =
|
|
3574
|
-
|
|
4219
|
+
setupProject = setupChoice === "full" || setupChoice === "project";
|
|
4220
|
+
const setupAi = setupChoice === "full" || setupChoice === "ai";
|
|
4221
|
+
if (setupAi) {
|
|
4222
|
+
aiIntegration = await promptForAiIntegration();
|
|
4223
|
+
}
|
|
4224
|
+
} else if (!options.skipMcp && !options.skipSkill) {
|
|
4225
|
+
aiIntegration = await promptForAiIntegration();
|
|
3575
4226
|
}
|
|
3576
4227
|
const projectInit = deps.projectInitAction ?? projectInitAction;
|
|
3577
4228
|
const mcpInit = deps.mcpInitAction ?? mcpInitAction;
|
|
4229
|
+
const skillInit = deps.skillInitAction ?? skillInitAction;
|
|
3578
4230
|
if (setupProject) {
|
|
3579
4231
|
const existingConfig = await deps.configService.loadProjectConfig();
|
|
3580
4232
|
if (existingConfig?.config.project) {
|
|
@@ -3606,7 +4258,21 @@ ${highlight("✓", useColors)} Project setup complete!
|
|
|
3606
4258
|
}
|
|
3607
4259
|
}
|
|
3608
4260
|
}
|
|
3609
|
-
if (
|
|
4261
|
+
if (aiIntegration === "skill") {
|
|
4262
|
+
console.log(`
|
|
4263
|
+
` + "=".repeat(50));
|
|
4264
|
+
console.log("Skill Setup");
|
|
4265
|
+
console.log("=".repeat(50) + `
|
|
4266
|
+
`);
|
|
4267
|
+
await skillInit({
|
|
4268
|
+
fileSystemService: deps.fileSystemService,
|
|
4269
|
+
promptService: deps.promptService,
|
|
4270
|
+
shellService: deps.shellService
|
|
4271
|
+
});
|
|
4272
|
+
console.log(`
|
|
4273
|
+
${highlight("✓", useColors)} Skill setup complete!
|
|
4274
|
+
`);
|
|
4275
|
+
} else if (aiIntegration === "mcp") {
|
|
3610
4276
|
const currentConfig = await deps.configService.loadProjectConfig();
|
|
3611
4277
|
const hasProjectNow = currentConfig?.config.project !== undefined;
|
|
3612
4278
|
console.log(`
|
|
@@ -3618,6 +4284,7 @@ ${highlight("✓", useColors)} Project setup complete!
|
|
|
3618
4284
|
fileSystemService: deps.fileSystemService,
|
|
3619
4285
|
promptService: deps.promptService,
|
|
3620
4286
|
configService: deps.configService,
|
|
4287
|
+
shellService: deps.shellService,
|
|
3621
4288
|
baseUrl: deps.baseUrl,
|
|
3622
4289
|
hasProject: hasProjectNow
|
|
3623
4290
|
});
|
|
@@ -3636,15 +4303,18 @@ ${highlight("✓", useColors)} MCP setup complete!
|
|
|
3636
4303
|
console.log(dim(` View at: ${deps.baseUrl}/projects/${finalConfig.config.project}`, useColors));
|
|
3637
4304
|
}
|
|
3638
4305
|
}
|
|
3639
|
-
if (
|
|
3640
|
-
console.log("
|
|
4306
|
+
if (aiIntegration === "skill") {
|
|
4307
|
+
console.log("AI Integration: Skill installed");
|
|
4308
|
+
console.log(dim(" The skill is available immediately.", useColors));
|
|
4309
|
+
} else if (aiIntegration === "mcp") {
|
|
4310
|
+
console.log("AI Integration: MCP server configured");
|
|
3641
4311
|
console.log(dim(" Restart your AI assistant to activate the MCP server.", useColors));
|
|
3642
4312
|
}
|
|
3643
4313
|
console.log(dim(`
|
|
3644
4314
|
Next steps:
|
|
3645
4315
|
` + ` • Use CLI commands: pkgseer pkg info <package>
|
|
3646
|
-
` + ` • Search
|
|
3647
|
-
` + " •
|
|
4316
|
+
` + ` • Search packages: pkgseer search <query> -P <packages>
|
|
4317
|
+
` + " • Search project docs: pkgseer docs search <query>", useColors));
|
|
3648
4318
|
}
|
|
3649
4319
|
function showCliUsage(useColors) {
|
|
3650
4320
|
console.log("Using PkgSeer via CLI");
|
|
@@ -3669,13 +4339,13 @@ function showCliUsage(useColors) {
|
|
|
3669
4339
|
` + "Tip: Run 'pkgseer quickstart' for a quick reference guide.", useColors));
|
|
3670
4340
|
}
|
|
3671
4341
|
function registerInitCommand(program) {
|
|
3672
|
-
program.command("init").summary("Set up project and
|
|
4342
|
+
program.command("init").summary("Set up project and AI integration").description(`Set up PkgSeer for your project.
|
|
3673
4343
|
|
|
3674
4344
|
Guides you through:
|
|
3675
4345
|
• Project configuration – track dependencies, monitor vulnerabilities
|
|
3676
|
-
• MCP server
|
|
4346
|
+
• AI integration – skill or MCP server for AI assistants
|
|
3677
4347
|
|
|
3678
|
-
Run separately: pkgseer project init, pkgseer mcp init`).option("--skip-project", "Skip project setup").option("--skip-mcp", "Skip MCP setup").action(async (options) => {
|
|
4348
|
+
Run separately: pkgseer project init, pkgseer skill init, pkgseer mcp init`).option("--skip-project", "Skip project setup").option("--skip-mcp", "Skip MCP setup (use skill instead)").option("--skip-skill", "Skip skill setup (use MCP instead)").action(async (options) => {
|
|
3679
4349
|
const deps = await createContainer();
|
|
3680
4350
|
await initAction(options, deps);
|
|
3681
4351
|
});
|
|
@@ -3912,7 +4582,7 @@ var argsSchema = {
|
|
|
3912
4582
|
function createComparePackagesTool(pkgseerService) {
|
|
3913
4583
|
return {
|
|
3914
4584
|
name: "compare_packages",
|
|
3915
|
-
description:
|
|
4585
|
+
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"}]',
|
|
3916
4586
|
schema: argsSchema,
|
|
3917
4587
|
handler: async ({ packages }, _extra) => {
|
|
3918
4588
|
return withErrorHandling("compare packages", async () => {
|
|
@@ -3933,16 +4603,47 @@ function createComparePackagesTool(pkgseerService) {
|
|
|
3933
4603
|
}
|
|
3934
4604
|
};
|
|
3935
4605
|
}
|
|
3936
|
-
// src/tools/fetch-
|
|
4606
|
+
// src/tools/fetch-code-context.ts
|
|
3937
4607
|
import { z as z4 } from "zod";
|
|
3938
4608
|
var argsSchema2 = {
|
|
3939
|
-
|
|
4609
|
+
repo_url: z4.string().min(1).describe("Repository URL (GitHub). Example: https://github.com/expressjs/express"),
|
|
4610
|
+
git_ref: z4.string().min(1).describe("Git reference (tag, commit, or branch). Example: v4.18.2"),
|
|
4611
|
+
file_path: z4.string().min(1).describe("Path to file in repository. Example: src/router/index.js"),
|
|
4612
|
+
start_line: z4.number().int().positive().optional().describe("Starting line (1-indexed). If omitted, starts from line 1."),
|
|
4613
|
+
end_line: z4.number().int().positive().optional().describe("Ending line. If omitted, returns to end of file.")
|
|
4614
|
+
};
|
|
4615
|
+
function createFetchCodeContextTool(pkgseerService) {
|
|
4616
|
+
return {
|
|
4617
|
+
name: "fetch_code_context",
|
|
4618
|
+
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.",
|
|
4619
|
+
schema: argsSchema2,
|
|
4620
|
+
handler: async ({ repo_url, git_ref, file_path, start_line, end_line }, _extra) => {
|
|
4621
|
+
return withErrorHandling("fetch code context", async () => {
|
|
4622
|
+
const result = await pkgseerService.fetchCodeContext(repo_url, git_ref, file_path, {
|
|
4623
|
+
startLine: start_line,
|
|
4624
|
+
endLine: end_line
|
|
4625
|
+
});
|
|
4626
|
+
const graphqlError = handleGraphQLErrors(result.errors);
|
|
4627
|
+
if (graphqlError)
|
|
4628
|
+
return graphqlError;
|
|
4629
|
+
if (!result.data.fetchCodeContext) {
|
|
4630
|
+
return errorResult(`Code not found: ${file_path} at ${git_ref} in ${repo_url}. ` + "Check that the repository, file path, and git ref are correct.");
|
|
4631
|
+
}
|
|
4632
|
+
return textResult(JSON.stringify(result.data.fetchCodeContext, null, 2));
|
|
4633
|
+
});
|
|
4634
|
+
}
|
|
4635
|
+
};
|
|
4636
|
+
}
|
|
4637
|
+
// src/tools/fetch-package-doc.ts
|
|
4638
|
+
import { z as z5 } from "zod";
|
|
4639
|
+
var argsSchema3 = {
|
|
4640
|
+
page_id: z5.string().max(500).describe("Globally unique documentation page identifier (from list_package_docs)")
|
|
3940
4641
|
};
|
|
3941
4642
|
function createFetchPackageDocTool(pkgseerService) {
|
|
3942
4643
|
return {
|
|
3943
4644
|
name: "fetch_package_doc",
|
|
3944
|
-
description: "
|
|
3945
|
-
schema:
|
|
4645
|
+
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.",
|
|
4646
|
+
schema: argsSchema3,
|
|
3946
4647
|
handler: async ({ page_id }, _extra) => {
|
|
3947
4648
|
return withErrorHandling("fetch documentation page", async () => {
|
|
3948
4649
|
const result = await pkgseerService.getDocPage(page_id);
|
|
@@ -3958,7 +4659,7 @@ function createFetchPackageDocTool(pkgseerService) {
|
|
|
3958
4659
|
};
|
|
3959
4660
|
}
|
|
3960
4661
|
// src/tools/list-package-docs.ts
|
|
3961
|
-
var
|
|
4662
|
+
var argsSchema4 = {
|
|
3962
4663
|
registry: schemas.registry,
|
|
3963
4664
|
package_name: schemas.packageName.describe("Name of the package to list documentation for"),
|
|
3964
4665
|
version: schemas.version
|
|
@@ -3966,8 +4667,8 @@ var argsSchema3 = {
|
|
|
3966
4667
|
function createListPackageDocsTool(pkgseerService) {
|
|
3967
4668
|
return {
|
|
3968
4669
|
name: "list_package_docs",
|
|
3969
|
-
description: "
|
|
3970
|
-
schema:
|
|
4670
|
+
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.",
|
|
4671
|
+
schema: argsSchema4,
|
|
3971
4672
|
handler: async ({ registry, package_name, version: version2 }, _extra) => {
|
|
3972
4673
|
return withErrorHandling("list package documentation", async () => {
|
|
3973
4674
|
const result = await pkgseerService.listPackageDocs(toGraphQLRegistry2(registry), package_name, version2);
|
|
@@ -3983,13 +4684,13 @@ function createListPackageDocsTool(pkgseerService) {
|
|
|
3983
4684
|
};
|
|
3984
4685
|
}
|
|
3985
4686
|
// src/tools/package-dependencies.ts
|
|
3986
|
-
import { z as
|
|
3987
|
-
var
|
|
4687
|
+
import { z as z6 } from "zod";
|
|
4688
|
+
var argsSchema5 = {
|
|
3988
4689
|
registry: schemas.registry,
|
|
3989
4690
|
package_name: schemas.packageName.describe("Name of the package to retrieve dependencies for"),
|
|
3990
4691
|
version: schemas.version,
|
|
3991
|
-
include_transitive:
|
|
3992
|
-
max_depth:
|
|
4692
|
+
include_transitive: z6.boolean().optional().describe("Whether to include transitive dependency DAG"),
|
|
4693
|
+
max_depth: z6.number().int().min(1).max(10).optional().describe("Maximum depth for transitive traversal (1-10)")
|
|
3993
4694
|
};
|
|
3994
4695
|
function decodeDag(rawDag) {
|
|
3995
4696
|
if (!rawDag || typeof rawDag !== "object")
|
|
@@ -4066,8 +4767,8 @@ function buildEdgeDepths(decodedDag, rootId) {
|
|
|
4066
4767
|
function createPackageDependenciesTool(pkgseerService) {
|
|
4067
4768
|
return {
|
|
4068
4769
|
name: "package_dependencies",
|
|
4069
|
-
description: "
|
|
4070
|
-
schema:
|
|
4770
|
+
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.",
|
|
4771
|
+
schema: argsSchema5,
|
|
4071
4772
|
handler: async ({ registry, package_name, version: version2, include_transitive, max_depth }, _extra) => {
|
|
4072
4773
|
return withErrorHandling("fetch package dependencies", async () => {
|
|
4073
4774
|
const result = await pkgseerService.getPackageDependencies(toGraphQLRegistry2(registry), package_name, version2, include_transitive, max_depth);
|
|
@@ -4105,7 +4806,7 @@ function createPackageDependenciesTool(pkgseerService) {
|
|
|
4105
4806
|
};
|
|
4106
4807
|
}
|
|
4107
4808
|
// src/tools/package-quality.ts
|
|
4108
|
-
var
|
|
4809
|
+
var argsSchema6 = {
|
|
4109
4810
|
registry: schemas.registry,
|
|
4110
4811
|
package_name: schemas.packageName.describe("Name of the package to analyze"),
|
|
4111
4812
|
version: schemas.version
|
|
@@ -4113,8 +4814,8 @@ var argsSchema5 = {
|
|
|
4113
4814
|
function createPackageQualityTool(pkgseerService) {
|
|
4114
4815
|
return {
|
|
4115
4816
|
name: "package_quality",
|
|
4116
|
-
description: "
|
|
4117
|
-
schema:
|
|
4817
|
+
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.",
|
|
4818
|
+
schema: argsSchema6,
|
|
4118
4819
|
handler: async ({ registry, package_name, version: version2 }, _extra) => {
|
|
4119
4820
|
return withErrorHandling("fetch package quality", async () => {
|
|
4120
4821
|
const result = await pkgseerService.getPackageQuality(toGraphQLRegistry2(registry), package_name, version2);
|
|
@@ -4130,15 +4831,15 @@ function createPackageQualityTool(pkgseerService) {
|
|
|
4130
4831
|
};
|
|
4131
4832
|
}
|
|
4132
4833
|
// src/tools/package-summary.ts
|
|
4133
|
-
var
|
|
4834
|
+
var argsSchema7 = {
|
|
4134
4835
|
registry: schemas.registry,
|
|
4135
4836
|
package_name: schemas.packageName.describe("Name of the package to retrieve summary for")
|
|
4136
4837
|
};
|
|
4137
4838
|
function createPackageSummaryTool(pkgseerService) {
|
|
4138
4839
|
return {
|
|
4139
4840
|
name: "package_summary",
|
|
4140
|
-
description: "
|
|
4141
|
-
schema:
|
|
4841
|
+
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).",
|
|
4842
|
+
schema: argsSchema7,
|
|
4142
4843
|
handler: async ({ registry, package_name }, _extra) => {
|
|
4143
4844
|
return withErrorHandling("fetch package summary", async () => {
|
|
4144
4845
|
const result = await pkgseerService.getPackageSummary(toGraphQLRegistry2(registry), package_name);
|
|
@@ -4154,7 +4855,7 @@ function createPackageSummaryTool(pkgseerService) {
|
|
|
4154
4855
|
};
|
|
4155
4856
|
}
|
|
4156
4857
|
// src/tools/package-vulnerabilities.ts
|
|
4157
|
-
var
|
|
4858
|
+
var argsSchema8 = {
|
|
4158
4859
|
registry: schemas.registry,
|
|
4159
4860
|
package_name: schemas.packageName.describe("Name of the package to inspect for vulnerabilities"),
|
|
4160
4861
|
version: schemas.version
|
|
@@ -4162,8 +4863,8 @@ var argsSchema7 = {
|
|
|
4162
4863
|
function createPackageVulnerabilitiesTool(pkgseerService) {
|
|
4163
4864
|
return {
|
|
4164
4865
|
name: "package_vulnerabilities",
|
|
4165
|
-
description: "
|
|
4166
|
-
schema:
|
|
4866
|
+
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.",
|
|
4867
|
+
schema: argsSchema8,
|
|
4167
4868
|
handler: async ({ registry, package_name, version: version2 }, _extra) => {
|
|
4168
4869
|
return withErrorHandling("fetch package vulnerabilities", async () => {
|
|
4169
4870
|
const result = await pkgseerService.getPackageVulnerabilities(toGraphQLRegistry2(registry), package_name, version2);
|
|
@@ -4178,79 +4879,126 @@ function createPackageVulnerabilitiesTool(pkgseerService) {
|
|
|
4178
4879
|
}
|
|
4179
4880
|
};
|
|
4180
4881
|
}
|
|
4181
|
-
// src/tools/search
|
|
4182
|
-
import { z as
|
|
4183
|
-
var
|
|
4882
|
+
// src/tools/search.ts
|
|
4883
|
+
import { z as z7 } from "zod";
|
|
4884
|
+
var packageInputSchema2 = z7.object({
|
|
4184
4885
|
registry: schemas.registry,
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4886
|
+
name: z7.string().min(1).describe("Package name"),
|
|
4887
|
+
version: z7.string().optional().describe("Specific version (defaults to latest)")
|
|
4888
|
+
});
|
|
4889
|
+
var argsSchema9 = {
|
|
4890
|
+
packages: z7.array(packageInputSchema2).min(1).max(20).describe("Packages to search (1-20). Each package needs registry and name."),
|
|
4891
|
+
query: z7.string().min(1).describe("Search query - natural language or keywords"),
|
|
4892
|
+
mode: z7.enum(["all", "code", "docs"]).optional().describe('Search mode: "all" (default), "code" only, or "docs" only'),
|
|
4893
|
+
limit: z7.number().int().min(1).max(100).optional().describe("Maximum results (default: 20)")
|
|
4191
4894
|
};
|
|
4192
|
-
function
|
|
4895
|
+
function toSearchMode2(mode) {
|
|
4896
|
+
if (!mode)
|
|
4897
|
+
return;
|
|
4898
|
+
const map = {
|
|
4899
|
+
all: "ALL",
|
|
4900
|
+
code: "CODE",
|
|
4901
|
+
docs: "DOCS"
|
|
4902
|
+
};
|
|
4903
|
+
return map[mode.toLowerCase()];
|
|
4904
|
+
}
|
|
4905
|
+
function formatIndexingWarnings2(indexingStatus) {
|
|
4906
|
+
if (!indexingStatus || indexingStatus.length === 0)
|
|
4907
|
+
return null;
|
|
4908
|
+
const warnings = [];
|
|
4909
|
+
for (const status of indexingStatus) {
|
|
4910
|
+
if (!status)
|
|
4911
|
+
continue;
|
|
4912
|
+
const pkg = `${status.packageName}@${status.version ?? "latest"} (${status.registry?.toLowerCase() ?? "unknown"})`;
|
|
4913
|
+
if (status.codeStatus && status.codeStatus !== "INDEXED") {
|
|
4914
|
+
warnings.push(` - ${pkg}: code ${status.codeStatus.toLowerCase()}`);
|
|
4915
|
+
}
|
|
4916
|
+
if (status.docsStatus && status.docsStatus !== "INDEXED") {
|
|
4917
|
+
warnings.push(` - ${pkg}: docs ${status.docsStatus.toLowerCase()}`);
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
if (warnings.length === 0)
|
|
4921
|
+
return null;
|
|
4922
|
+
return `
|
|
4923
|
+
Note: Some packages are not fully indexed:
|
|
4924
|
+
` + warnings.join(`
|
|
4925
|
+
`) + `
|
|
4926
|
+
Results may be incomplete. Retry later for more complete results.`;
|
|
4927
|
+
}
|
|
4928
|
+
function createSearchTool(pkgseerService) {
|
|
4193
4929
|
return {
|
|
4194
|
-
name: "
|
|
4195
|
-
description: "
|
|
4196
|
-
schema:
|
|
4197
|
-
handler: async ({
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
include_snippets,
|
|
4203
|
-
limit,
|
|
4204
|
-
version: version2
|
|
4205
|
-
}, _extra) => {
|
|
4206
|
-
return withErrorHandling("search package documentation", async () => {
|
|
4207
|
-
const normalizedTerms = terms?.map((term) => term.trim()).filter((term) => term.length > 0) ?? [];
|
|
4208
|
-
if (normalizedTerms.length === 0) {
|
|
4209
|
-
return errorResult("Search terms are required to run documentation search.");
|
|
4930
|
+
name: "search",
|
|
4931
|
+
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.",
|
|
4932
|
+
schema: argsSchema9,
|
|
4933
|
+
handler: async ({ packages, query, mode, limit }, _extra) => {
|
|
4934
|
+
return withErrorHandling("search packages", async () => {
|
|
4935
|
+
const normalizedQuery = query.trim();
|
|
4936
|
+
if (normalizedQuery.length === 0) {
|
|
4937
|
+
return errorResult("Search query is required.");
|
|
4210
4938
|
}
|
|
4211
|
-
const
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4939
|
+
const graphqlPackages = packages.map((pkg) => ({
|
|
4940
|
+
registry: toGraphQLRegistry2(pkg.registry),
|
|
4941
|
+
name: pkg.name,
|
|
4942
|
+
version: pkg.version
|
|
4943
|
+
}));
|
|
4944
|
+
const result = await pkgseerService.combinedSearch(graphqlPackages, normalizedQuery, {
|
|
4945
|
+
mode: toSearchMode2(mode),
|
|
4946
|
+
limit
|
|
4219
4947
|
});
|
|
4220
4948
|
const graphqlError = handleGraphQLErrors(result.errors);
|
|
4221
4949
|
if (graphqlError)
|
|
4222
4950
|
return graphqlError;
|
|
4223
|
-
if (!result.data.
|
|
4224
|
-
return errorResult(
|
|
4951
|
+
if (!result.data.combinedSearch) {
|
|
4952
|
+
return errorResult("No search results returned. Check package names and registries.");
|
|
4225
4953
|
}
|
|
4226
|
-
|
|
4227
|
-
|
|
4954
|
+
const searchResult = result.data.combinedSearch;
|
|
4955
|
+
const entries = searchResult.entries ?? [];
|
|
4956
|
+
if (entries.length === 0) {
|
|
4957
|
+
return errorResult(`No results found for "${normalizedQuery}". ` + "Try broader terms or different packages.");
|
|
4958
|
+
}
|
|
4959
|
+
let response = JSON.stringify(searchResult, null, 2);
|
|
4960
|
+
const indexingWarning = formatIndexingWarnings2(searchResult.indexingStatus);
|
|
4961
|
+
if (indexingWarning) {
|
|
4962
|
+
response += indexingWarning;
|
|
4228
4963
|
}
|
|
4229
|
-
return textResult(
|
|
4964
|
+
return textResult(response);
|
|
4230
4965
|
});
|
|
4231
4966
|
}
|
|
4232
4967
|
};
|
|
4233
4968
|
}
|
|
4234
4969
|
// src/tools/search-project-docs.ts
|
|
4235
|
-
import { z as
|
|
4236
|
-
var
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4970
|
+
import { z as z8 } from "zod";
|
|
4971
|
+
var argsSchema10 = {
|
|
4972
|
+
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."),
|
|
4973
|
+
project: z8.string().optional().describe("Project name to search. Overrides value from pkgseer.yml if provided."),
|
|
4974
|
+
terms: z8.array(z8.string()).optional().describe("Search terms to match. Provide a few key words or phrases that should appear in results."),
|
|
4975
|
+
match_mode: z8.enum(["any", "or", "all", "and"]).optional().describe('How to combine terms: "any" (OR) or "all" (AND).'),
|
|
4976
|
+
include_snippets: z8.boolean().optional().describe("Include content excerpts around matches"),
|
|
4977
|
+
limit: z8.number().int().min(1).max(100).optional().describe("Maximum number of results to return")
|
|
4242
4978
|
};
|
|
4243
4979
|
function createSearchProjectDocsTool(deps) {
|
|
4244
|
-
const { pkgseerService,
|
|
4980
|
+
const { pkgseerService, configService, defaultProjectDir } = deps;
|
|
4245
4981
|
return {
|
|
4246
4982
|
name: "search_project_docs",
|
|
4247
|
-
description: "Searches documentation across all dependencies in a PkgSeer project using search terms and match modes (any/all). Returns ranked results from multiple packages.
|
|
4248
|
-
schema:
|
|
4249
|
-
handler: async ({
|
|
4983
|
+
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.",
|
|
4984
|
+
schema: argsSchema10,
|
|
4985
|
+
handler: async ({
|
|
4986
|
+
project_directory,
|
|
4987
|
+
project,
|
|
4988
|
+
terms,
|
|
4989
|
+
match_mode,
|
|
4990
|
+
include_snippets,
|
|
4991
|
+
limit
|
|
4992
|
+
}, _extra) => {
|
|
4250
4993
|
return withErrorHandling("search project documentation", async () => {
|
|
4251
|
-
const
|
|
4994
|
+
const targetDir = project_directory ?? defaultProjectDir;
|
|
4995
|
+
const projectConfig = await configService.loadProjectConfigFrom(targetDir);
|
|
4996
|
+
const resolvedProject = project ?? projectConfig?.config.project;
|
|
4252
4997
|
if (!resolvedProject) {
|
|
4253
|
-
return errorResult(
|
|
4998
|
+
return errorResult(`No project specified and no pkgseer.yml found. To fix:
|
|
4999
|
+
` + ` 1. Pass project_directory: '/path/to/project' (folder containing pkgseer.yml)
|
|
5000
|
+
` + ` 2. Pass project: 'project-name' directly if you know the project name
|
|
5001
|
+
` + " 3. Run 'pkgseer project init' to create pkgseer.yml in your project");
|
|
4254
5002
|
}
|
|
4255
5003
|
const normalizedTerms = terms?.map((term) => term.trim()).filter((term) => term.length > 0) ?? [];
|
|
4256
5004
|
if (normalizedTerms.length === 0) {
|
|
@@ -4287,8 +5035,13 @@ var TOOL_FACTORIES = {
|
|
|
4287
5035
|
compare_packages: ({ pkgseerService }) => createComparePackagesTool(pkgseerService),
|
|
4288
5036
|
list_package_docs: ({ pkgseerService }) => createListPackageDocsTool(pkgseerService),
|
|
4289
5037
|
fetch_package_doc: ({ pkgseerService }) => createFetchPackageDocTool(pkgseerService),
|
|
4290
|
-
|
|
4291
|
-
|
|
5038
|
+
search: ({ pkgseerService }) => createSearchTool(pkgseerService),
|
|
5039
|
+
fetch_code_context: ({ pkgseerService }) => createFetchCodeContextTool(pkgseerService),
|
|
5040
|
+
search_project_docs: ({ pkgseerService, configService, defaultProjectDir }) => createSearchProjectDocsTool({
|
|
5041
|
+
pkgseerService,
|
|
5042
|
+
configService,
|
|
5043
|
+
defaultProjectDir
|
|
5044
|
+
})
|
|
4292
5045
|
};
|
|
4293
5046
|
var PUBLIC_READ_TOOLS = [
|
|
4294
5047
|
"package_summary",
|
|
@@ -4298,21 +5051,29 @@ var PUBLIC_READ_TOOLS = [
|
|
|
4298
5051
|
"compare_packages",
|
|
4299
5052
|
"list_package_docs",
|
|
4300
5053
|
"fetch_package_doc",
|
|
4301
|
-
"
|
|
5054
|
+
"search",
|
|
5055
|
+
"fetch_code_context"
|
|
4302
5056
|
];
|
|
4303
5057
|
var PROJECT_READ_TOOLS = ["search_project_docs"];
|
|
4304
5058
|
var ALL_TOOLS = [...PUBLIC_READ_TOOLS, ...PROJECT_READ_TOOLS];
|
|
4305
5059
|
function createMcpServer(deps) {
|
|
4306
|
-
const { pkgseerService, config } = deps;
|
|
5060
|
+
const { pkgseerService, configService, config, fileSystemService } = deps;
|
|
4307
5061
|
const server = new McpServer({
|
|
4308
5062
|
name: "pkgseer",
|
|
4309
5063
|
version: "0.1.0"
|
|
4310
5064
|
});
|
|
5065
|
+
const defaultProjectDir = fileSystemService.getCwd();
|
|
4311
5066
|
const enabledToolNames = config.enabled_tools ?? ALL_TOOLS;
|
|
4312
5067
|
const toolsToRegister = enabledToolNames.filter((name) => ALL_TOOLS.includes(name));
|
|
5068
|
+
const factoryDeps = {
|
|
5069
|
+
pkgseerService,
|
|
5070
|
+
configService,
|
|
5071
|
+
config,
|
|
5072
|
+
defaultProjectDir
|
|
5073
|
+
};
|
|
4313
5074
|
for (const toolName of toolsToRegister) {
|
|
4314
5075
|
const factory = TOOL_FACTORIES[toolName];
|
|
4315
|
-
const tool = factory(
|
|
5076
|
+
const tool = factory(factoryDeps);
|
|
4316
5077
|
server.registerTool(tool.name, { description: tool.description, inputSchema: tool.schema }, tool.handler);
|
|
4317
5078
|
}
|
|
4318
5079
|
return server;
|
|
@@ -5259,103 +6020,13 @@ function registerProjectUploadCommand(program) {
|
|
|
5259
6020
|
});
|
|
5260
6021
|
});
|
|
5261
6022
|
}
|
|
5262
|
-
// src/commands/quickstart.ts
|
|
5263
|
-
var QUICKSTART_TEXT = `# PkgSeer CLI - Quick Reference for AI Agents
|
|
5264
|
-
|
|
5265
|
-
PkgSeer provides package intelligence for npm, PyPI, and Hex registries.
|
|
5266
|
-
|
|
5267
|
-
## Package Commands (pkgseer pkg)
|
|
5268
|
-
|
|
5269
|
-
### Get package info
|
|
5270
|
-
\`pkgseer pkg info <package> [-r npm|pypi|hex] [--json]\`
|
|
5271
|
-
Returns: metadata, versions, security advisories, quickstart
|
|
5272
|
-
|
|
5273
|
-
### Check quality score
|
|
5274
|
-
\`pkgseer pkg quality <package> [-r registry] [-v pkg-version] [--json]\`
|
|
5275
|
-
Returns: overall score, category breakdown, rule details
|
|
5276
|
-
|
|
5277
|
-
### List dependencies
|
|
5278
|
-
\`pkgseer pkg deps <package> [-r registry] [-v pkg-version] [-t] [-d depth] [--json]\`
|
|
5279
|
-
Returns: direct deps, transitive count, dependency tree (with -t)
|
|
5280
|
-
|
|
5281
|
-
### Check vulnerabilities
|
|
5282
|
-
\`pkgseer pkg vulns <package> [-r registry] [-v pkg-version] [--json]\`
|
|
5283
|
-
Returns: CVEs, severity, affected versions, upgrade guidance
|
|
5284
|
-
|
|
5285
|
-
### Compare packages
|
|
5286
|
-
\`pkgseer pkg compare <pkg1> <pkg2> [...] [--json]\`
|
|
5287
|
-
Format: [registry:]name[@version] (e.g., npm:lodash, pypi:requests@2.31.0)
|
|
5288
|
-
Returns: side-by-side quality, downloads, vulnerabilities
|
|
5289
|
-
|
|
5290
|
-
## Documentation Commands (pkgseer docs)
|
|
5291
|
-
|
|
5292
|
-
### List doc pages
|
|
5293
|
-
\`pkgseer docs list <package> [-r registry] [-v pkg-version] [--json]\`
|
|
5294
|
-
Returns: page titles, IDs (slugs), word counts
|
|
5295
|
-
|
|
5296
|
-
### Get doc page
|
|
5297
|
-
\`pkgseer docs get <package>/<page-id> [<package>/<page-id> ...] [-r registry] [-v pkg-version] [--json]\`
|
|
5298
|
-
Returns: full page content (markdown), breadcrumbs, source URL
|
|
5299
|
-
Supports multiple pages: \`pkgseer docs get express/readme express/api\`
|
|
5300
|
-
|
|
5301
|
-
### Search docs
|
|
5302
|
-
\`pkgseer docs search "<query>" [-p package] [-r registry] [--json]\`
|
|
5303
|
-
\`pkgseer docs search --keywords term1,term2 [-s] [-l limit]\`
|
|
5304
|
-
Default: searches project docs (if project configured)
|
|
5305
|
-
With -p: searches specific package docs
|
|
5306
|
-
Returns: ranked results with relevance scores
|
|
5307
|
-
|
|
5308
|
-
## Tips for AI Agents
|
|
5309
|
-
|
|
5310
|
-
1. Use \`--json\` for structured data parsing
|
|
5311
|
-
2. Default registry is npm; use \`-r pypi\` or \`-r hex\` for others
|
|
5312
|
-
3. Version defaults to latest; use \`-v <version>\` for specific version
|
|
5313
|
-
4. For docs search, configure project in pkgseer.yml for project-wide search
|
|
5314
|
-
5. Compare up to 10 packages at once with \`pkg compare\`
|
|
5315
|
-
|
|
5316
|
-
## MCP Server
|
|
5317
|
-
|
|
5318
|
-
For Model Context Protocol integration:
|
|
5319
|
-
\`pkgseer mcp\`
|
|
5320
|
-
|
|
5321
|
-
Add to MCP config:
|
|
5322
|
-
{
|
|
5323
|
-
"pkgseer": {
|
|
5324
|
-
"command": "pkgseer",
|
|
5325
|
-
"args": ["mcp"]
|
|
5326
|
-
}
|
|
5327
|
-
}
|
|
5328
|
-
`;
|
|
5329
|
-
function quickstartAction(options) {
|
|
5330
|
-
if (options.json) {
|
|
5331
|
-
console.log(JSON.stringify({
|
|
5332
|
-
version,
|
|
5333
|
-
quickstart: QUICKSTART_TEXT
|
|
5334
|
-
}));
|
|
5335
|
-
} else {
|
|
5336
|
-
console.log(QUICKSTART_TEXT);
|
|
5337
|
-
}
|
|
5338
|
-
}
|
|
5339
|
-
var QUICKSTART_DESCRIPTION = `Show quick reference for AI agents.
|
|
5340
|
-
|
|
5341
|
-
Displays a concise guide to pkgseer CLI commands optimized
|
|
5342
|
-
for LLM agents. Includes command syntax, options, and tips
|
|
5343
|
-
for effective usage.
|
|
5344
|
-
|
|
5345
|
-
Use --json for structured output.`;
|
|
5346
|
-
function registerQuickstartCommand(program) {
|
|
5347
|
-
program.command("quickstart").summary("Show quick reference for AI agents").description(QUICKSTART_DESCRIPTION).option("--json", "Output as JSON").action((options) => {
|
|
5348
|
-
quickstartAction(options);
|
|
5349
|
-
});
|
|
5350
|
-
}
|
|
5351
|
-
|
|
5352
6023
|
// src/cli.ts
|
|
5353
6024
|
var program = new Command;
|
|
5354
6025
|
program.name("pkgseer").description("Package intelligence for your AI assistant").version(version).addHelpText("after", `
|
|
5355
6026
|
Getting started:
|
|
5356
|
-
pkgseer init Interactive setup wizard (project +
|
|
6027
|
+
pkgseer init Interactive setup wizard (project + AI)
|
|
5357
6028
|
pkgseer login Authenticate with your account
|
|
5358
|
-
pkgseer
|
|
6029
|
+
pkgseer skill init Install AI agent skill
|
|
5359
6030
|
|
|
5360
6031
|
Package commands:
|
|
5361
6032
|
pkgseer pkg info <package> Get package summary
|
|
@@ -5372,9 +6043,9 @@ Documentation commands:
|
|
|
5372
6043
|
Learn more at https://pkgseer.dev`);
|
|
5373
6044
|
registerInitCommand(program);
|
|
5374
6045
|
registerMcpCommand(program);
|
|
6046
|
+
registerSkillCommand(program);
|
|
5375
6047
|
registerLoginCommand(program);
|
|
5376
6048
|
registerLogoutCommand(program);
|
|
5377
|
-
registerQuickstartCommand(program);
|
|
5378
6049
|
var auth = program.command("auth").description("View and manage authentication");
|
|
5379
6050
|
registerAuthStatusCommand(auth);
|
|
5380
6051
|
var config = program.command("config").description("View and manage configuration");
|