githits 0.4.2 → 0.4.4
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.plugin/plugin.json +1 -1
- package/README.md +43 -17
- package/dist/cli.js +1185 -397
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-jdygt0ra.js → chunk-96tjsv6y.js} +1 -1
- package/dist/shared/chunk-k35egwwf.js +15 -0
- package/dist/shared/{chunk-v8sths32.js → chunk-tfqxat16.js} +188 -59
- package/gemini-extension.json +1 -1
- package/package.json +4 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/dist/shared/chunk-q2mre780.js +0 -11
package/dist/cli.js
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
PackageIntelligenceValidationError,
|
|
33
33
|
PackageIntelligenceVersionNotFoundError,
|
|
34
34
|
PromptServiceImpl,
|
|
35
|
+
clearAutoLoginAuthSessionMetadata,
|
|
35
36
|
createAuthCommandDependencies,
|
|
36
37
|
createAuthStatusDependencies,
|
|
37
38
|
createContainer,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
formatUpdateNotice,
|
|
43
44
|
getCodeNavigationUrl,
|
|
44
45
|
isTelemetryEnabled,
|
|
46
|
+
loadAutoLoginAuthSessionMetadata,
|
|
45
47
|
refreshExpiredToken,
|
|
46
48
|
setClientMode,
|
|
47
49
|
setMcpClientVersionProvider,
|
|
@@ -49,11 +51,11 @@ import {
|
|
|
49
51
|
shouldRunUpdateCheck,
|
|
50
52
|
startTelemetrySpan,
|
|
51
53
|
withTelemetrySpan
|
|
52
|
-
} from "./shared/chunk-
|
|
54
|
+
} from "./shared/chunk-tfqxat16.js";
|
|
53
55
|
import {
|
|
54
56
|
__require,
|
|
55
57
|
version
|
|
56
|
-
} from "./shared/chunk-
|
|
58
|
+
} from "./shared/chunk-96tjsv6y.js";
|
|
57
59
|
|
|
58
60
|
// src/cli.ts
|
|
59
61
|
import { Command } from "commander";
|
|
@@ -914,7 +916,7 @@ var GREP_REPO_PATTERN_NOTE = "Text grep over indexed source files. `literal` (de
|
|
|
914
916
|
function buildGrepRepoParams(input) {
|
|
915
917
|
const pattern = input.pattern ?? "";
|
|
916
918
|
if (pattern.length === 0 || pattern.trim().length === 0) {
|
|
917
|
-
throw new InvalidPackageSpecError("`pattern` is required — pass the text to search for.");
|
|
919
|
+
throw new InvalidPackageSpecError("`pattern` is required — pass the text to search for. If you are trying to list files or count files in scope, use `code_files` instead.");
|
|
918
920
|
}
|
|
919
921
|
if (Buffer.byteLength(pattern, "utf8") > PATTERN_MAX) {
|
|
920
922
|
throw new InvalidPackageSpecError(`\`pattern\` must be ≤ ${PATTERN_MAX} UTF-8 bytes.`);
|
|
@@ -1057,7 +1059,13 @@ function normalizeWaitTimeoutMs(value) {
|
|
|
1057
1059
|
}
|
|
1058
1060
|
return value;
|
|
1059
1061
|
}
|
|
1062
|
+
// src/shared/shell-quote.ts
|
|
1063
|
+
function shellQuote(value) {
|
|
1064
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1060
1067
|
// src/shared/grep-repo-response.ts
|
|
1068
|
+
var UTF8_ENCODER = new TextEncoder;
|
|
1061
1069
|
function buildGrepRepoSuccessPayload(result, options) {
|
|
1062
1070
|
const envelope = {
|
|
1063
1071
|
pattern: options.pattern,
|
|
@@ -1369,7 +1377,7 @@ function renderVerboseLine(line, gutterWidth, useColors) {
|
|
|
1369
1377
|
if (line.symbolHint) {
|
|
1370
1378
|
const hintIndent = " ".repeat(2 + gutterWidth + 2);
|
|
1371
1379
|
return `${matchRow}
|
|
1372
|
-
${hintIndent}${dim(
|
|
1380
|
+
${hintIndent}${dim(`in: ${line.symbolHint}`, useColors)}`;
|
|
1373
1381
|
}
|
|
1374
1382
|
return matchRow;
|
|
1375
1383
|
}
|
|
@@ -1388,8 +1396,9 @@ function formatSymbolHint(symbol) {
|
|
|
1388
1396
|
parts.push("public");
|
|
1389
1397
|
if (symbol.arity !== undefined)
|
|
1390
1398
|
parts.push(`arity=${symbol.arity}`);
|
|
1391
|
-
if (symbol.callerCount !== undefined)
|
|
1399
|
+
if (symbol.callerCount !== undefined) {
|
|
1392
1400
|
parts.push(`callers=${symbol.callerCount}`);
|
|
1401
|
+
}
|
|
1393
1402
|
if (symbol.startLine !== undefined && symbol.endLine !== undefined) {
|
|
1394
1403
|
parts.push(`L${symbol.startLine}-${symbol.endLine}`);
|
|
1395
1404
|
}
|
|
@@ -1426,9 +1435,6 @@ function shouldSuggestNarrowingScope(envelope) {
|
|
|
1426
1435
|
function formatCount(count, singular, plural = `${singular}s`) {
|
|
1427
1436
|
return `${count} ${count === 1 ? singular : plural}`;
|
|
1428
1437
|
}
|
|
1429
|
-
function shellQuote(value) {
|
|
1430
|
-
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
1431
|
-
}
|
|
1432
1438
|
function padLeft(text, width) {
|
|
1433
1439
|
return text.length >= width ? text : `${" ".repeat(width - text.length)}${text}`;
|
|
1434
1440
|
}
|
|
@@ -1449,7 +1455,21 @@ function mergeRanges(existing, incoming) {
|
|
|
1449
1455
|
return merged;
|
|
1450
1456
|
}
|
|
1451
1457
|
function clampCharacterOffset(text, offset) {
|
|
1452
|
-
|
|
1458
|
+
if (offset <= 0)
|
|
1459
|
+
return 0;
|
|
1460
|
+
let byteOffset = 0;
|
|
1461
|
+
for (let index = 0;index < text.length; ) {
|
|
1462
|
+
const codePoint = text.codePointAt(index);
|
|
1463
|
+
if (codePoint === undefined)
|
|
1464
|
+
break;
|
|
1465
|
+
const char = String.fromCodePoint(codePoint);
|
|
1466
|
+
const nextByteOffset = byteOffset + UTF8_ENCODER.encode(char).length;
|
|
1467
|
+
if (nextByteOffset > offset)
|
|
1468
|
+
return index;
|
|
1469
|
+
byteOffset = nextByteOffset;
|
|
1470
|
+
index += char.length;
|
|
1471
|
+
}
|
|
1472
|
+
return text.length;
|
|
1453
1473
|
}
|
|
1454
1474
|
// src/shared/grep-repo-text.ts
|
|
1455
1475
|
var SEP = " | ";
|
|
@@ -1509,33 +1529,8 @@ function buildHeader(envelope) {
|
|
|
1509
1529
|
flags.push("case-sensitive");
|
|
1510
1530
|
if (flags.length > 0)
|
|
1511
1531
|
parts.push(flags.join(","));
|
|
1512
|
-
const filterEcho = buildFilterEcho(envelope);
|
|
1513
|
-
if (filterEcho)
|
|
1514
|
-
parts.push(filterEcho);
|
|
1515
1532
|
return parts.join(SEP);
|
|
1516
1533
|
}
|
|
1517
|
-
function buildFilterEcho(envelope) {
|
|
1518
|
-
const filter = envelope.filter;
|
|
1519
|
-
if (!filter)
|
|
1520
|
-
return "";
|
|
1521
|
-
const parts = [];
|
|
1522
|
-
if (filter.path)
|
|
1523
|
-
parts.push(`path=${quote2(filter.path)}`);
|
|
1524
|
-
if (filter.pathPrefix)
|
|
1525
|
-
parts.push(`path_prefix=${quote2(filter.pathPrefix)}`);
|
|
1526
|
-
if (filter.globs?.length)
|
|
1527
|
-
parts.push(`globs=${filter.globs.join(",")}`);
|
|
1528
|
-
if (filter.extensions?.length) {
|
|
1529
|
-
parts.push(`exts=${filter.extensions.join(",")}`);
|
|
1530
|
-
}
|
|
1531
|
-
if (typeof filter.maxMatches === "number") {
|
|
1532
|
-
parts.push(`max_matches=${filter.maxMatches}`);
|
|
1533
|
-
}
|
|
1534
|
-
if (typeof filter.maxMatchesPerFile === "number") {
|
|
1535
|
-
parts.push(`max_matches_per_file=${filter.maxMatchesPerFile}`);
|
|
1536
|
-
}
|
|
1537
|
-
return parts.join(" ");
|
|
1538
|
-
}
|
|
1539
1534
|
function buildTrailer(envelope) {
|
|
1540
1535
|
const lines = [];
|
|
1541
1536
|
if (envelope.truncatedReason) {
|
|
@@ -1701,7 +1696,7 @@ function buildHeader2(envelope) {
|
|
|
1701
1696
|
];
|
|
1702
1697
|
if (identity)
|
|
1703
1698
|
parts.push(identity);
|
|
1704
|
-
const filter =
|
|
1699
|
+
const filter = buildFilterEcho(envelope);
|
|
1705
1700
|
if (filter)
|
|
1706
1701
|
parts.push(filter);
|
|
1707
1702
|
return parts.join(SEP2);
|
|
@@ -1716,7 +1711,7 @@ function buildIdentity(envelope) {
|
|
|
1716
1711
|
}
|
|
1717
1712
|
return "";
|
|
1718
1713
|
}
|
|
1719
|
-
function
|
|
1714
|
+
function buildFilterEcho(envelope) {
|
|
1720
1715
|
const parts = [];
|
|
1721
1716
|
if (envelope.filter?.path) {
|
|
1722
1717
|
parts.push(`path=${quote3(envelope.filter.path)}`);
|
|
@@ -1910,6 +1905,8 @@ function formatListPackageDocsTerminal(envelope, options) {
|
|
|
1910
1905
|
lines.push(...meta);
|
|
1911
1906
|
lines.push("");
|
|
1912
1907
|
}
|
|
1908
|
+
lines.push(dim("Read a page: githits docs read '<pageId>'", options.useColors));
|
|
1909
|
+
lines.push("");
|
|
1913
1910
|
if (envelope.nextCursor) {
|
|
1914
1911
|
lines.push(dim(`Next cursor: ${envelope.nextCursor}`, options.useColors));
|
|
1915
1912
|
}
|
|
@@ -1923,7 +1920,7 @@ function formatListPackageDocsTerminal(envelope, options) {
|
|
|
1923
1920
|
}
|
|
1924
1921
|
function buildSummaryHeader(envelope, useColors) {
|
|
1925
1922
|
const target = envelope.registry && envelope.name ? `${envelope.registry}:${envelope.name}${envelope.version ? `@${envelope.version}` : ""}` : "package docs";
|
|
1926
|
-
const summary = `${target}
|
|
1923
|
+
const summary = `${target} | ${envelope.pages.length} page${envelope.pages.length === 1 ? "" : "s"}`;
|
|
1927
1924
|
const suffix = envelope.total !== undefined ? ` of ${envelope.total}` : "";
|
|
1928
1925
|
return `${colorize(summary, "bold", useColors)}${dim(suffix, useColors)}`;
|
|
1929
1926
|
}
|
|
@@ -2022,7 +2019,7 @@ var SUPPORTED_DEPS_REGISTRIES = new Set([
|
|
|
2022
2019
|
"RUBYGEMS",
|
|
2023
2020
|
"GO"
|
|
2024
2021
|
]);
|
|
2025
|
-
var
|
|
2022
|
+
var SUPPORTED_DEPS_REGISTRIES_LIST = PKGSEER_REGISTRY_ARGS.filter((arg) => SUPPORTED_DEPS_REGISTRIES.has(toPkgseerRegistry(arg))).join(", ");
|
|
2026
2023
|
function supportsDependenciesRegistry(registry) {
|
|
2027
2024
|
return SUPPORTED_DEPS_REGISTRIES.has(registry);
|
|
2028
2025
|
}
|
|
@@ -2037,7 +2034,7 @@ function buildPackageDependenciesParams(input) {
|
|
|
2037
2034
|
}
|
|
2038
2035
|
const registry = toPkgseerRegistry(normalisedRegistryArg);
|
|
2039
2036
|
if (!supportsDependenciesRegistry(registry)) {
|
|
2040
|
-
throw new UnsupportedDependenciesRegistryError(`pkg deps only supports ${
|
|
2037
|
+
throw new UnsupportedDependenciesRegistryError(`pkg deps only supports ${SUPPORTED_DEPS_REGISTRIES_LIST}. Got: ${normalisedRegistryArg}.`);
|
|
2041
2038
|
}
|
|
2042
2039
|
const version2 = normaliseVersion(input.version);
|
|
2043
2040
|
const canonicalLifecycles = resolveLifecycles(input.lifecycle);
|
|
@@ -2415,7 +2412,7 @@ function formatPackageDependenciesTerminal(report, options = {}) {
|
|
|
2415
2412
|
const issues = formatConflictsAndCycles(payload, verbose, useColors);
|
|
2416
2413
|
if (issues)
|
|
2417
2414
|
blocks.push(issues);
|
|
2418
|
-
} else {
|
|
2415
|
+
} else if (!showGroups) {
|
|
2419
2416
|
blocks.push(formatDirectDepsList(payload, verbose, useColors));
|
|
2420
2417
|
}
|
|
2421
2418
|
if (showGroups) {
|
|
@@ -2429,7 +2426,7 @@ function formatPackageDependenciesTerminal(report, options = {}) {
|
|
|
2429
2426
|
function formatHeaderBlock(payload, useColors, showGroups, options) {
|
|
2430
2427
|
const name = colorize(payload.name, "bold", useColors);
|
|
2431
2428
|
const lines = [
|
|
2432
|
-
`${name} @ ${payload.version}
|
|
2429
|
+
`${name} @ ${payload.version} | ${payload.registry}`
|
|
2433
2430
|
];
|
|
2434
2431
|
if (payload.requestedVersion) {
|
|
2435
2432
|
lines.push(dim(`(requested ${payload.requestedVersion})`, useColors));
|
|
@@ -2469,13 +2466,13 @@ function formatSummaryRow(payload, useColors, showGroups, options) {
|
|
|
2469
2466
|
countParts.push(colorize(`${cycleCount} ${noun}`, "red", useColors));
|
|
2470
2467
|
}
|
|
2471
2468
|
}
|
|
2472
|
-
const countLine = countParts.join("
|
|
2469
|
+
const countLine = countParts.join(" | ");
|
|
2473
2470
|
if (showGroups)
|
|
2474
2471
|
return countLine;
|
|
2475
2472
|
const hidden = collectHiddenGroupNames(payload);
|
|
2476
2473
|
if (hidden.length === 0)
|
|
2477
2474
|
return countLine;
|
|
2478
|
-
const hiddenLine = dim(`Hidden groups: ${hidden.join(", ")}
|
|
2475
|
+
const hiddenLine = dim(`Hidden groups: ${hidden.join(", ")} - ${options.hiddenGroupsHint ?? "use --lifecycle all."}`, useColors);
|
|
2479
2476
|
return `${countLine}
|
|
2480
2477
|
${hiddenLine}`;
|
|
2481
2478
|
}
|
|
@@ -2490,18 +2487,21 @@ function formatDirectDepsList(payload, verbose, useColors) {
|
|
|
2490
2487
|
if (!runtime || runtime.count === 0)
|
|
2491
2488
|
return "";
|
|
2492
2489
|
const sorted = sortAlphabetically(runtime.items, (i) => i.name);
|
|
2490
|
+
const lines = ["Runtime dependencies:", ""];
|
|
2493
2491
|
if (!verbose) {
|
|
2494
|
-
|
|
2492
|
+
lines.push(...sorted.map((item) => ` ${formatDepLabel(item)}`));
|
|
2493
|
+
return lines.join(`
|
|
2495
2494
|
`);
|
|
2496
2495
|
}
|
|
2497
2496
|
const rootLabel = `${payload.name}@${payload.version}`;
|
|
2498
|
-
|
|
2497
|
+
lines.push(...sorted.map((item) => {
|
|
2499
2498
|
const head = ` ${formatDepLabel(item)}`;
|
|
2500
2499
|
const constraintLabel = item.constraint ?? "*";
|
|
2501
2500
|
const line = dim(` - ${constraintLabel} required by ${rootLabel}`, useColors);
|
|
2502
2501
|
return `${head}
|
|
2503
2502
|
${line}`;
|
|
2504
|
-
})
|
|
2503
|
+
}));
|
|
2504
|
+
return lines.join(`
|
|
2505
2505
|
`);
|
|
2506
2506
|
}
|
|
2507
2507
|
function formatDepLabel(item) {
|
|
@@ -2516,11 +2516,13 @@ function formatTransitiveDepsList(payload, verbose, useColors) {
|
|
|
2516
2516
|
if (packages.length === 0)
|
|
2517
2517
|
return "";
|
|
2518
2518
|
const sorted = [...packages].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
2519
|
+
const lines = ["Transitive packages:", ""];
|
|
2519
2520
|
if (!verbose) {
|
|
2520
|
-
|
|
2521
|
+
lines.push(...sorted.map((pkg) => ` ${formatPackageLabel(pkg)}`));
|
|
2522
|
+
return lines.join(`
|
|
2521
2523
|
`);
|
|
2522
2524
|
}
|
|
2523
|
-
|
|
2525
|
+
lines.push(...sorted.map((pkg) => {
|
|
2524
2526
|
const head = ` ${formatPackageLabel(pkg)}`;
|
|
2525
2527
|
const importers = pkg.importers ?? [];
|
|
2526
2528
|
if (importers.length === 0)
|
|
@@ -2528,7 +2530,8 @@ function formatTransitiveDepsList(payload, verbose, useColors) {
|
|
|
2528
2530
|
const bullets = formatImporterBullets(importers, useColors);
|
|
2529
2531
|
return `${head}
|
|
2530
2532
|
${bullets}`;
|
|
2531
|
-
})
|
|
2533
|
+
}));
|
|
2534
|
+
return lines.join(`
|
|
2532
2535
|
`);
|
|
2533
2536
|
}
|
|
2534
2537
|
function formatPackageLabel(pkg) {
|
|
@@ -2583,7 +2586,7 @@ function formatConflictsAndCycles(payload, verbose, useColors) {
|
|
|
2583
2586
|
lines.push("");
|
|
2584
2587
|
lines.push(colorize(`Circular dependencies (${cycles.length}):`, "red", useColors));
|
|
2585
2588
|
for (const c of cycles)
|
|
2586
|
-
lines.push(` ${c.cycle.join("
|
|
2589
|
+
lines.push(` ${c.cycle.join(" -> ")}`);
|
|
2587
2590
|
}
|
|
2588
2591
|
return lines.join(`
|
|
2589
2592
|
`);
|
|
@@ -2598,6 +2601,8 @@ function formatGroupsBlock(payload, verbose, useColors) {
|
|
|
2598
2601
|
}
|
|
2599
2602
|
const summary = summariseGroupsByLifecycle(groups.items);
|
|
2600
2603
|
const groupNoun = groups.items.length === 1 ? "group" : "groups";
|
|
2604
|
+
lines.push("Dependency groups:");
|
|
2605
|
+
lines.push("");
|
|
2601
2606
|
lines.push(colorize(`${groups.items.length} ${groupNoun} (${summary}):`, "bold", useColors));
|
|
2602
2607
|
lines.push("");
|
|
2603
2608
|
if (verbose && groups.environmentMarkers && groups.environmentMarkers.length > 0) {
|
|
@@ -2622,9 +2627,7 @@ function formatGroupsBlock(payload, verbose, useColors) {
|
|
|
2622
2627
|
} else {
|
|
2623
2628
|
const nameWidth = Math.max(...deps.map((d) => d.name.length));
|
|
2624
2629
|
for (const dep of deps) {
|
|
2625
|
-
|
|
2626
|
-
const constraint = dep.constraint ?? "";
|
|
2627
|
-
lines.push(` ${name} ${constraint}`.trimEnd());
|
|
2630
|
+
lines.push(` ${formatGroupDependencyRow(dep, group, payload, nameWidth)}`);
|
|
2628
2631
|
}
|
|
2629
2632
|
}
|
|
2630
2633
|
lines.push("");
|
|
@@ -2632,6 +2635,20 @@ function formatGroupsBlock(payload, verbose, useColors) {
|
|
|
2632
2635
|
return lines.join(`
|
|
2633
2636
|
`).trimEnd();
|
|
2634
2637
|
}
|
|
2638
|
+
function formatGroupDependencyRow(dep, group, payload, nameWidth) {
|
|
2639
|
+
const constraint = dep.constraint ?? "";
|
|
2640
|
+
if (group.lifecycle !== "runtime") {
|
|
2641
|
+
const name = dep.name.padEnd(nameWidth);
|
|
2642
|
+
return `${name} ${constraint}`.trimEnd();
|
|
2643
|
+
}
|
|
2644
|
+
const resolvedVersion = payload.runtime?.items.find((item) => item.name === dep.name)?.version;
|
|
2645
|
+
if (!resolvedVersion) {
|
|
2646
|
+
const name = dep.name.padEnd(nameWidth);
|
|
2647
|
+
return `${name} ${constraint}`.trimEnd();
|
|
2648
|
+
}
|
|
2649
|
+
const versionedName = `${dep.name}@${resolvedVersion}`.padEnd(nameWidth);
|
|
2650
|
+
return `${versionedName} ${constraint}`.trimEnd();
|
|
2651
|
+
}
|
|
2635
2652
|
function formatEnvironmentMarker(marker) {
|
|
2636
2653
|
if (marker.type && marker.value)
|
|
2637
2654
|
return `${marker.type}: ${marker.value}`;
|
|
@@ -2900,12 +2917,6 @@ function buildPackageSummarySuccessPayload(summary) {
|
|
|
2900
2917
|
const github = buildGithub(pkg.githubRepository);
|
|
2901
2918
|
if (github)
|
|
2902
2919
|
payload.github = github;
|
|
2903
|
-
if (summary.quickstart?.installCommand) {
|
|
2904
|
-
payload.install = summary.quickstart.installCommand;
|
|
2905
|
-
}
|
|
2906
|
-
const usage = normaliseUsage(summary.quickstart?.usageExample);
|
|
2907
|
-
if (usage)
|
|
2908
|
-
payload.usage = usage;
|
|
2909
2920
|
const vulns = buildVulnerabilities(summary.security);
|
|
2910
2921
|
if (vulns)
|
|
2911
2922
|
payload.vulnerabilities = vulns;
|
|
@@ -2964,20 +2975,12 @@ function buildGithub(github) {
|
|
|
2964
2975
|
result.lastPushedAt = lastPushedAt;
|
|
2965
2976
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
2966
2977
|
}
|
|
2967
|
-
function normaliseUsage(usage) {
|
|
2968
|
-
if (!usage)
|
|
2969
|
-
return;
|
|
2970
|
-
const cleaned = usage.replace(/\r\n/g, `
|
|
2971
|
-
`).replace(/\r/g, `
|
|
2972
|
-
`);
|
|
2973
|
-
return cleaned.length > 0 ? cleaned : undefined;
|
|
2974
|
-
}
|
|
2975
2978
|
function buildVulnerabilities(security) {
|
|
2976
2979
|
if (!security)
|
|
2977
2980
|
return;
|
|
2978
|
-
|
|
2979
|
-
if (total === 0)
|
|
2981
|
+
if (typeof security.vulnerabilityCount !== "number")
|
|
2980
2982
|
return;
|
|
2983
|
+
const total = security.vulnerabilityCount;
|
|
2981
2984
|
const result = {
|
|
2982
2985
|
total,
|
|
2983
2986
|
affectsLatest: security.hasCurrentVulnerabilities ?? false
|
|
@@ -3033,7 +3036,7 @@ function pickChangelogSummary(entry) {
|
|
|
3033
3036
|
const firstLine = entry.body.split(/\r?\n/).map((line) => stripMarkdownHeading(line.trim())).find((line) => line.length > 0);
|
|
3034
3037
|
if (!firstLine)
|
|
3035
3038
|
return;
|
|
3036
|
-
return firstLine.length > 120 ? `${firstLine.slice(0,
|
|
3039
|
+
return firstLine.length > 120 ? `${firstLine.slice(0, 117).trimEnd()}...` : firstLine;
|
|
3037
3040
|
}
|
|
3038
3041
|
function stripMarkdownHeading(line) {
|
|
3039
3042
|
return line.replace(/^#{1,6}\s+/, "").trim();
|
|
@@ -3044,7 +3047,7 @@ function formatPackageSummaryTerminal(summary, options = {}) {
|
|
|
3044
3047
|
const width = resolveWidth(options.terminalWidth);
|
|
3045
3048
|
const now = options.now ?? new Date;
|
|
3046
3049
|
const sections = [];
|
|
3047
|
-
const header = lean.license ? `${colorize(lean.name, "bold", useColors)} @ ${lean.version}
|
|
3050
|
+
const header = lean.license ? `${colorize(lean.name, "bold", useColors)} @ ${lean.version} | ${lean.license}` : `${colorize(lean.name, "bold", useColors)} @ ${lean.version}`;
|
|
3048
3051
|
sections.push(header);
|
|
3049
3052
|
if (lean.description) {
|
|
3050
3053
|
sections.push(wrapText(lean.description, width));
|
|
@@ -3054,9 +3057,6 @@ function formatPackageSummaryTerminal(summary, options = {}) {
|
|
|
3054
3057
|
sections.push(fields.join(`
|
|
3055
3058
|
`));
|
|
3056
3059
|
}
|
|
3057
|
-
if (lean.vulnerabilities) {
|
|
3058
|
-
sections.push(formatVulnFooter(lean.vulnerabilities, useColors));
|
|
3059
|
-
}
|
|
3060
3060
|
if (options.verbose) {
|
|
3061
3061
|
const verbose = buildVerboseSections(lean, useColors);
|
|
3062
3062
|
if (verbose.length > 0)
|
|
@@ -3101,10 +3101,19 @@ function wrapText(text, width) {
|
|
|
3101
3101
|
function buildFieldList(lean, summary, useColors, now) {
|
|
3102
3102
|
const fields = [];
|
|
3103
3103
|
if (lean.repository) {
|
|
3104
|
+
const repositoryParts = [dim(lean.repository, useColors)];
|
|
3105
|
+
const githubPopularity = formatGithubPopularity(lean.github);
|
|
3106
|
+
if (githubPopularity)
|
|
3107
|
+
repositoryParts.push(`(${githubPopularity})`);
|
|
3104
3108
|
fields.push({
|
|
3105
3109
|
label: "Repository",
|
|
3106
|
-
value:
|
|
3110
|
+
value: repositoryParts.join(" ")
|
|
3107
3111
|
});
|
|
3112
|
+
} else {
|
|
3113
|
+
const githubPopularity = formatGithubPopularity(lean.github);
|
|
3114
|
+
if (githubPopularity) {
|
|
3115
|
+
fields.push({ label: "GitHub", value: githubPopularity });
|
|
3116
|
+
}
|
|
3108
3117
|
}
|
|
3109
3118
|
if (lean.homepage) {
|
|
3110
3119
|
fields.push({ label: "Homepage", value: dim(lean.homepage, useColors) });
|
|
@@ -3124,54 +3133,53 @@ function buildFieldList(lean, summary, useColors, now) {
|
|
|
3124
3133
|
value: `${formatCompactNumber(lean.downloads.total)} total`
|
|
3125
3134
|
});
|
|
3126
3135
|
}
|
|
3127
|
-
if (lean.
|
|
3128
|
-
fields.push({
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3136
|
+
if (lean.vulnerabilities) {
|
|
3137
|
+
fields.push({
|
|
3138
|
+
label: "Vulnerabilities",
|
|
3139
|
+
value: formatVulnerabilityStatus(lean.vulnerabilities)
|
|
3140
|
+
});
|
|
3132
3141
|
}
|
|
3133
3142
|
const labelWidth = Math.max(10, ...fields.map((field) => field.label.length));
|
|
3134
3143
|
return fields.map((field) => `${field.label.padEnd(labelWidth)} ${field.value}`);
|
|
3135
3144
|
}
|
|
3136
|
-
function
|
|
3145
|
+
function formatGithubPopularity(github) {
|
|
3146
|
+
if (!github)
|
|
3147
|
+
return;
|
|
3137
3148
|
const parts = [];
|
|
3149
|
+
if (github.archived) {
|
|
3150
|
+
parts.push("[ARCHIVED]");
|
|
3151
|
+
}
|
|
3138
3152
|
if (github.stars !== undefined) {
|
|
3139
|
-
parts.push(
|
|
3153
|
+
parts.push(`${formatCompactNumber(github.stars)} stars`);
|
|
3140
3154
|
}
|
|
3141
3155
|
if (github.forks !== undefined) {
|
|
3142
3156
|
parts.push(`${formatCompactNumber(github.forks)} forks`);
|
|
3143
3157
|
}
|
|
3144
3158
|
if (github.openIssues !== undefined) {
|
|
3145
|
-
parts.push(`${formatCompactNumber(github.openIssues)}
|
|
3146
|
-
}
|
|
3147
|
-
if (github.archived) {
|
|
3148
|
-
parts.push("archived");
|
|
3159
|
+
parts.push(`${formatCompactNumber(github.openIssues)} issues`);
|
|
3149
3160
|
}
|
|
3150
|
-
return parts.join("
|
|
3161
|
+
return parts.length > 0 ? parts.join(", ") : undefined;
|
|
3151
3162
|
}
|
|
3152
|
-
function
|
|
3163
|
+
function formatVulnerabilityStatus(vulns) {
|
|
3164
|
+
if (vulns.total === 0) {
|
|
3165
|
+
return "No active vulnerabilities in latest published version";
|
|
3166
|
+
}
|
|
3153
3167
|
const noun = vulns.total === 1 ? "vulnerability" : "vulnerabilities";
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3168
|
+
if (vulns.affectsLatest) {
|
|
3169
|
+
return `${vulns.total} active ${noun}; latest affected`;
|
|
3170
|
+
}
|
|
3171
|
+
return `${vulns.total} known ${noun}; latest not affected`;
|
|
3157
3172
|
}
|
|
3158
3173
|
function buildVerboseSections(lean, useColors) {
|
|
3159
3174
|
const blocks = [];
|
|
3160
|
-
if (lean.
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
`).map((line) => ` ${line}`).join(`
|
|
3165
|
-
`)
|
|
3166
|
-
].join(`
|
|
3167
|
-
`));
|
|
3175
|
+
if (lean.github) {
|
|
3176
|
+
const github = formatVerboseGithub(lean.github, useColors);
|
|
3177
|
+
if (github)
|
|
3178
|
+
blocks.push(github);
|
|
3168
3179
|
}
|
|
3169
3180
|
if (lean.vulnerabilities?.recent && lean.vulnerabilities.recent.length > 0) {
|
|
3170
3181
|
blocks.push(formatVerboseAdvisories(lean.vulnerabilities.recent, useColors));
|
|
3171
3182
|
}
|
|
3172
|
-
if (lean.github?.topics && lean.github.topics.length > 0) {
|
|
3173
|
-
blocks.push(`${"Topics".padEnd(10)} ${lean.github.topics.join(", ")}`);
|
|
3174
|
-
}
|
|
3175
3183
|
if (lean.recentChanges && lean.recentChanges.length > 0) {
|
|
3176
3184
|
blocks.push(formatVerboseChanges(lean.recentChanges, useColors));
|
|
3177
3185
|
}
|
|
@@ -3179,6 +3187,23 @@ function buildVerboseSections(lean, useColors) {
|
|
|
3179
3187
|
|
|
3180
3188
|
`);
|
|
3181
3189
|
}
|
|
3190
|
+
function formatVerboseGithub(github, useColors) {
|
|
3191
|
+
const fields = [];
|
|
3192
|
+
if (github.language)
|
|
3193
|
+
fields.push({ label: "Language", value: github.language });
|
|
3194
|
+
if (github.lastPushedAt) {
|
|
3195
|
+
fields.push({ label: "Last pushed", value: github.lastPushedAt });
|
|
3196
|
+
}
|
|
3197
|
+
if (github.topics && github.topics.length > 0) {
|
|
3198
|
+
fields.push({ label: "Topics", value: github.topics.join(", ") });
|
|
3199
|
+
}
|
|
3200
|
+
const labelWidth = Math.max(10, ...fields.map((field) => field.label.length));
|
|
3201
|
+
return fields.length > 0 ? [
|
|
3202
|
+
highlight("GitHub", useColors),
|
|
3203
|
+
...fields.map((field) => ` ${field.label.padEnd(labelWidth)} ${field.value}`)
|
|
3204
|
+
].join(`
|
|
3205
|
+
`) : undefined;
|
|
3206
|
+
}
|
|
3182
3207
|
function formatVerboseAdvisories(advisories, useColors) {
|
|
3183
3208
|
const labelWidth = Math.max(...advisories.map((a) => (a.severityLabel ?? "").length));
|
|
3184
3209
|
const lines = [highlight("Recent advisories", useColors)];
|
|
@@ -3228,12 +3253,22 @@ var SUPPORTED_VULN_REGISTRIES = new Set([
|
|
|
3228
3253
|
"NPM",
|
|
3229
3254
|
"PYPI",
|
|
3230
3255
|
"HEX",
|
|
3231
|
-
"CRATES"
|
|
3256
|
+
"CRATES",
|
|
3257
|
+
"NUGET",
|
|
3258
|
+
"MAVEN",
|
|
3259
|
+
"PACKAGIST",
|
|
3260
|
+
"RUBYGEMS",
|
|
3261
|
+
"GO"
|
|
3232
3262
|
]);
|
|
3233
|
-
var SUPPORTED_VULN_REGISTRIES_HUMAN = "npm, pypi, hex, and
|
|
3263
|
+
var SUPPORTED_VULN_REGISTRIES_HUMAN = "npm, pypi, hex, crates, nuget, maven, packagist, rubygems, and go";
|
|
3234
3264
|
function supportsVulnerabilitiesRegistry(registry) {
|
|
3235
3265
|
return SUPPORTED_VULN_REGISTRIES.has(registry);
|
|
3236
3266
|
}
|
|
3267
|
+
var ADVISORY_SCOPE_TO_GRAPHQL = {
|
|
3268
|
+
affected: "AFFECTED",
|
|
3269
|
+
non_affecting: "NON_AFFECTING",
|
|
3270
|
+
all: "ALL"
|
|
3271
|
+
};
|
|
3237
3272
|
function buildPackageVulnerabilitiesParams(input) {
|
|
3238
3273
|
const trimmedName = input.packageName?.trim() ?? "";
|
|
3239
3274
|
if (!trimmedName) {
|
|
@@ -3247,22 +3282,27 @@ function buildPackageVulnerabilitiesParams(input) {
|
|
|
3247
3282
|
if (!supportsVulnerabilitiesRegistry(registry)) {
|
|
3248
3283
|
throw new UnsupportedVulnerabilitiesRegistryError(`pkg vulns only supports ${SUPPORTED_VULN_REGISTRIES_HUMAN}. Got: ${normalisedRegistryArg}.`);
|
|
3249
3284
|
}
|
|
3250
|
-
const
|
|
3285
|
+
const severityLabel2 = resolveMinSeverityLabel(input.minSeverity);
|
|
3286
|
+
const minSeverity = severityLabel2 !== undefined ? SEVERITY_LABEL_TO_CVSS[severityLabel2] : undefined;
|
|
3251
3287
|
const trimmedVersion = input.version?.trim();
|
|
3252
3288
|
if (trimmedVersion && /^v\d/i.test(trimmedVersion)) {
|
|
3253
3289
|
throw new InvalidPackageSpecError(`Invalid version '${trimmedVersion}'. Use the canonical package version without a leading 'v' (for example '4.18.0', not 'v4.18.0').`);
|
|
3254
3290
|
}
|
|
3291
|
+
const advisoryScope = resolveAdvisoryScope(input.advisoryScope);
|
|
3292
|
+
const filterWithScope = buildFilterEcho2(severityLabel2, input.includeWithdrawn, advisoryScope);
|
|
3255
3293
|
return {
|
|
3256
3294
|
params: {
|
|
3257
3295
|
registry,
|
|
3258
3296
|
packageName: trimmedName,
|
|
3259
3297
|
version: trimmedVersion && trimmedVersion.length > 0 ? trimmedVersion : undefined,
|
|
3260
3298
|
minSeverity,
|
|
3261
|
-
includeWithdrawn: input.includeWithdrawn
|
|
3262
|
-
|
|
3299
|
+
includeWithdrawn: input.includeWithdrawn,
|
|
3300
|
+
advisoryScope: advisoryScope ? ADVISORY_SCOPE_TO_GRAPHQL[advisoryScope] : undefined
|
|
3301
|
+
},
|
|
3302
|
+
...filterWithScope ? { filter: filterWithScope } : {}
|
|
3263
3303
|
};
|
|
3264
3304
|
}
|
|
3265
|
-
function
|
|
3305
|
+
function resolveMinSeverityLabel(raw) {
|
|
3266
3306
|
if (raw === undefined)
|
|
3267
3307
|
return;
|
|
3268
3308
|
const trimmed = raw.trim();
|
|
@@ -3272,12 +3312,36 @@ function resolveMinSeverity(raw) {
|
|
|
3272
3312
|
if (!isSeverityLabel(lower)) {
|
|
3273
3313
|
throw new InvalidPackageSpecError(`Unsupported severity '${raw}'. Expected one of: low, medium, high, critical.`);
|
|
3274
3314
|
}
|
|
3275
|
-
return
|
|
3315
|
+
return lower;
|
|
3316
|
+
}
|
|
3317
|
+
function buildFilterEcho2(minSeverity, includeWithdrawn, advisoryScope) {
|
|
3318
|
+
const filter = {};
|
|
3319
|
+
if (minSeverity !== undefined)
|
|
3320
|
+
filter.minSeverity = minSeverity;
|
|
3321
|
+
if (includeWithdrawn === true)
|
|
3322
|
+
filter.includeWithdrawn = true;
|
|
3323
|
+
if (advisoryScope !== undefined && advisoryScope !== "affected") {
|
|
3324
|
+
filter.advisoryScope = advisoryScope;
|
|
3325
|
+
}
|
|
3326
|
+
return Object.keys(filter).length > 0 ? filter : undefined;
|
|
3327
|
+
}
|
|
3328
|
+
function resolveAdvisoryScope(raw) {
|
|
3329
|
+
if (raw === undefined)
|
|
3330
|
+
return;
|
|
3331
|
+
const trimmed = raw.trim();
|
|
3332
|
+
if (trimmed.length === 0)
|
|
3333
|
+
return;
|
|
3334
|
+
const lower = trimmed.toLowerCase().replace(/-/g, "_");
|
|
3335
|
+
if (lower === "affected" || lower === "non_affecting" || lower === "all") {
|
|
3336
|
+
return lower;
|
|
3337
|
+
}
|
|
3338
|
+
throw new InvalidPackageSpecError(`Unsupported advisory scope '${raw}'. Expected one of: affected, non_affecting, all.`);
|
|
3276
3339
|
}
|
|
3277
3340
|
function isSeverityLabel(value) {
|
|
3278
3341
|
return value === "low" || value === "medium" || value === "high" || value === "critical";
|
|
3279
3342
|
}
|
|
3280
3343
|
// src/shared/package-vulnerabilities-response.ts
|
|
3344
|
+
var DEFAULT_ADVISORY_CAP = 5;
|
|
3281
3345
|
function buildPackageVulnerabilitiesSuccessPayload(report, options = {}) {
|
|
3282
3346
|
const pkg = report.package;
|
|
3283
3347
|
const security = report.security;
|
|
@@ -3293,11 +3357,12 @@ function buildPackageVulnerabilitiesSuccessPayload(report, options = {}) {
|
|
|
3293
3357
|
if (requestedEcho !== undefined) {
|
|
3294
3358
|
payload.requestedVersion = requestedEcho;
|
|
3295
3359
|
}
|
|
3296
|
-
if (
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3360
|
+
if (options.filter !== undefined) {
|
|
3361
|
+
payload.filter = options.filter;
|
|
3362
|
+
}
|
|
3363
|
+
const sortedAdvisories = sortAdvisories(dedupedAdvisories.map(buildAdvisory));
|
|
3364
|
+
if (sortedAdvisories.length > 0) {
|
|
3365
|
+
payload.advisories = sortedAdvisories;
|
|
3301
3366
|
}
|
|
3302
3367
|
const upgradePaths = security?.upgradePaths;
|
|
3303
3368
|
if (upgradePaths && upgradePaths.length > 0) {
|
|
@@ -3343,7 +3408,7 @@ function buildSummary(total, security, dedupedAdvisories) {
|
|
|
3343
3408
|
if (typeof security?.currentVersionAffected === "boolean") {
|
|
3344
3409
|
summary.affected = security.currentVersionAffected;
|
|
3345
3410
|
}
|
|
3346
|
-
if (
|
|
3411
|
+
if (dedupedAdvisories.length === 0)
|
|
3347
3412
|
return summary;
|
|
3348
3413
|
const bySeverity = computeBySeverity(dedupedAdvisories);
|
|
3349
3414
|
const anyCounted = Object.values(bySeverity).some((n) => n > 0);
|
|
@@ -3731,17 +3796,25 @@ function lowerRegistry3(value) {
|
|
|
3731
3796
|
}
|
|
3732
3797
|
function formatPackageVulnerabilitiesTerminal(report, options = {}) {
|
|
3733
3798
|
const payload = buildPackageVulnerabilitiesSuccessPayload(report, {
|
|
3734
|
-
requestedVersion: options.requestedVersion
|
|
3799
|
+
requestedVersion: options.requestedVersion,
|
|
3800
|
+
filter: options.filter
|
|
3735
3801
|
});
|
|
3736
3802
|
const useColors = options.useColors ?? false;
|
|
3737
3803
|
const verbose = options.verbose ?? false;
|
|
3804
|
+
const surface = options.surface ?? "cli";
|
|
3738
3805
|
const headerLine = formatHeader(payload, useColors);
|
|
3739
3806
|
const requestedLine = payload.requestedVersion ? dim(`(requested ${payload.requestedVersion})`, useColors) : undefined;
|
|
3807
|
+
const filterLines = formatFilterLines(payload.filter);
|
|
3740
3808
|
if (payload.summary.total === 0) {
|
|
3741
3809
|
const lines = [headerLine];
|
|
3742
3810
|
if (requestedLine)
|
|
3743
3811
|
lines.push(requestedLine);
|
|
3812
|
+
lines.push(...filterLines);
|
|
3744
3813
|
lines.push(formatNoAffectedVulnerabilitiesLine(payload));
|
|
3814
|
+
if (payload.advisories && payload.advisories.length > 0) {
|
|
3815
|
+
const rangeLimit = resolveAffectedRangesLimit(options.terminalWidth);
|
|
3816
|
+
lines.push("", formatAdvisoryList(payload.advisories, verbose, useColors, rangeLimit, surface));
|
|
3817
|
+
}
|
|
3745
3818
|
return `${lines.join(`
|
|
3746
3819
|
`)}
|
|
3747
3820
|
`;
|
|
@@ -3750,15 +3823,21 @@ function formatPackageVulnerabilitiesTerminal(report, options = {}) {
|
|
|
3750
3823
|
const headerBlock = [headerLine];
|
|
3751
3824
|
if (requestedLine)
|
|
3752
3825
|
headerBlock.push(requestedLine);
|
|
3826
|
+
headerBlock.push(...filterLines);
|
|
3753
3827
|
headerBlock.push(formatSummaryLine(payload, useColors));
|
|
3754
|
-
const
|
|
3828
|
+
const selectedAdvisoryCount = payload.advisories?.length ?? 0;
|
|
3829
|
+
const scope = payload.filter?.advisoryScope;
|
|
3830
|
+
const selectedCountLine = formatSelectedAdvisoryCountLine(selectedAdvisoryCount, scope);
|
|
3831
|
+
if (selectedCountLine)
|
|
3832
|
+
headerBlock.push(selectedCountLine);
|
|
3833
|
+
const breakdown = scope === undefined ? formatBreakdownLine(payload.summary, useColors) : undefined;
|
|
3755
3834
|
if (breakdown)
|
|
3756
3835
|
headerBlock.push(breakdown);
|
|
3757
3836
|
blocks.push(headerBlock.join(`
|
|
3758
3837
|
`));
|
|
3759
3838
|
if (payload.advisories && payload.advisories.length > 0) {
|
|
3760
3839
|
const rangeLimit = resolveAffectedRangesLimit(options.terminalWidth);
|
|
3761
|
-
blocks.push(formatAdvisoryList(payload.advisories, verbose, useColors, rangeLimit));
|
|
3840
|
+
blocks.push(formatAdvisoryList(payload.advisories, verbose, useColors, rangeLimit, surface));
|
|
3762
3841
|
}
|
|
3763
3842
|
const upgradeFooter = formatUpgradeFooter(payload.upgradePaths);
|
|
3764
3843
|
if (upgradeFooter)
|
|
@@ -3770,7 +3849,29 @@ function formatPackageVulnerabilitiesTerminal(report, options = {}) {
|
|
|
3770
3849
|
}
|
|
3771
3850
|
function formatHeader(payload, useColors) {
|
|
3772
3851
|
const name = colorize(payload.name, "bold", useColors);
|
|
3773
|
-
return `${name} @ ${payload.version}
|
|
3852
|
+
return `${name} @ ${payload.version} | ${payload.registry}`;
|
|
3853
|
+
}
|
|
3854
|
+
function formatFilterLines(filter) {
|
|
3855
|
+
if (!filter)
|
|
3856
|
+
return [];
|
|
3857
|
+
const lines = [];
|
|
3858
|
+
if (filter.advisoryScope) {
|
|
3859
|
+
lines.push(`Scope ${formatAdvisoryScope(filter.advisoryScope)}`);
|
|
3860
|
+
}
|
|
3861
|
+
if (filter.minSeverity) {
|
|
3862
|
+
lines.push(`Filter severity >= ${filter.minSeverity}`);
|
|
3863
|
+
}
|
|
3864
|
+
if (filter.includeWithdrawn === true) {
|
|
3865
|
+
lines.push("Filter include withdrawn");
|
|
3866
|
+
}
|
|
3867
|
+
return lines;
|
|
3868
|
+
}
|
|
3869
|
+
function formatAdvisoryScope(scope) {
|
|
3870
|
+
if (scope === "non_affecting")
|
|
3871
|
+
return "historical advisories only";
|
|
3872
|
+
if (scope === "all")
|
|
3873
|
+
return "all package advisories";
|
|
3874
|
+
return scope;
|
|
3774
3875
|
}
|
|
3775
3876
|
function formatSummaryLine(payload, useColors) {
|
|
3776
3877
|
const n = payload.summary.total;
|
|
@@ -3786,13 +3887,41 @@ function formatSummaryLine(payload, useColors) {
|
|
|
3786
3887
|
return base;
|
|
3787
3888
|
}
|
|
3788
3889
|
function formatNoAffectedVulnerabilitiesLine(payload) {
|
|
3890
|
+
const selectedAdvisoryCount = payload.advisories?.length ?? 0;
|
|
3891
|
+
if (payload.filter?.advisoryScope === "non_affecting") {
|
|
3892
|
+
if (selectedAdvisoryCount > 0) {
|
|
3893
|
+
return "No active vulnerabilities affect this version; historical advisories are listed below.";
|
|
3894
|
+
}
|
|
3895
|
+
return "No active vulnerabilities affect this version; no historical advisories match the current filter.";
|
|
3896
|
+
}
|
|
3897
|
+
if (payload.filter?.advisoryScope === "all") {
|
|
3898
|
+
if (selectedAdvisoryCount > 0) {
|
|
3899
|
+
return "No active vulnerabilities affect this version; package advisories are listed below.";
|
|
3900
|
+
}
|
|
3901
|
+
return "No active vulnerabilities affect this version; no package advisories match the current filter.";
|
|
3902
|
+
}
|
|
3903
|
+
if (payload.filter !== undefined) {
|
|
3904
|
+
return "No vulnerabilities matching the filter affect this version.";
|
|
3905
|
+
}
|
|
3789
3906
|
const historical = payload.summary.nonAffectingVulnerabilityCount ?? 0;
|
|
3790
3907
|
if (historical > 0) {
|
|
3791
3908
|
const noun = historical === 1 ? "historical advisory" : "historical advisories";
|
|
3792
3909
|
const verb = historical === 1 ? "does" : "do";
|
|
3793
|
-
return `No vulnerabilities affect this version (${historical} ${noun} ${verb} not apply).`;
|
|
3910
|
+
return `No active vulnerabilities affect this version (${historical} ${noun} ${verb} not apply).`;
|
|
3911
|
+
}
|
|
3912
|
+
return "No active vulnerabilities affect this version.";
|
|
3913
|
+
}
|
|
3914
|
+
function formatSelectedAdvisoryCountLine(count, scope) {
|
|
3915
|
+
if (scope === undefined)
|
|
3916
|
+
return;
|
|
3917
|
+
const noun = count === 1 ? "advisory" : "advisories";
|
|
3918
|
+
if (scope === "non_affecting") {
|
|
3919
|
+
return ` showing ${count} historical ${noun} that do not affect this version`;
|
|
3920
|
+
}
|
|
3921
|
+
if (scope === "all") {
|
|
3922
|
+
return ` showing ${count} package ${noun} across affected and historical scopes`;
|
|
3794
3923
|
}
|
|
3795
|
-
return
|
|
3924
|
+
return;
|
|
3796
3925
|
}
|
|
3797
3926
|
function formatBreakdownLine(summary, useColors) {
|
|
3798
3927
|
if (summary.total <= 1)
|
|
@@ -3818,22 +3947,31 @@ function formatBreakdownLine(summary, useColors) {
|
|
|
3818
3947
|
}
|
|
3819
3948
|
if (parts.length === 0)
|
|
3820
3949
|
return;
|
|
3821
|
-
return ` ${parts.join("
|
|
3950
|
+
return ` ${parts.join(" | ")}`;
|
|
3822
3951
|
}
|
|
3823
|
-
function formatAdvisoryList(advisories, verbose, useColors, rangeLimit) {
|
|
3824
|
-
const
|
|
3952
|
+
function formatAdvisoryList(advisories, verbose, useColors, rangeLimit, surface) {
|
|
3953
|
+
const renderedAdvisories = verbose ? advisories : advisories.slice(0, DEFAULT_ADVISORY_CAP);
|
|
3954
|
+
const labelWidth = Math.max(...renderedAdvisories.map((a) => severityColumnLabel(a).length));
|
|
3825
3955
|
const lines = [];
|
|
3826
|
-
for (const advisory of
|
|
3827
|
-
lines.push(...formatAdvisoryLines(advisory, labelWidth, verbose, useColors, rangeLimit));
|
|
3956
|
+
for (const advisory of renderedAdvisories) {
|
|
3957
|
+
lines.push(...formatAdvisoryLines(advisory, labelWidth, verbose, useColors, rangeLimit, surface));
|
|
3828
3958
|
lines.push("");
|
|
3829
3959
|
}
|
|
3960
|
+
const hidden = advisories.length - renderedAdvisories.length;
|
|
3961
|
+
if (hidden > 0) {
|
|
3962
|
+
lines.push(dim(formatAdvisoryCapHint(hidden, surface), useColors));
|
|
3963
|
+
}
|
|
3830
3964
|
return lines.join(`
|
|
3831
3965
|
`).trimEnd();
|
|
3832
3966
|
}
|
|
3967
|
+
function formatAdvisoryCapHint(hidden, surface) {
|
|
3968
|
+
const hint = surface === "mcp" ? "use verbose=true or format=json" : "use -v";
|
|
3969
|
+
return `... (+${hidden} more; ${hint})`;
|
|
3970
|
+
}
|
|
3833
3971
|
function severityColumnLabel(advisory) {
|
|
3834
3972
|
if (advisory.isMalicious === true) {
|
|
3835
3973
|
if (advisory.severityLabel)
|
|
3836
|
-
return `MALWARE
|
|
3974
|
+
return `MALWARE | ${advisory.severityLabel}`;
|
|
3837
3975
|
return "MALWARE";
|
|
3838
3976
|
}
|
|
3839
3977
|
return advisory.severityLabel ?? "unrated";
|
|
@@ -3862,7 +4000,7 @@ function severityColumnColor(advisory, useColors, padded) {
|
|
|
3862
4000
|
return dim(padded, useColors);
|
|
3863
4001
|
}
|
|
3864
4002
|
}
|
|
3865
|
-
function formatAdvisoryLines(advisory, labelWidth, verbose, useColors, rangeLimit) {
|
|
4003
|
+
function formatAdvisoryLines(advisory, labelWidth, verbose, useColors, rangeLimit, surface) {
|
|
3866
4004
|
const rawLabel = severityColumnLabel(advisory);
|
|
3867
4005
|
const padded = rawLabel.padEnd(labelWidth);
|
|
3868
4006
|
const colouredLabel = severityColumnColor(advisory, useColors, padded);
|
|
@@ -3879,7 +4017,7 @@ function formatAdvisoryLines(advisory, labelWidth, verbose, useColors, rangeLimi
|
|
|
3879
4017
|
lines.push(` ${label.padEnd(detailWidth)} ${value}`);
|
|
3880
4018
|
};
|
|
3881
4019
|
if (advisory.affectedRanges && advisory.affectedRanges.length > 0) {
|
|
3882
|
-
pushRow("affected", formatRangeList(advisory.affectedRanges, verbose, useColors, rangeLimit, advisory.affectedVersionRangesCount, advisory.affectedVersionRangesTruncated));
|
|
4020
|
+
pushRow("affected", formatRangeList(advisory.affectedRanges, verbose, useColors, rangeLimit, surface, advisory.affectedVersionRangesCount, advisory.affectedVersionRangesTruncated));
|
|
3883
4021
|
}
|
|
3884
4022
|
if (advisory.fixedIn && advisory.fixedIn.length > 0) {
|
|
3885
4023
|
pushRow("fixed in", advisory.fixedIn.join(", "));
|
|
@@ -3906,12 +4044,12 @@ function formatAdvisoryLines(advisory, labelWidth, verbose, useColors, rangeLimi
|
|
|
3906
4044
|
}
|
|
3907
4045
|
return lines;
|
|
3908
4046
|
}
|
|
3909
|
-
function formatRangeList(ranges, verbose, useColors, limit, totalCount, backendTruncated) {
|
|
4047
|
+
function formatRangeList(ranges, verbose, useColors, limit, surface, totalCount, backendTruncated) {
|
|
3910
4048
|
const actualTotal = Math.max(totalCount ?? ranges.length, ranges.length);
|
|
3911
4049
|
const backendHidden = backendTruncated === true ? actualTotal - ranges.length : 0;
|
|
3912
4050
|
const appendBackendHint = (shown2) => {
|
|
3913
4051
|
if (backendHidden > 0) {
|
|
3914
|
-
const hint2 = dim(
|
|
4052
|
+
const hint2 = dim(`... (+${backendHidden} ranges omitted by service)`, useColors);
|
|
3915
4053
|
return shown2.length > 0 ? `${shown2}, ${hint2}` : hint2;
|
|
3916
4054
|
}
|
|
3917
4055
|
return shown2;
|
|
@@ -3921,7 +4059,8 @@ function formatRangeList(ranges, verbose, useColors, limit, totalCount, backendT
|
|
|
3921
4059
|
}
|
|
3922
4060
|
const shown = ranges.slice(0, limit).join(", ");
|
|
3923
4061
|
const localHidden = ranges.length - limit;
|
|
3924
|
-
const
|
|
4062
|
+
const localHint = surface === "mcp" ? "use verbose=true" : "use -v";
|
|
4063
|
+
const hintText = backendHidden > 0 ? `... (+${localHidden} more with ${localHint}; +${backendHidden} omitted by service)` : `... (+${localHidden} more; ${localHint})`;
|
|
3925
4064
|
const hint = dim(hintText, useColors);
|
|
3926
4065
|
return `${shown}, ${hint}`;
|
|
3927
4066
|
}
|
|
@@ -3972,16 +4111,144 @@ function requirePositiveInteger(raw, label) {
|
|
|
3972
4111
|
}
|
|
3973
4112
|
return parsed;
|
|
3974
4113
|
}
|
|
4114
|
+
// src/shared/read-file-response.ts
|
|
4115
|
+
function buildReadFileSuccessPayload(result, options) {
|
|
4116
|
+
const envelope = {
|
|
4117
|
+
path: result.filePath ?? options.requestedFilePath
|
|
4118
|
+
};
|
|
4119
|
+
if (options.registry)
|
|
4120
|
+
envelope.registry = options.registry;
|
|
4121
|
+
if (options.name)
|
|
4122
|
+
envelope.name = options.name;
|
|
4123
|
+
if (options.repoUrl)
|
|
4124
|
+
envelope.repoUrl = options.repoUrl;
|
|
4125
|
+
if (options.gitRef)
|
|
4126
|
+
envelope.gitRef = options.gitRef;
|
|
4127
|
+
if (result.language != null)
|
|
4128
|
+
envelope.language = result.language;
|
|
4129
|
+
if (result.totalLines != null)
|
|
4130
|
+
envelope.totalLines = result.totalLines;
|
|
4131
|
+
if (result.startLine != null)
|
|
4132
|
+
envelope.startLine = result.startLine;
|
|
4133
|
+
if (result.endLine != null)
|
|
4134
|
+
envelope.endLine = result.endLine;
|
|
4135
|
+
if (result.isBinary) {
|
|
4136
|
+
envelope.isBinary = true;
|
|
4137
|
+
} else if (result.content != null) {
|
|
4138
|
+
envelope.content = result.content;
|
|
4139
|
+
}
|
|
4140
|
+
return envelope;
|
|
4141
|
+
}
|
|
4142
|
+
function formatReadFileTerminal(envelope, options) {
|
|
4143
|
+
const verbose = options.verbose ?? false;
|
|
4144
|
+
if (envelope.isBinary) {
|
|
4145
|
+
return formatBinary(envelope, options, verbose);
|
|
4146
|
+
}
|
|
4147
|
+
if (envelope.content == null) {
|
|
4148
|
+
return formatNoContent(envelope, options, verbose);
|
|
4149
|
+
}
|
|
4150
|
+
if (!verbose) {
|
|
4151
|
+
return envelope.content;
|
|
4152
|
+
}
|
|
4153
|
+
return formatVerboseBody(envelope, options);
|
|
4154
|
+
}
|
|
4155
|
+
function formatBinary(envelope, options, verbose) {
|
|
4156
|
+
const sentinel = dim("Binary file — cannot display as text.", options.useColors);
|
|
4157
|
+
if (verbose) {
|
|
4158
|
+
return `${buildHeader4(envelope, options)}
|
|
4159
|
+
|
|
4160
|
+
${sentinel}
|
|
4161
|
+
`;
|
|
4162
|
+
}
|
|
4163
|
+
return `${sentinel}
|
|
4164
|
+
`;
|
|
4165
|
+
}
|
|
4166
|
+
function formatNoContent(envelope, options, verbose) {
|
|
4167
|
+
const sentinel = dim("(no content returned)", options.useColors);
|
|
4168
|
+
if (verbose) {
|
|
4169
|
+
return `${buildHeader4(envelope, options)}
|
|
4170
|
+
|
|
4171
|
+
${sentinel}
|
|
4172
|
+
`;
|
|
4173
|
+
}
|
|
4174
|
+
return `${sentinel}
|
|
4175
|
+
`;
|
|
4176
|
+
}
|
|
4177
|
+
function formatVerboseBody(envelope, options) {
|
|
4178
|
+
const lines = [];
|
|
4179
|
+
lines.push(buildHeader4(envelope, options));
|
|
4180
|
+
lines.push("");
|
|
4181
|
+
const bodyLines = splitReadFileContentLines(envelope);
|
|
4182
|
+
const startLine = envelope.startLine ?? 1;
|
|
4183
|
+
const endLine = startLine + bodyLines.length - 1;
|
|
4184
|
+
const gutterWidth = String(endLine).length;
|
|
4185
|
+
for (let i = 0;i < bodyLines.length; i++) {
|
|
4186
|
+
const lineNumber = startLine + i;
|
|
4187
|
+
const gutter = dim(String(lineNumber).padStart(gutterWidth, " "), options.useColors);
|
|
4188
|
+
lines.push(`${gutter} ${bodyLines[i]}`);
|
|
4189
|
+
}
|
|
4190
|
+
if (envelope.hint) {
|
|
4191
|
+
lines.push("");
|
|
4192
|
+
lines.push(dim(envelope.hint, options.useColors));
|
|
4193
|
+
}
|
|
4194
|
+
lines.push("");
|
|
4195
|
+
return lines.join(`
|
|
4196
|
+
`);
|
|
4197
|
+
}
|
|
4198
|
+
function splitReadFileContentLines(envelope) {
|
|
4199
|
+
if (!envelope.content)
|
|
4200
|
+
return [];
|
|
4201
|
+
const bodyLines = envelope.content.split(`
|
|
4202
|
+
`);
|
|
4203
|
+
const expectedCount = expectedLineCount(envelope);
|
|
4204
|
+
if (expectedCount === undefined) {
|
|
4205
|
+
if (bodyLines[bodyLines.length - 1] === "")
|
|
4206
|
+
bodyLines.pop();
|
|
4207
|
+
return bodyLines;
|
|
4208
|
+
}
|
|
4209
|
+
while (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === "" && bodyLines.length > expectedCount) {
|
|
4210
|
+
bodyLines.pop();
|
|
4211
|
+
}
|
|
4212
|
+
return bodyLines;
|
|
4213
|
+
}
|
|
4214
|
+
function expectedLineCount(envelope) {
|
|
4215
|
+
if (envelope.startLine === undefined || envelope.endLine === undefined) {
|
|
4216
|
+
return;
|
|
4217
|
+
}
|
|
4218
|
+
if (envelope.endLine < envelope.startLine)
|
|
4219
|
+
return;
|
|
4220
|
+
return envelope.endLine - envelope.startLine + 1;
|
|
4221
|
+
}
|
|
4222
|
+
function buildHeader4(envelope, options) {
|
|
4223
|
+
const parts = [envelope.path];
|
|
4224
|
+
if (envelope.language)
|
|
4225
|
+
parts.push(envelope.language);
|
|
4226
|
+
const rangeLabel = buildRangeLabel(envelope);
|
|
4227
|
+
if (rangeLabel)
|
|
4228
|
+
parts.push(rangeLabel);
|
|
4229
|
+
return colorize(parts.join(" · "), "bold", options.useColors);
|
|
4230
|
+
}
|
|
4231
|
+
function buildRangeLabel(envelope) {
|
|
4232
|
+
const { startLine, endLine, totalLines } = envelope;
|
|
4233
|
+
if (startLine != null && endLine != null) {
|
|
4234
|
+
return totalLines != null ? `lines ${startLine}-${endLine} of ${totalLines}` : `lines ${startLine}-${endLine}`;
|
|
4235
|
+
}
|
|
4236
|
+
if (totalLines != null) {
|
|
4237
|
+
return `${totalLines} lines`;
|
|
4238
|
+
}
|
|
4239
|
+
return;
|
|
4240
|
+
}
|
|
4241
|
+
|
|
3975
4242
|
// src/shared/read-file-text.ts
|
|
3976
4243
|
var SEP4 = " | ";
|
|
3977
4244
|
function renderReadFileText(envelope) {
|
|
3978
4245
|
const lines = [];
|
|
3979
|
-
lines.push(
|
|
4246
|
+
lines.push(buildHeader5(envelope));
|
|
3980
4247
|
lines.push("");
|
|
3981
4248
|
if (envelope.isBinary) {
|
|
3982
4249
|
lines.push("Binary file - cannot display as text.");
|
|
3983
4250
|
} else if (envelope.content) {
|
|
3984
|
-
appendNumberedContent(lines, envelope.content, envelope.startLine ?? 1);
|
|
4251
|
+
appendNumberedContent(lines, envelope.content, envelope.startLine ?? 1, envelope.endLine);
|
|
3985
4252
|
} else {
|
|
3986
4253
|
lines.push("(no content returned)");
|
|
3987
4254
|
}
|
|
@@ -3992,7 +4259,7 @@ function renderReadFileText(envelope) {
|
|
|
3992
4259
|
return lines.join(`
|
|
3993
4260
|
`);
|
|
3994
4261
|
}
|
|
3995
|
-
function
|
|
4262
|
+
function buildHeader5(envelope) {
|
|
3996
4263
|
const parts = [`code_read${SEP4}${envelope.path}`];
|
|
3997
4264
|
if (envelope.language)
|
|
3998
4265
|
parts.push(envelope.language);
|
|
@@ -4009,14 +4276,10 @@ function buildRange(envelope) {
|
|
|
4009
4276
|
return `${envelope.totalLines} lines`;
|
|
4010
4277
|
return;
|
|
4011
4278
|
}
|
|
4012
|
-
function appendNumberedContent(lines, content, startLine) {
|
|
4013
|
-
const bodyLines = content
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
bodyLines.pop();
|
|
4017
|
-
}
|
|
4018
|
-
const endLine = startLine + bodyLines.length - 1;
|
|
4019
|
-
const width = String(endLine).length;
|
|
4279
|
+
function appendNumberedContent(lines, content, startLine, endLine) {
|
|
4280
|
+
const bodyLines = splitReadFileContentLines({ content, startLine, endLine });
|
|
4281
|
+
const renderedEndLine = startLine + bodyLines.length - 1;
|
|
4282
|
+
const width = String(renderedEndLine).length;
|
|
4020
4283
|
for (let i = 0;i < bodyLines.length; i += 1) {
|
|
4021
4284
|
lines.push(`${String(startLine + i).padStart(width, " ")} ${bodyLines[i]}`);
|
|
4022
4285
|
}
|
|
@@ -4112,7 +4375,7 @@ function formatReadPackageDocTerminal(envelope, options) {
|
|
|
4112
4375
|
return envelope.content ?? "";
|
|
4113
4376
|
}
|
|
4114
4377
|
const lines = [];
|
|
4115
|
-
lines.push(
|
|
4378
|
+
lines.push(buildHeader6(envelope, options.useColors));
|
|
4116
4379
|
lines.push(`pageId: ${envelope.pageId}`);
|
|
4117
4380
|
if (envelope.sourceUrl)
|
|
4118
4381
|
lines.push(`source: ${envelope.sourceUrl}`);
|
|
@@ -4132,7 +4395,7 @@ function formatReadPackageDocTerminal(envelope, options) {
|
|
|
4132
4395
|
`)}
|
|
4133
4396
|
`;
|
|
4134
4397
|
}
|
|
4135
|
-
function
|
|
4398
|
+
function buildHeader6(envelope, useColors) {
|
|
4136
4399
|
const badge = envelope.sourceKind === "repo" ? "[repo]" : "[crawled]";
|
|
4137
4400
|
const title = envelope.title ?? envelope.pageId;
|
|
4138
4401
|
const prefix = envelope.registry && envelope.name ? `${envelope.registry}:${envelope.name}${envelope.version ? `@${envelope.version}` : ""}` : "documentation";
|
|
@@ -4142,7 +4405,7 @@ function buildHeader5(envelope, useColors) {
|
|
|
4142
4405
|
var SEP5 = " | ";
|
|
4143
4406
|
function renderReadPackageDocText(envelope) {
|
|
4144
4407
|
const lines = [];
|
|
4145
|
-
lines.push(
|
|
4408
|
+
lines.push(buildHeader7(envelope));
|
|
4146
4409
|
if (envelope.sourceUrl)
|
|
4147
4410
|
lines.push(`source: ${envelope.sourceUrl}`);
|
|
4148
4411
|
if (envelope.filePath) {
|
|
@@ -4159,7 +4422,7 @@ function renderReadPackageDocText(envelope) {
|
|
|
4159
4422
|
return lines.join(`
|
|
4160
4423
|
`);
|
|
4161
4424
|
}
|
|
4162
|
-
function
|
|
4425
|
+
function buildHeader7(envelope) {
|
|
4163
4426
|
const parts = [`docs_read${SEP5}${envelope.pageId}`];
|
|
4164
4427
|
if (envelope.title)
|
|
4165
4428
|
parts.push(envelope.title);
|
|
@@ -4479,6 +4742,7 @@ var AUTO_LOGIN_ELIGIBLE_COMMANDS = new Set([
|
|
|
4479
4742
|
"pkg deps",
|
|
4480
4743
|
"pkg changelog"
|
|
4481
4744
|
]);
|
|
4745
|
+
var AUTH_METADATA_TRUST_WINDOW_MS = 10 * 60 * 1000;
|
|
4482
4746
|
function getCommandPath(command) {
|
|
4483
4747
|
const names = [];
|
|
4484
4748
|
let current = command;
|
|
@@ -4511,10 +4775,15 @@ async function maybeAutoLoginBeforeCommand(command, deps) {
|
|
|
4511
4775
|
})) {
|
|
4512
4776
|
return { status: "skipped" };
|
|
4513
4777
|
}
|
|
4778
|
+
const metadata = await deps.loadAuthSessionMetadata?.();
|
|
4779
|
+
if (metadata && isUnexpiredAuthSessionMetadata(metadata, new Date)) {
|
|
4780
|
+
return { status: "already-authenticated" };
|
|
4781
|
+
}
|
|
4514
4782
|
const container = await deps.createContainer();
|
|
4515
4783
|
if (container.hasValidToken) {
|
|
4516
4784
|
return { status: "already-authenticated" };
|
|
4517
4785
|
}
|
|
4786
|
+
await deps.clearAuthSessionMetadata?.();
|
|
4518
4787
|
const result = await deps.loginFlow({}, container);
|
|
4519
4788
|
switch (result.status) {
|
|
4520
4789
|
case "success":
|
|
@@ -4525,6 +4794,20 @@ async function maybeAutoLoginBeforeCommand(command, deps) {
|
|
|
4525
4794
|
return { status: "failed", message: result.message };
|
|
4526
4795
|
}
|
|
4527
4796
|
}
|
|
4797
|
+
function isUnexpiredAuthSessionMetadata(metadata, now) {
|
|
4798
|
+
const updatedAtMs = Date.parse(metadata.updatedAt);
|
|
4799
|
+
if (Number.isNaN(updatedAtMs))
|
|
4800
|
+
return false;
|
|
4801
|
+
if (now.getTime() - updatedAtMs > AUTH_METADATA_TRUST_WINDOW_MS) {
|
|
4802
|
+
return false;
|
|
4803
|
+
}
|
|
4804
|
+
if (metadata.expiresAt === null)
|
|
4805
|
+
return true;
|
|
4806
|
+
const expiresAtMs = Date.parse(metadata.expiresAt);
|
|
4807
|
+
if (Number.isNaN(expiresAtMs))
|
|
4808
|
+
return false;
|
|
4809
|
+
return now.getTime() < expiresAtMs;
|
|
4810
|
+
}
|
|
4528
4811
|
|
|
4529
4812
|
// src/shared/root-cli-pre-action.ts
|
|
4530
4813
|
function createRootCliPreAction(deps) {
|
|
@@ -4615,11 +4898,11 @@ function buildUnifiedSearchParams(input) {
|
|
|
4615
4898
|
}
|
|
4616
4899
|
function resolveTargets(target, targets) {
|
|
4617
4900
|
if (target && targets) {
|
|
4618
|
-
throw new InvalidArgumentError("Provide either target or targets, not both.");
|
|
4901
|
+
throw new InvalidArgumentError("Provide either `target` for one search target or `targets` for multiple, not both.");
|
|
4619
4902
|
}
|
|
4620
4903
|
const resolved = target ? [target] : targets ?? [];
|
|
4621
4904
|
if (resolved.length === 0) {
|
|
4622
|
-
throw new InvalidArgumentError("
|
|
4905
|
+
throw new InvalidArgumentError("Provide either `target` for one search target or `targets` for multiple; neither was set.");
|
|
4623
4906
|
}
|
|
4624
4907
|
const deduped = [];
|
|
4625
4908
|
const seen = new Set;
|
|
@@ -4738,7 +5021,7 @@ function combineWarnings(parserWarnings, sourceStatus, hits = [], progress) {
|
|
|
4738
5021
|
out.push(...buildHitFreshnessWarnings(hits));
|
|
4739
5022
|
out.push(...buildProgressFreshnessWarnings(progress));
|
|
4740
5023
|
out.push(...buildSourceStatusWarnings(sourceStatus));
|
|
4741
|
-
return out;
|
|
5024
|
+
return Array.from(new Set(out));
|
|
4742
5025
|
}
|
|
4743
5026
|
function buildUnifiedSearchErrorPayload(error2) {
|
|
4744
5027
|
const mapped = mapCodeNavigationError(error2);
|
|
@@ -5171,7 +5454,7 @@ var SUMMARY_WRAP_WIDTH = 76;
|
|
|
5171
5454
|
var SEP6 = " | ";
|
|
5172
5455
|
function renderUnifiedSearchSuccess(payload) {
|
|
5173
5456
|
const lines = [];
|
|
5174
|
-
lines.push(
|
|
5457
|
+
lines.push(buildHeader8(payload));
|
|
5175
5458
|
lines.push("");
|
|
5176
5459
|
if (payload.results.length === 0) {
|
|
5177
5460
|
lines.push(payload.completed ? "No hits." : "No hits yet - indexing.");
|
|
@@ -5202,7 +5485,7 @@ function renderUnifiedSearchError(payload) {
|
|
|
5202
5485
|
return lines.join(`
|
|
5203
5486
|
`);
|
|
5204
5487
|
}
|
|
5205
|
-
function
|
|
5488
|
+
function buildHeader8(payload) {
|
|
5206
5489
|
const count = payload.results.length;
|
|
5207
5490
|
const status = payload.completed ? `${count} hit${count === 1 ? "" : "s"}` : `${count} partial`;
|
|
5208
5491
|
const parts = [`search${SEP6}${status}`];
|
|
@@ -5220,7 +5503,7 @@ function appendUnifiedSearchHits(lines, hits) {
|
|
|
5220
5503
|
});
|
|
5221
5504
|
}
|
|
5222
5505
|
function appendHit(lines, index, hit) {
|
|
5223
|
-
const headerParts = [hit
|
|
5506
|
+
const headerParts = [formatHitPrimary(hit), shortType(hit.type)];
|
|
5224
5507
|
lines.push(`[${index}] ${headerParts.join(" ")}`);
|
|
5225
5508
|
const locator = buildLocatorLine(hit);
|
|
5226
5509
|
if (locator)
|
|
@@ -5234,6 +5517,26 @@ function appendHit(lines, index, hit) {
|
|
|
5234
5517
|
}
|
|
5235
5518
|
}
|
|
5236
5519
|
}
|
|
5520
|
+
function formatHitPrimary(hit) {
|
|
5521
|
+
const loc = hit.locator;
|
|
5522
|
+
if (hit.type === "documentation_page" && loc.pageId) {
|
|
5523
|
+
const target = formatDocsPageTarget(loc, hit.target);
|
|
5524
|
+
return target ? `${loc.pageId} ${target}` : loc.pageId;
|
|
5525
|
+
}
|
|
5526
|
+
if (hit.type === "repository_doc" && loc.filePath) {
|
|
5527
|
+
return `${hit.target} ${loc.filePath}${formatLineRange(loc.startLine, loc.endLine)}`;
|
|
5528
|
+
}
|
|
5529
|
+
return hit.target;
|
|
5530
|
+
}
|
|
5531
|
+
function formatDocsPageTarget(locator, fallbackTarget) {
|
|
5532
|
+
return locator.registry && locator.packageName ? `${locator.registry}:${locator.packageName}` : stripVersionFromTarget(fallbackTarget);
|
|
5533
|
+
}
|
|
5534
|
+
function stripVersionFromTarget(value) {
|
|
5535
|
+
if (!value)
|
|
5536
|
+
return "";
|
|
5537
|
+
const atIndex = value.lastIndexOf("@");
|
|
5538
|
+
return atIndex > 0 ? value.slice(0, atIndex) : value;
|
|
5539
|
+
}
|
|
5237
5540
|
function shortType(type) {
|
|
5238
5541
|
switch (type) {
|
|
5239
5542
|
case "repository_code":
|
|
@@ -5402,7 +5705,7 @@ function wrapText2(text, width) {
|
|
|
5402
5705
|
var SEP7 = " | ";
|
|
5403
5706
|
function renderUnifiedSearchStatusText(payload) {
|
|
5404
5707
|
const lines = [];
|
|
5405
|
-
lines.push(
|
|
5708
|
+
lines.push(buildHeader9(payload));
|
|
5406
5709
|
if (!payload.completed && payload.progress) {
|
|
5407
5710
|
lines.push(formatProgress(payload.progress));
|
|
5408
5711
|
if (payload.progress.targets?.length) {
|
|
@@ -5426,7 +5729,7 @@ function renderUnifiedSearchStatusText(payload) {
|
|
|
5426
5729
|
return lines.join(`
|
|
5427
5730
|
`);
|
|
5428
5731
|
}
|
|
5429
|
-
function
|
|
5732
|
+
function buildHeader9(payload) {
|
|
5430
5733
|
const state = payload.completed ? "complete" : "indexing";
|
|
5431
5734
|
const parts = [`search_status${SEP7}${state}`];
|
|
5432
5735
|
if (payload.searchRef)
|
|
@@ -5999,8 +6302,8 @@ function looksLikeMissingNavpackMessage(message) {
|
|
|
5999
6302
|
const lower = message.toLowerCase();
|
|
6000
6303
|
return lower.includes("has no navpack for this ref") || lower.includes("navpack was pruned") || lower.includes("indexedrepository row still claims current state");
|
|
6001
6304
|
}
|
|
6002
|
-
function handleCodeNavCommandError(error2, json, terminalRenderer, exitCode = 1) {
|
|
6003
|
-
const mapped = mapCodeNavigationError(error2);
|
|
6305
|
+
function handleCodeNavCommandError(error2, json, terminalRenderer, exitCode = 1, mapMappedError = (mapped) => mapped) {
|
|
6306
|
+
const mapped = mapMappedError(mapCodeNavigationError(error2));
|
|
6004
6307
|
if (json) {
|
|
6005
6308
|
console.error(JSON.stringify({
|
|
6006
6309
|
error: mapped.message,
|
|
@@ -6271,7 +6574,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
|
|
|
6271
6574
|
match in --verbose output; full payload in --json).`;
|
|
6272
6575
|
function registerCodeGrepCommand(pkgCommand) {
|
|
6273
6576
|
return pkgCommand.command("grep").summary("Deterministic text grep over indexed dependency source").description(PKG_GREP_DESCRIPTION).argument("[spec-or-pattern]", "Spec mode: package spec (e.g. npm:express). Repo mode (with --repo-url): the pattern.").argument("[pattern-or-prefix]", "Spec mode: the pattern. Repo mode: optional path-prefix.").argument("[path-prefix]", "Spec mode only: optional path-prefix. Ignored with --repo-url.").option("--repo-url <url>", "Repository URL addressing (requires --git-ref)").option("--git-ref <ref>", "Tag, commit, branch, or HEAD. Required with --repo-url.").option("--path <path>", "Exact file path to grep").option("--glob <glob>", "Glob scope (repeatable)", collectRepeatable2, []).option("--ext <ext>", "Extension filter without leading dot (repeatable)", collectRepeatable2, []).option("--regex", "Interpret the pattern as RE2 regex").option("--case-sensitive", "Enable ASCII case-sensitive matching").option("-C, --context <n>", "Context lines before and after each match (0-10)").option("-B, --before-context <n>", "Context lines before each match (0-10)").option("-A, --after-context <n>", "Context lines after each match (0-10)").option("--exclude-docs", "Skip files classified as documentation").option("--exclude-tests", "Skip files classified as tests").option("--limit <n>", "Max matches to return on this page (1-1000, default 50)").option("--per-file-limit <n>", "Cap matches per file within this page (0-1000, 0 = unlimited)").option("--cursor <cursor>", "Opaque nextCursor from a previous grep result").option("--symbol-field <field>", `Repeatable; surfaces in --json and under each --verbose match. ${GREP_REPO_SYMBOL_FIELDS_NOTE}`, collectRepeatable2, []).option("--wait <ms>", `Indexing wait timeout (0-${MAX_WAIT_TIMEOUT_MS}, default ${DEFAULT_WAIT_TIMEOUT_MS})`).option("-v, --verbose", "Render grouped output with file headers").option("--json", "Emit the JSON envelope").action(async (arg1, arg2, arg3, options) => {
|
|
6274
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
6577
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-k35egwwf.js");
|
|
6275
6578
|
const deps = await createContainer2();
|
|
6276
6579
|
await pkgGrepAction(arg1, arg2, arg3, options, {
|
|
6277
6580
|
codeNavigationService: deps.codeNavigationService,
|
|
@@ -6282,6 +6585,36 @@ function registerCodeGrepCommand(pkgCommand) {
|
|
|
6282
6585
|
});
|
|
6283
6586
|
}
|
|
6284
6587
|
|
|
6588
|
+
// src/shared/read-file-error.ts
|
|
6589
|
+
function withReadFileRecovery(mapped, requestedPath) {
|
|
6590
|
+
if (mapped.code !== "FILE_NOT_FOUND" && mapped.code !== "NOT_FOUND") {
|
|
6591
|
+
return mapped;
|
|
6592
|
+
}
|
|
6593
|
+
return {
|
|
6594
|
+
...mapped,
|
|
6595
|
+
details: {
|
|
6596
|
+
...mapped.details,
|
|
6597
|
+
action: buildReadFileNotFoundAction(requestedPath)
|
|
6598
|
+
}
|
|
6599
|
+
};
|
|
6600
|
+
}
|
|
6601
|
+
function buildReadFileNotFoundAction(requestedPath) {
|
|
6602
|
+
const prefix = buildPathPrefixSuggestion(requestedPath);
|
|
6603
|
+
return "`code_read` reads files only, not directories. " + `Use \`code_files\` with \`path_prefix: ${JSON.stringify(prefix)}\` ` + "to list candidate files, then pass an emitted `path` back to `code_read`.";
|
|
6604
|
+
}
|
|
6605
|
+
function buildPathPrefixSuggestion(requestedPath) {
|
|
6606
|
+
const trimmed = requestedPath.trim();
|
|
6607
|
+
if (trimmed === "")
|
|
6608
|
+
return "";
|
|
6609
|
+
if (trimmed.endsWith("/"))
|
|
6610
|
+
return trimmed;
|
|
6611
|
+
const slash = trimmed.lastIndexOf("/");
|
|
6612
|
+
const basename = slash === -1 ? trimmed : trimmed.slice(slash + 1);
|
|
6613
|
+
if (!basename.includes("."))
|
|
6614
|
+
return `${trimmed}/`;
|
|
6615
|
+
return slash === -1 ? "" : trimmed.slice(0, slash + 1);
|
|
6616
|
+
}
|
|
6617
|
+
|
|
6285
6618
|
// src/shared/read-file-request.ts
|
|
6286
6619
|
var WAIT_MIN3 = 0;
|
|
6287
6620
|
var WAIT_MAX2 = 60000;
|
|
@@ -6290,6 +6623,9 @@ function buildReadFileParams(input) {
|
|
|
6290
6623
|
if (!filePath) {
|
|
6291
6624
|
throw new InvalidPackageSpecError("`file_path` is required — pass the path to the file within the package or repo.");
|
|
6292
6625
|
}
|
|
6626
|
+
if (filePath.endsWith("/")) {
|
|
6627
|
+
throw new InvalidPackageSpecError(`\`file_path\` must be an exact file path, not a directory prefix. Use \`code_files\` with \`path_prefix: ${JSON.stringify(filePath)}\` to list files, then pass an emitted \`path\` to \`code_read\`.`);
|
|
6628
|
+
}
|
|
6293
6629
|
const startLine = normaliseLine(input.startLine, "start_line");
|
|
6294
6630
|
const endLine = normaliseLine(input.endLine, "end_line");
|
|
6295
6631
|
if (startLine !== undefined && endLine !== undefined && startLine > endLine) {
|
|
@@ -6323,118 +6659,10 @@ function normaliseWaitTimeoutMs2(raw) {
|
|
|
6323
6659
|
return raw;
|
|
6324
6660
|
}
|
|
6325
6661
|
|
|
6326
|
-
// src/shared/read-file-response.ts
|
|
6327
|
-
function buildReadFileSuccessPayload(result, options) {
|
|
6328
|
-
const envelope = {
|
|
6329
|
-
path: result.filePath ?? options.requestedFilePath
|
|
6330
|
-
};
|
|
6331
|
-
if (options.registry)
|
|
6332
|
-
envelope.registry = options.registry;
|
|
6333
|
-
if (options.name)
|
|
6334
|
-
envelope.name = options.name;
|
|
6335
|
-
if (options.repoUrl)
|
|
6336
|
-
envelope.repoUrl = options.repoUrl;
|
|
6337
|
-
if (options.gitRef)
|
|
6338
|
-
envelope.gitRef = options.gitRef;
|
|
6339
|
-
if (result.language != null)
|
|
6340
|
-
envelope.language = result.language;
|
|
6341
|
-
if (result.totalLines != null)
|
|
6342
|
-
envelope.totalLines = result.totalLines;
|
|
6343
|
-
if (result.startLine != null)
|
|
6344
|
-
envelope.startLine = result.startLine;
|
|
6345
|
-
if (result.endLine != null)
|
|
6346
|
-
envelope.endLine = result.endLine;
|
|
6347
|
-
if (result.isBinary) {
|
|
6348
|
-
envelope.isBinary = true;
|
|
6349
|
-
} else if (result.content != null) {
|
|
6350
|
-
envelope.content = result.content;
|
|
6351
|
-
}
|
|
6352
|
-
return envelope;
|
|
6353
|
-
}
|
|
6354
|
-
function formatReadFileTerminal(envelope, options) {
|
|
6355
|
-
const verbose = options.verbose ?? false;
|
|
6356
|
-
if (envelope.isBinary) {
|
|
6357
|
-
return formatBinary(envelope, options, verbose);
|
|
6358
|
-
}
|
|
6359
|
-
if (envelope.content == null) {
|
|
6360
|
-
return formatNoContent(envelope, options, verbose);
|
|
6361
|
-
}
|
|
6362
|
-
if (!verbose) {
|
|
6363
|
-
return envelope.content;
|
|
6364
|
-
}
|
|
6365
|
-
return formatVerboseBody(envelope, options);
|
|
6366
|
-
}
|
|
6367
|
-
function formatBinary(envelope, options, verbose) {
|
|
6368
|
-
const sentinel = dim("Binary file — cannot display as text.", options.useColors);
|
|
6369
|
-
if (verbose) {
|
|
6370
|
-
return `${buildHeader9(envelope, options)}
|
|
6371
|
-
|
|
6372
|
-
${sentinel}
|
|
6373
|
-
`;
|
|
6374
|
-
}
|
|
6375
|
-
return `${sentinel}
|
|
6376
|
-
`;
|
|
6377
|
-
}
|
|
6378
|
-
function formatNoContent(envelope, options, verbose) {
|
|
6379
|
-
const sentinel = dim("(no content returned)", options.useColors);
|
|
6380
|
-
if (verbose) {
|
|
6381
|
-
return `${buildHeader9(envelope, options)}
|
|
6382
|
-
|
|
6383
|
-
${sentinel}
|
|
6384
|
-
`;
|
|
6385
|
-
}
|
|
6386
|
-
return `${sentinel}
|
|
6387
|
-
`;
|
|
6388
|
-
}
|
|
6389
|
-
function formatVerboseBody(envelope, options) {
|
|
6390
|
-
const lines = [];
|
|
6391
|
-
lines.push(buildHeader9(envelope, options));
|
|
6392
|
-
lines.push("");
|
|
6393
|
-
const content = envelope.content ?? "";
|
|
6394
|
-
const bodyLines = content.split(`
|
|
6395
|
-
`);
|
|
6396
|
-
if (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === "") {
|
|
6397
|
-
bodyLines.pop();
|
|
6398
|
-
}
|
|
6399
|
-
const startLine = envelope.startLine ?? 1;
|
|
6400
|
-
const endLine = startLine + bodyLines.length - 1;
|
|
6401
|
-
const gutterWidth = String(endLine).length;
|
|
6402
|
-
for (let i = 0;i < bodyLines.length; i++) {
|
|
6403
|
-
const lineNumber = startLine + i;
|
|
6404
|
-
const gutter = dim(String(lineNumber).padStart(gutterWidth, " "), options.useColors);
|
|
6405
|
-
lines.push(`${gutter} ${bodyLines[i]}`);
|
|
6406
|
-
}
|
|
6407
|
-
if (envelope.hint) {
|
|
6408
|
-
lines.push("");
|
|
6409
|
-
lines.push(dim(envelope.hint, options.useColors));
|
|
6410
|
-
}
|
|
6411
|
-
lines.push("");
|
|
6412
|
-
return lines.join(`
|
|
6413
|
-
`);
|
|
6414
|
-
}
|
|
6415
|
-
function buildHeader9(envelope, options) {
|
|
6416
|
-
const parts = [envelope.path];
|
|
6417
|
-
if (envelope.language)
|
|
6418
|
-
parts.push(envelope.language);
|
|
6419
|
-
const rangeLabel = buildRangeLabel(envelope);
|
|
6420
|
-
if (rangeLabel)
|
|
6421
|
-
parts.push(rangeLabel);
|
|
6422
|
-
return colorize(parts.join(" · "), "bold", options.useColors);
|
|
6423
|
-
}
|
|
6424
|
-
function buildRangeLabel(envelope) {
|
|
6425
|
-
const { startLine, endLine, totalLines } = envelope;
|
|
6426
|
-
if (startLine != null && endLine != null) {
|
|
6427
|
-
return totalLines != null ? `lines ${startLine}-${endLine} of ${totalLines}` : `lines ${startLine}-${endLine}`;
|
|
6428
|
-
}
|
|
6429
|
-
if (totalLines != null) {
|
|
6430
|
-
return `${totalLines} lines`;
|
|
6431
|
-
}
|
|
6432
|
-
return;
|
|
6433
|
-
}
|
|
6434
|
-
|
|
6435
6662
|
// src/commands/code/read.ts
|
|
6436
6663
|
async function pkgReadAction(firstArg, secondArg, options, deps) {
|
|
6437
6664
|
requireAuth(deps);
|
|
6665
|
+
let requestedFilePath = "";
|
|
6438
6666
|
try {
|
|
6439
6667
|
if (!deps.codeNavigationUrl || !deps.codeNavigationService) {
|
|
6440
6668
|
throw new InvalidPackageSpecError("Code navigation is not configured for this environment.");
|
|
@@ -6446,6 +6674,7 @@ async function pkgReadAction(firstArg, secondArg, options, deps) {
|
|
|
6446
6674
|
}
|
|
6447
6675
|
const target = resolveCliCodeNavTarget(spec, options);
|
|
6448
6676
|
const pathWithRange = parsePathWithOptionalRange(path.trim());
|
|
6677
|
+
requestedFilePath = pathWithRange.filePath;
|
|
6449
6678
|
const range = resolveLineRange(options, pathWithRange);
|
|
6450
6679
|
const wait = parseIntCliOption(options.wait, "--wait", 0, MAX_WAIT_TIMEOUT_MS);
|
|
6451
6680
|
const build = buildReadFileParams({
|
|
@@ -6472,7 +6701,7 @@ async function pkgReadAction(firstArg, secondArg, options, deps) {
|
|
|
6472
6701
|
verbose: options.verbose ?? false
|
|
6473
6702
|
}));
|
|
6474
6703
|
} catch (error2) {
|
|
6475
|
-
handleCodeNavCommandError(error2, options.json ?? false, formatFileErrorWithFilesHint);
|
|
6704
|
+
handleCodeNavCommandError(error2, options.json ?? false, formatFileErrorWithFilesHint, 1, (mapped) => withReadFileRecovery(mapped, requestedFilePath));
|
|
6476
6705
|
}
|
|
6477
6706
|
}
|
|
6478
6707
|
function resolvePositionals3(firstArg, secondArg, hasRepoUrl) {
|
|
@@ -6805,7 +7034,7 @@ function registerExampleCommand(program) {
|
|
|
6805
7034
|
});
|
|
6806
7035
|
}
|
|
6807
7036
|
async function loadContainer() {
|
|
6808
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
7037
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-k35egwwf.js");
|
|
6809
7038
|
return createContainer2();
|
|
6810
7039
|
}
|
|
6811
7040
|
// src/commands/feedback.ts
|
|
@@ -6833,17 +7062,25 @@ async function feedbackAction(solutionId, options, deps) {
|
|
|
6833
7062
|
process.exit(1);
|
|
6834
7063
|
}
|
|
6835
7064
|
}
|
|
6836
|
-
var FEEDBACK_DESCRIPTION = `Submit feedback on a
|
|
7065
|
+
var FEEDBACK_DESCRIPTION = `Submit feedback on a tool result or the GitHits experience.
|
|
7066
|
+
|
|
7067
|
+
Two modes:
|
|
7068
|
+
- Solution-tied: pass the [solution_id] from a prior 'githits example'
|
|
7069
|
+
result (shown at the bottom of the markdown / under 'solution_id'
|
|
7070
|
+
in --json) to anchor feedback to that specific result.
|
|
7071
|
+
- Generic: omit [solution_id] to send feedback about any command
|
|
7072
|
+
(search, pkg, docs, code) or the overall experience. A --message
|
|
7073
|
+
is strongly recommended here.
|
|
6837
7074
|
|
|
6838
|
-
|
|
6839
|
-
feedback or --reject for negative. Optionally add a message.
|
|
7075
|
+
Use --accept for positive feedback or --reject for negative.
|
|
6840
7076
|
|
|
6841
7077
|
Examples:
|
|
6842
7078
|
githits feedback abc123 --accept
|
|
6843
7079
|
githits feedback abc123 --reject -m "Example was outdated"
|
|
6844
|
-
githits feedback
|
|
7080
|
+
githits feedback --accept -m "code_grep regex is fast on npm:lodash"
|
|
7081
|
+
githits feedback --reject -m "search missing kotlin support"`;
|
|
6845
7082
|
function registerFeedbackCommand(program) {
|
|
6846
|
-
program.command("feedback").summary("Submit feedback on a
|
|
7083
|
+
program.command("feedback").summary("Submit feedback on a tool result or the GitHits experience").description(FEEDBACK_DESCRIPTION).argument("[solution_id]", "Solution ID from a prior 'githits example' result (omit for generic feedback)").addOption(new Option2("--accept", "Mark as helpful").conflicts("reject")).addOption(new Option2("--reject", "Mark as unhelpful").conflicts("accept")).option("-m, --message <text>", "Feedback explanation").option("--json", "Output as JSON for piping").action(async (solutionId, options) => {
|
|
6847
7084
|
try {
|
|
6848
7085
|
const deps = await createContainer();
|
|
6849
7086
|
await feedbackAction(solutionId, options, deps);
|
|
@@ -7053,6 +7290,39 @@ function mergeServerConfig(existingContent, serversKey, serverName, serverConfig
|
|
|
7053
7290
|
`
|
|
7054
7291
|
};
|
|
7055
7292
|
}
|
|
7293
|
+
function removeServerConfig(existingContent, serversKey, serverName) {
|
|
7294
|
+
const parsedConfig = parseConfigObject(existingContent);
|
|
7295
|
+
if (parsedConfig.format === "invalid") {
|
|
7296
|
+
return {
|
|
7297
|
+
status: "parse_error",
|
|
7298
|
+
error: parsedConfig.error
|
|
7299
|
+
};
|
|
7300
|
+
}
|
|
7301
|
+
const config = parsedConfig.value;
|
|
7302
|
+
const servers = config[serversKey];
|
|
7303
|
+
if (servers === undefined) {
|
|
7304
|
+
return { status: "not_configured" };
|
|
7305
|
+
}
|
|
7306
|
+
if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
|
|
7307
|
+
return {
|
|
7308
|
+
status: "parse_error",
|
|
7309
|
+
error: `"${serversKey}" is not a JSON object`
|
|
7310
|
+
};
|
|
7311
|
+
}
|
|
7312
|
+
const serversObj = servers;
|
|
7313
|
+
const matchingKeys = getMatchingServerKeys(serversObj, serverName);
|
|
7314
|
+
if (matchingKeys.length === 0) {
|
|
7315
|
+
return { status: "not_configured" };
|
|
7316
|
+
}
|
|
7317
|
+
for (const key of matchingKeys) {
|
|
7318
|
+
delete serversObj[key];
|
|
7319
|
+
}
|
|
7320
|
+
return {
|
|
7321
|
+
status: "removed",
|
|
7322
|
+
content: `${JSON.stringify(config, null, 2)}
|
|
7323
|
+
`
|
|
7324
|
+
};
|
|
7325
|
+
}
|
|
7056
7326
|
function formatSetupPreview(config) {
|
|
7057
7327
|
if (config.method === "cli") {
|
|
7058
7328
|
return config.commands.map((cmd) => `Will run: ${cmd.command} ${cmd.args.join(" ")}`).join(`
|
|
@@ -7063,6 +7333,13 @@ function formatSetupPreview(config) {
|
|
|
7063
7333
|
|
|
7064
7334
|
${snippet}`;
|
|
7065
7335
|
}
|
|
7336
|
+
function formatUninstallPreview(config) {
|
|
7337
|
+
if (config.method === "cli") {
|
|
7338
|
+
return config.commands.map((cmd) => `Will run: ${cmd.command} ${cmd.args.join(" ")}`).join(`
|
|
7339
|
+
`);
|
|
7340
|
+
}
|
|
7341
|
+
return `Will remove ${config.serverName} from ${config.configPath}`;
|
|
7342
|
+
}
|
|
7066
7343
|
async function isAlreadyConfigured(config, fs) {
|
|
7067
7344
|
try {
|
|
7068
7345
|
const content = await fs.readFile(config.configPath);
|
|
@@ -7085,16 +7362,48 @@ async function isAlreadyConfigured(config, fs) {
|
|
|
7085
7362
|
return false;
|
|
7086
7363
|
}
|
|
7087
7364
|
}
|
|
7365
|
+
async function getConfigUninstallCheckStatus(config, fs) {
|
|
7366
|
+
try {
|
|
7367
|
+
const content = await fs.readFile(config.configPath);
|
|
7368
|
+
const parsedConfig = parseConfigObject(content);
|
|
7369
|
+
if (parsedConfig.format === "invalid") {
|
|
7370
|
+
return {
|
|
7371
|
+
status: "failed",
|
|
7372
|
+
message: `Cannot parse ${config.configPath}: ${parsedConfig.error}. File left unchanged.`
|
|
7373
|
+
};
|
|
7374
|
+
}
|
|
7375
|
+
const servers = parsedConfig.value[config.serversKey];
|
|
7376
|
+
if (servers === undefined) {
|
|
7377
|
+
return { status: "not_configured" };
|
|
7378
|
+
}
|
|
7379
|
+
if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
|
|
7380
|
+
return {
|
|
7381
|
+
status: "failed",
|
|
7382
|
+
message: `Cannot parse ${config.configPath}: "${config.serversKey}" is not a JSON object. File left unchanged.`
|
|
7383
|
+
};
|
|
7384
|
+
}
|
|
7385
|
+
const hasEntry = getMatchingServerKeys(servers, config.serverName).length > 0;
|
|
7386
|
+
return { status: hasEntry ? "configured" : "not_configured" };
|
|
7387
|
+
} catch (err) {
|
|
7388
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
7389
|
+
return { status: "not_configured" };
|
|
7390
|
+
}
|
|
7391
|
+
return {
|
|
7392
|
+
status: "failed",
|
|
7393
|
+
message: `Cannot read ${config.configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
7394
|
+
};
|
|
7395
|
+
}
|
|
7396
|
+
}
|
|
7088
7397
|
async function getCliCheckStatus(check, execService) {
|
|
7089
7398
|
try {
|
|
7090
7399
|
const result = await execService.exec(check.command, check.args);
|
|
7091
|
-
if (check.requireExitCodeZero && result.exitCode !== 0) {
|
|
7092
|
-
return "probe_failed";
|
|
7093
|
-
}
|
|
7094
7400
|
const combined = `${result.stdout} ${result.stderr}`;
|
|
7095
7401
|
if (check.notConfiguredPattern?.test(combined)) {
|
|
7096
7402
|
return "not_configured";
|
|
7097
7403
|
}
|
|
7404
|
+
if (check.requireExitCodeZero && result.exitCode !== 0) {
|
|
7405
|
+
return "probe_failed";
|
|
7406
|
+
}
|
|
7098
7407
|
if (check.configuredPattern) {
|
|
7099
7408
|
return check.configuredPattern.test(combined) ? "configured" : "not_configured";
|
|
7100
7409
|
}
|
|
@@ -7112,9 +7421,18 @@ var ALREADY_EXISTS_PATTERNS = [
|
|
|
7112
7421
|
/already added/i,
|
|
7113
7422
|
/extension\s+"githits"\s+is\s+already\s+installed/i
|
|
7114
7423
|
];
|
|
7424
|
+
var ALREADY_ABSENT_PATTERNS = [
|
|
7425
|
+
/(?:plugin|extension|server|mcp server)\s+["']?githits["']?\s+(?:was\s+)?not\s+found/i,
|
|
7426
|
+
/["']?githits["']?\s+(?:plugin|extension|server)?\s*(?:does\s+not\s+exist|is\s+not\s+installed|not\s+installed)/i,
|
|
7427
|
+
/unknown\s+(?:plugin|extension|server)\s+["']?githits["']?/i,
|
|
7428
|
+
/marketplace\s+["']?githits-plugins["']?\s+(?:was\s+)?not\s+found/i
|
|
7429
|
+
];
|
|
7115
7430
|
function isAlreadyConfiguredOutput(output) {
|
|
7116
7431
|
return ALREADY_EXISTS_PATTERNS.some((pattern) => pattern.test(output));
|
|
7117
7432
|
}
|
|
7433
|
+
function isAlreadyAbsentOutput(output) {
|
|
7434
|
+
return ALREADY_ABSENT_PATTERNS.some((pattern) => pattern.test(output));
|
|
7435
|
+
}
|
|
7118
7436
|
async function executeCliCommand(cmd, execService) {
|
|
7119
7437
|
try {
|
|
7120
7438
|
const result = await execService.exec(cmd.command, cmd.args);
|
|
@@ -7146,6 +7464,37 @@ async function executeCliCommand(cmd, execService) {
|
|
|
7146
7464
|
};
|
|
7147
7465
|
}
|
|
7148
7466
|
}
|
|
7467
|
+
async function executeCliUninstallCommand(cmd, execService) {
|
|
7468
|
+
try {
|
|
7469
|
+
const result = await execService.exec(cmd.command, cmd.args);
|
|
7470
|
+
const combined = `${result.stdout} ${result.stderr}`;
|
|
7471
|
+
if (result.exitCode === 0) {
|
|
7472
|
+
return { status: "removed", message: "Removed successfully" };
|
|
7473
|
+
}
|
|
7474
|
+
if (isAlreadyAbsentOutput(combined)) {
|
|
7475
|
+
return {
|
|
7476
|
+
status: "not_configured",
|
|
7477
|
+
message: `GitHits not configured via ${cmd.command}`
|
|
7478
|
+
};
|
|
7479
|
+
}
|
|
7480
|
+
const detail = result.stderr.trim() || result.stdout.trim();
|
|
7481
|
+
return {
|
|
7482
|
+
status: "failed",
|
|
7483
|
+
message: `Command exited with code ${result.exitCode}${detail ? `: ${detail}` : ""}`
|
|
7484
|
+
};
|
|
7485
|
+
} catch (err) {
|
|
7486
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
7487
|
+
return {
|
|
7488
|
+
status: "failed",
|
|
7489
|
+
message: `"${cmd.command}" not found on PATH. Install it or remove GitHits manually.`
|
|
7490
|
+
};
|
|
7491
|
+
}
|
|
7492
|
+
return {
|
|
7493
|
+
status: "failed",
|
|
7494
|
+
message: `Failed to run command: ${err instanceof Error ? err.message : String(err)}`
|
|
7495
|
+
};
|
|
7496
|
+
}
|
|
7497
|
+
}
|
|
7149
7498
|
async function executeCliSetup(setup, execService) {
|
|
7150
7499
|
let anyAlreadyConfigured = false;
|
|
7151
7500
|
for (const cmd of setup.commands) {
|
|
@@ -7165,6 +7514,52 @@ async function executeCliSetup(setup, execService) {
|
|
|
7165
7514
|
}
|
|
7166
7515
|
return { status: "success", message: "Configured successfully" };
|
|
7167
7516
|
}
|
|
7517
|
+
async function executeCliUninstall(uninstall, execService) {
|
|
7518
|
+
if (uninstall.commands.length === 0) {
|
|
7519
|
+
return {
|
|
7520
|
+
status: "failed",
|
|
7521
|
+
message: "No uninstall commands configured."
|
|
7522
|
+
};
|
|
7523
|
+
}
|
|
7524
|
+
let anyRemoved = false;
|
|
7525
|
+
let anyNotConfigured = false;
|
|
7526
|
+
const warnings = [];
|
|
7527
|
+
for (let index = 0;index < uninstall.commands.length; index += 1) {
|
|
7528
|
+
const cmd = uninstall.commands[index];
|
|
7529
|
+
const result = await executeCliUninstallCommand(cmd, execService);
|
|
7530
|
+
if (result.status === "failed") {
|
|
7531
|
+
if (anyRemoved) {
|
|
7532
|
+
warnings.push(result.message);
|
|
7533
|
+
continue;
|
|
7534
|
+
}
|
|
7535
|
+
return result;
|
|
7536
|
+
}
|
|
7537
|
+
if (result.status === "removed") {
|
|
7538
|
+
anyRemoved = true;
|
|
7539
|
+
}
|
|
7540
|
+
if (result.status === "not_configured") {
|
|
7541
|
+
if (anyRemoved) {
|
|
7542
|
+
warnings.push(result.message);
|
|
7543
|
+
continue;
|
|
7544
|
+
}
|
|
7545
|
+
anyNotConfigured = true;
|
|
7546
|
+
}
|
|
7547
|
+
}
|
|
7548
|
+
if (anyRemoved) {
|
|
7549
|
+
return {
|
|
7550
|
+
status: "removed",
|
|
7551
|
+
message: "Removed successfully",
|
|
7552
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
7553
|
+
};
|
|
7554
|
+
}
|
|
7555
|
+
if (anyNotConfigured) {
|
|
7556
|
+
return {
|
|
7557
|
+
status: "not_configured",
|
|
7558
|
+
message: `GitHits not configured via ${uninstall.commands[0]?.command}`
|
|
7559
|
+
};
|
|
7560
|
+
}
|
|
7561
|
+
return { status: "removed", message: "Removed successfully" };
|
|
7562
|
+
}
|
|
7168
7563
|
async function executeConfigFileSetup(setup, fs) {
|
|
7169
7564
|
try {
|
|
7170
7565
|
const parentDir = fs.getDirname(setup.configPath);
|
|
@@ -7208,6 +7603,51 @@ async function executeConfigFileSetup(setup, fs) {
|
|
|
7208
7603
|
};
|
|
7209
7604
|
}
|
|
7210
7605
|
}
|
|
7606
|
+
async function executeConfigFileUninstall(setup, fs) {
|
|
7607
|
+
try {
|
|
7608
|
+
let existingContent = "";
|
|
7609
|
+
try {
|
|
7610
|
+
existingContent = await fs.readFile(setup.configPath);
|
|
7611
|
+
} catch (err) {
|
|
7612
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
7613
|
+
return {
|
|
7614
|
+
status: "not_configured",
|
|
7615
|
+
message: `GitHits not configured in ${setup.configPath}`
|
|
7616
|
+
};
|
|
7617
|
+
}
|
|
7618
|
+
return {
|
|
7619
|
+
status: "failed",
|
|
7620
|
+
message: `Cannot read ${setup.configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
7621
|
+
};
|
|
7622
|
+
}
|
|
7623
|
+
const result = removeServerConfig(existingContent, setup.serversKey, setup.serverName);
|
|
7624
|
+
if (result.status === "not_configured") {
|
|
7625
|
+
return {
|
|
7626
|
+
status: "not_configured",
|
|
7627
|
+
message: `GitHits not configured in ${setup.configPath}`
|
|
7628
|
+
};
|
|
7629
|
+
}
|
|
7630
|
+
if (result.status === "parse_error") {
|
|
7631
|
+
return {
|
|
7632
|
+
status: "failed",
|
|
7633
|
+
message: `Cannot parse ${setup.configPath}: ${result.error}. File left unchanged.`
|
|
7634
|
+
};
|
|
7635
|
+
}
|
|
7636
|
+
await fs.atomicWriteFile(setup.configPath, result.content);
|
|
7637
|
+
return { status: "removed", message: "Removed successfully" };
|
|
7638
|
+
} catch (err) {
|
|
7639
|
+
if (err instanceof Error && "code" in err && err.code === "EACCES") {
|
|
7640
|
+
return {
|
|
7641
|
+
status: "failed",
|
|
7642
|
+
message: `Permission denied writing to ${setup.configPath}. Check file permissions.`
|
|
7643
|
+
};
|
|
7644
|
+
}
|
|
7645
|
+
return {
|
|
7646
|
+
status: "failed",
|
|
7647
|
+
message: `Failed to uninstall: ${err instanceof Error ? err.message : String(err)}`
|
|
7648
|
+
};
|
|
7649
|
+
}
|
|
7650
|
+
}
|
|
7211
7651
|
|
|
7212
7652
|
// src/commands/init/agent-definitions.ts
|
|
7213
7653
|
var GITHITS_SERVER_NAME = "GitHits";
|
|
@@ -7217,6 +7657,10 @@ var GITHITS_MCP_INVOCATION = [
|
|
|
7217
7657
|
GITHITS_MCP_COMMAND,
|
|
7218
7658
|
...GITHITS_MCP_ARGS
|
|
7219
7659
|
];
|
|
7660
|
+
var CLAUDE_GITHITS_PLUGIN = "githits";
|
|
7661
|
+
var CLAUDE_GITHITS_MARKETPLACE = "githits-plugins";
|
|
7662
|
+
var CLAUDE_GITHITS_PLUGIN_REF = `${CLAUDE_GITHITS_PLUGIN}@${CLAUDE_GITHITS_MARKETPLACE}`;
|
|
7663
|
+
var CLAUDE_GITHITS_MARKETPLACE_SOURCE = "githits-com/githits-cli";
|
|
7220
7664
|
function getAppDataPath(fs, appName) {
|
|
7221
7665
|
const home = fs.getHomeDir();
|
|
7222
7666
|
switch (process.platform) {
|
|
@@ -7274,18 +7718,36 @@ var claudeCode = {
|
|
|
7274
7718
|
commands: [
|
|
7275
7719
|
{
|
|
7276
7720
|
command: "claude",
|
|
7277
|
-
args: [
|
|
7721
|
+
args: [
|
|
7722
|
+
"plugin",
|
|
7723
|
+
"marketplace",
|
|
7724
|
+
"add",
|
|
7725
|
+
CLAUDE_GITHITS_MARKETPLACE_SOURCE
|
|
7726
|
+
]
|
|
7278
7727
|
},
|
|
7279
7728
|
{
|
|
7280
7729
|
command: "claude",
|
|
7281
|
-
args: ["plugin", "install",
|
|
7730
|
+
args: ["plugin", "install", CLAUDE_GITHITS_PLUGIN_REF]
|
|
7282
7731
|
}
|
|
7283
7732
|
],
|
|
7284
7733
|
checkCommand: {
|
|
7285
7734
|
command: "claude",
|
|
7286
7735
|
args: ["plugin", "list"],
|
|
7287
|
-
configuredPattern: /githits/i
|
|
7736
|
+
configuredPattern: /(^|\s)githits@githits-plugins\b/i
|
|
7288
7737
|
}
|
|
7738
|
+
}),
|
|
7739
|
+
getUninstallConfig: () => ({
|
|
7740
|
+
method: "cli",
|
|
7741
|
+
commands: [
|
|
7742
|
+
{
|
|
7743
|
+
command: "claude",
|
|
7744
|
+
args: ["plugin", "uninstall", CLAUDE_GITHITS_PLUGIN]
|
|
7745
|
+
},
|
|
7746
|
+
{
|
|
7747
|
+
command: "claude",
|
|
7748
|
+
args: ["plugin", "marketplace", "remove", CLAUDE_GITHITS_MARKETPLACE]
|
|
7749
|
+
}
|
|
7750
|
+
]
|
|
7289
7751
|
})
|
|
7290
7752
|
};
|
|
7291
7753
|
var cursor = {
|
|
@@ -7371,8 +7833,17 @@ var codexCli = {
|
|
|
7371
7833
|
checkCommand: {
|
|
7372
7834
|
command: "codex",
|
|
7373
7835
|
args: ["mcp", "list"],
|
|
7374
|
-
configuredPattern:
|
|
7836
|
+
configuredPattern: /^\s*githits\b/im
|
|
7375
7837
|
}
|
|
7838
|
+
}),
|
|
7839
|
+
getUninstallConfig: () => ({
|
|
7840
|
+
method: "cli",
|
|
7841
|
+
commands: [
|
|
7842
|
+
{
|
|
7843
|
+
command: "codex",
|
|
7844
|
+
args: ["mcp", "remove", "githits"]
|
|
7845
|
+
}
|
|
7846
|
+
]
|
|
7376
7847
|
})
|
|
7377
7848
|
};
|
|
7378
7849
|
var vscode = {
|
|
@@ -7440,6 +7911,15 @@ var geminiCli = {
|
|
|
7440
7911
|
notConfiguredPattern: /not installed/i,
|
|
7441
7912
|
requireExitCodeZero: true
|
|
7442
7913
|
}
|
|
7914
|
+
}),
|
|
7915
|
+
getUninstallConfig: () => ({
|
|
7916
|
+
method: "cli",
|
|
7917
|
+
commands: [
|
|
7918
|
+
{
|
|
7919
|
+
command: "gemini",
|
|
7920
|
+
args: ["extensions", "uninstall", "githits"]
|
|
7921
|
+
}
|
|
7922
|
+
]
|
|
7443
7923
|
})
|
|
7444
7924
|
};
|
|
7445
7925
|
async function isGeminiExtensionInstalledFromFilesystem(fs) {
|
|
@@ -7591,6 +8071,90 @@ async function verifyAgentConfigured(agent, fileSystemService, execService) {
|
|
|
7591
8071
|
message: `${agent.name} verification failed: agent not detected after setup.`
|
|
7592
8072
|
};
|
|
7593
8073
|
}
|
|
8074
|
+
async function verifyAgentUnconfigured(agent, fileSystemService, execService) {
|
|
8075
|
+
const postCheck = await scanAgents([agent], fileSystemService, execService);
|
|
8076
|
+
if (postCheck.needsSetup.some((a) => a.id === agent.id)) {
|
|
8077
|
+
return { ok: true };
|
|
8078
|
+
}
|
|
8079
|
+
if (postCheck.notDetected.some((a) => a.id === agent.id)) {
|
|
8080
|
+
return {
|
|
8081
|
+
ok: false,
|
|
8082
|
+
message: `${agent.name} verification failed: agent was not detected after uninstall, so removal could not be confirmed.`
|
|
8083
|
+
};
|
|
8084
|
+
}
|
|
8085
|
+
return {
|
|
8086
|
+
ok: false,
|
|
8087
|
+
message: `${agent.name} verification failed: still configured after uninstall.`
|
|
8088
|
+
};
|
|
8089
|
+
}
|
|
8090
|
+
async function scanCliAgentForUninstall(agent, fileSystemService, execService) {
|
|
8091
|
+
const config = agent.getSetupConfig(fileSystemService);
|
|
8092
|
+
if (config.method !== "cli" || !config.checkCommand) {
|
|
8093
|
+
return {
|
|
8094
|
+
status: "failed",
|
|
8095
|
+
message: `${agent.name} does not have a verified uninstall check command.`
|
|
8096
|
+
};
|
|
8097
|
+
}
|
|
8098
|
+
const checkStatus = await getCliCheckStatus(config.checkCommand, execService);
|
|
8099
|
+
if (checkStatus === "configured") {
|
|
8100
|
+
return "configured";
|
|
8101
|
+
}
|
|
8102
|
+
if (checkStatus === "not_configured") {
|
|
8103
|
+
return "not_configured";
|
|
8104
|
+
}
|
|
8105
|
+
if (agent.id === "gemini-cli" && await isGeminiExtensionInstalledFromFilesystem(fileSystemService)) {
|
|
8106
|
+
return "configured";
|
|
8107
|
+
}
|
|
8108
|
+
return {
|
|
8109
|
+
status: "failed",
|
|
8110
|
+
message: `Cannot inspect ${agent.name}: ${config.checkCommand.command} ${config.checkCommand.args.join(" ")} failed.`
|
|
8111
|
+
};
|
|
8112
|
+
}
|
|
8113
|
+
async function scanAgentsForUninstall(fileSystemService, execService) {
|
|
8114
|
+
const setupScan = await scanAgents(agentDefinitions, fileSystemService, execService);
|
|
8115
|
+
const result = {
|
|
8116
|
+
configured: [],
|
|
8117
|
+
notConfigured: [],
|
|
8118
|
+
notDetected: setupScan.notDetected,
|
|
8119
|
+
failed: []
|
|
8120
|
+
};
|
|
8121
|
+
for (const agent of [
|
|
8122
|
+
...setupScan.alreadyConfigured,
|
|
8123
|
+
...setupScan.needsSetup
|
|
8124
|
+
]) {
|
|
8125
|
+
const config = agent.getSetupConfig(fileSystemService);
|
|
8126
|
+
if (config.method === "config-file") {
|
|
8127
|
+
const check = await getConfigUninstallCheckStatus(config, fileSystemService);
|
|
8128
|
+
if (check.status === "configured") {
|
|
8129
|
+
result.configured.push(agent);
|
|
8130
|
+
} else if (check.status === "failed") {
|
|
8131
|
+
result.failed.push({
|
|
8132
|
+
id: agent.id,
|
|
8133
|
+
name: agent.name,
|
|
8134
|
+
status: "failed",
|
|
8135
|
+
message: check.message
|
|
8136
|
+
});
|
|
8137
|
+
} else {
|
|
8138
|
+
result.notConfigured.push(agent);
|
|
8139
|
+
}
|
|
8140
|
+
} else {
|
|
8141
|
+
const check = await scanCliAgentForUninstall(agent, fileSystemService, execService);
|
|
8142
|
+
if (check === "configured") {
|
|
8143
|
+
result.configured.push(agent);
|
|
8144
|
+
} else if (check === "not_configured") {
|
|
8145
|
+
result.notConfigured.push(agent);
|
|
8146
|
+
} else {
|
|
8147
|
+
result.failed.push({
|
|
8148
|
+
id: agent.id,
|
|
8149
|
+
name: agent.name,
|
|
8150
|
+
status: "failed",
|
|
8151
|
+
message: check.message
|
|
8152
|
+
});
|
|
8153
|
+
}
|
|
8154
|
+
}
|
|
8155
|
+
}
|
|
8156
|
+
return result;
|
|
8157
|
+
}
|
|
7594
8158
|
async function initAction(options, deps) {
|
|
7595
8159
|
const useColors = shouldUseColors();
|
|
7596
8160
|
const { fileSystemService, promptService, execService, createLoginDeps } = deps;
|
|
@@ -7756,6 +8320,147 @@ async function initAction(options, deps) {
|
|
|
7756
8320
|
}
|
|
7757
8321
|
console.log();
|
|
7758
8322
|
}
|
|
8323
|
+
async function initUninstallAction(options, deps) {
|
|
8324
|
+
const useColors = shouldUseColors();
|
|
8325
|
+
const { fileSystemService, promptService, execService } = deps;
|
|
8326
|
+
console.log(`
|
|
8327
|
+
${colorize("GitHits", "bold", useColors)} — Remove MCP server from your coding agents
|
|
8328
|
+
`);
|
|
8329
|
+
console.log(` Scanning for configured agents...
|
|
8330
|
+
`);
|
|
8331
|
+
const scan = await scanAgentsForUninstall(fileSystemService, execService);
|
|
8332
|
+
for (const agent of scan.configured) {
|
|
8333
|
+
console.log(` ${colorize(`● ${agent.name} — configured`, "cyan", useColors)}`);
|
|
8334
|
+
}
|
|
8335
|
+
for (const agent of scan.notConfigured) {
|
|
8336
|
+
console.log(` ${warning(`${agent.name} — not configured`, useColors)}`);
|
|
8337
|
+
}
|
|
8338
|
+
for (const outcome of scan.failed) {
|
|
8339
|
+
console.log(` ${error(`${outcome.name} — cannot inspect config`, useColors)}`);
|
|
8340
|
+
}
|
|
8341
|
+
for (const agent of scan.notDetected) {
|
|
8342
|
+
console.log(` ${colorize(`${agent.name} — not detected`, "dim", useColors)}`);
|
|
8343
|
+
}
|
|
8344
|
+
console.log();
|
|
8345
|
+
if (scan.configured.length === 0 && scan.failed.length === 0) {
|
|
8346
|
+
console.log(` No GitHits MCP configurations found. Nothing to uninstall.
|
|
8347
|
+
`);
|
|
8348
|
+
return;
|
|
8349
|
+
}
|
|
8350
|
+
const outcomes = [...scan.failed];
|
|
8351
|
+
let alwaysMode = options.yes ?? false;
|
|
8352
|
+
for (const agent of scan.configured) {
|
|
8353
|
+
console.log(` Uninstalling from ${colorize(agent.name, "bold", useColors)}...
|
|
8354
|
+
`);
|
|
8355
|
+
const setupConfig = agent.getSetupConfig(fileSystemService);
|
|
8356
|
+
const uninstallConfig = setupConfig.method === "config-file" ? setupConfig : agent.getUninstallConfig?.(fileSystemService);
|
|
8357
|
+
if (!uninstallConfig) {
|
|
8358
|
+
outcomes.push({
|
|
8359
|
+
id: agent.id,
|
|
8360
|
+
name: agent.name,
|
|
8361
|
+
status: "failed",
|
|
8362
|
+
message: `${agent.name} does not have a verified uninstall command.`
|
|
8363
|
+
});
|
|
8364
|
+
console.log(` ${error(`${agent.name} does not have a verified uninstall command.`, useColors)}
|
|
8365
|
+
`);
|
|
8366
|
+
continue;
|
|
8367
|
+
}
|
|
8368
|
+
const preview = formatUninstallPreview(uninstallConfig);
|
|
8369
|
+
for (const line of preview.split(`
|
|
8370
|
+
`)) {
|
|
8371
|
+
console.log(` ${line}`);
|
|
8372
|
+
}
|
|
8373
|
+
console.log();
|
|
8374
|
+
if (!alwaysMode) {
|
|
8375
|
+
let choice;
|
|
8376
|
+
try {
|
|
8377
|
+
choice = await promptService.confirm3("Proceed?");
|
|
8378
|
+
} catch (err) {
|
|
8379
|
+
if (err instanceof ExitPromptError) {
|
|
8380
|
+
console.log(`
|
|
8381
|
+
Uninstall cancelled.
|
|
8382
|
+
`);
|
|
8383
|
+
return;
|
|
8384
|
+
}
|
|
8385
|
+
throw err;
|
|
8386
|
+
}
|
|
8387
|
+
if (choice === "no") {
|
|
8388
|
+
outcomes.push({ id: agent.id, name: agent.name, status: "skipped" });
|
|
8389
|
+
console.log();
|
|
8390
|
+
continue;
|
|
8391
|
+
}
|
|
8392
|
+
if (choice === "always") {
|
|
8393
|
+
alwaysMode = true;
|
|
8394
|
+
}
|
|
8395
|
+
}
|
|
8396
|
+
let result = uninstallConfig.method === "cli" ? await executeCliUninstall(uninstallConfig, execService) : await executeConfigFileUninstall(uninstallConfig, fileSystemService);
|
|
8397
|
+
if (result.status === "removed") {
|
|
8398
|
+
const verification = await verifyAgentUnconfigured(agent, fileSystemService, execService);
|
|
8399
|
+
if (!verification.ok) {
|
|
8400
|
+
result = {
|
|
8401
|
+
status: "failed",
|
|
8402
|
+
message: verification.message ?? `${agent.name} verification failed after uninstall.`,
|
|
8403
|
+
warnings: result.warnings
|
|
8404
|
+
};
|
|
8405
|
+
}
|
|
8406
|
+
}
|
|
8407
|
+
outcomes.push({
|
|
8408
|
+
id: agent.id,
|
|
8409
|
+
name: agent.name,
|
|
8410
|
+
status: result.status,
|
|
8411
|
+
message: result.status === "failed" ? result.message : undefined,
|
|
8412
|
+
warnings: result.warnings
|
|
8413
|
+
});
|
|
8414
|
+
if (result.status === "removed") {
|
|
8415
|
+
console.log(` ${success(`${agent.name} removed`, useColors)}
|
|
8416
|
+
`);
|
|
8417
|
+
for (const warn of result.warnings ?? []) {
|
|
8418
|
+
console.log(` ${warning(`Warning: ${warn}`, useColors)}`);
|
|
8419
|
+
}
|
|
8420
|
+
} else if (result.status === "not_configured") {
|
|
8421
|
+
console.log(` ${warning(`${agent.name} was not configured`, useColors)}
|
|
8422
|
+
`);
|
|
8423
|
+
} else {
|
|
8424
|
+
console.log(` ${error(result.message, useColors)}
|
|
8425
|
+
`);
|
|
8426
|
+
for (const warn of result.warnings ?? []) {
|
|
8427
|
+
console.log(` ${warning(`Warning: ${warn}`, useColors)}`);
|
|
8428
|
+
}
|
|
8429
|
+
}
|
|
8430
|
+
}
|
|
8431
|
+
const removed = outcomes.filter((o) => o.status === "removed").length;
|
|
8432
|
+
const notConfigured = outcomes.filter((o) => o.status === "not_configured").length + scan.notConfigured.length;
|
|
8433
|
+
const failed = outcomes.filter((o) => o.status === "failed").length;
|
|
8434
|
+
const skipped = outcomes.filter((o) => o.status === "skipped").length;
|
|
8435
|
+
if (failed > 0) {
|
|
8436
|
+
console.log(" Uninstall completed with errors.");
|
|
8437
|
+
} else if (removed > 0) {
|
|
8438
|
+
console.log(" Done! GitHits MCP configuration was removed.");
|
|
8439
|
+
} else if (skipped > 0) {
|
|
8440
|
+
console.log(" Uninstall skipped.");
|
|
8441
|
+
} else if (notConfigured > 0) {
|
|
8442
|
+
console.log(" No GitHits MCP configurations were active. Nothing to remove.");
|
|
8443
|
+
}
|
|
8444
|
+
if (removed > 0) {
|
|
8445
|
+
console.log(` ${removed} agent${removed !== 1 ? "s" : ""} removed.`);
|
|
8446
|
+
}
|
|
8447
|
+
if (notConfigured > 0) {
|
|
8448
|
+
console.log(` ${notConfigured} agent${notConfigured !== 1 ? "s" : ""} not configured.`);
|
|
8449
|
+
}
|
|
8450
|
+
if (skipped > 0) {
|
|
8451
|
+
console.log(` ${skipped} agent${skipped !== 1 ? "s" : ""} skipped.`);
|
|
8452
|
+
}
|
|
8453
|
+
if (failed > 0) {
|
|
8454
|
+
console.log(` ${failed} agent${failed !== 1 ? "s" : ""} failed to uninstall.`);
|
|
8455
|
+
for (const outcome of outcomes.filter((o) => o.status === "failed")) {
|
|
8456
|
+
console.log(` - ${outcome.name}: ${outcome.message ?? "Unknown error"}`);
|
|
8457
|
+
for (const warn of outcome.warnings ?? []) {
|
|
8458
|
+
console.log(` Warning: ${warn}`);
|
|
8459
|
+
}
|
|
8460
|
+
}
|
|
8461
|
+
}
|
|
8462
|
+
console.log();
|
|
8463
|
+
}
|
|
7759
8464
|
function printAuthRecoveryHint() {
|
|
7760
8465
|
console.log(" You can still configure MCP, but GitHits tools will require auth.");
|
|
7761
8466
|
console.log(" Recovery steps:");
|
|
@@ -7775,8 +8480,13 @@ and sets up unconfigured ones with your confirmation.
|
|
|
7775
8480
|
Supports CLI-based setup (Claude Code, Codex, Gemini CLI) and config
|
|
7776
8481
|
file editing (Cursor, Windsurf, VS Code, Cline, Claude Desktop,
|
|
7777
8482
|
Google Antigravity) with atomic writes.`;
|
|
8483
|
+
var INIT_UNINSTALL_DESCRIPTION = `Remove GitHits MCP server configuration from your coding agents.
|
|
8484
|
+
|
|
8485
|
+
Scans for available agents that currently have GitHits configured, then removes
|
|
8486
|
+
only the GitHits MCP/plugin configuration with your confirmation. Authentication
|
|
8487
|
+
tokens are not removed; use \`githits logout\` to remove stored credentials.`;
|
|
7778
8488
|
function registerInitCommand(program) {
|
|
7779
|
-
program.command("init").summary("Set up MCP server for your coding agents").description(INIT_DESCRIPTION).option("-y, --yes", "Skip prompts, configure all detected agents").option("--skip-login", "Skip authentication step").action(async (options) => {
|
|
8489
|
+
const initCommand = program.command("init").summary("Set up MCP server for your coding agents").description(INIT_DESCRIPTION).option("-y, --yes", "Skip prompts, configure all detected agents").option("--skip-login", "Skip authentication step").action(async (options) => {
|
|
7780
8490
|
const fileSystemService = new FileSystemServiceImpl;
|
|
7781
8491
|
const promptService = new PromptServiceImpl;
|
|
7782
8492
|
const execService = new ExecServiceImpl;
|
|
@@ -7787,6 +8497,16 @@ function registerInitCommand(program) {
|
|
|
7787
8497
|
createLoginDeps: () => createAuthCommandDependencies()
|
|
7788
8498
|
});
|
|
7789
8499
|
});
|
|
8500
|
+
initCommand.command("uninstall").summary("Remove MCP server from your coding agents").description(INIT_UNINSTALL_DESCRIPTION).option("-y, --yes", "Skip prompts, uninstall from all configured agents").action(async (options) => {
|
|
8501
|
+
const fileSystemService = new FileSystemServiceImpl;
|
|
8502
|
+
const promptService = new PromptServiceImpl;
|
|
8503
|
+
const execService = new ExecServiceImpl;
|
|
8504
|
+
await initUninstallAction(options, {
|
|
8505
|
+
fileSystemService,
|
|
8506
|
+
promptService,
|
|
8507
|
+
execService
|
|
8508
|
+
});
|
|
8509
|
+
});
|
|
7790
8510
|
}
|
|
7791
8511
|
// src/commands/languages.ts
|
|
7792
8512
|
async function languagesAction(query, options, deps) {
|
|
@@ -7908,13 +8628,17 @@ function classify3(operation, error2) {
|
|
|
7908
8628
|
|
|
7909
8629
|
// src/tools/feedback.ts
|
|
7910
8630
|
var schema = {
|
|
7911
|
-
solution_id: z.string().min(1).describe("
|
|
7912
|
-
accepted: z.boolean().describe("True
|
|
7913
|
-
feedback_text: z.string().optional().describe('Optional
|
|
8631
|
+
solution_id: z.string().min(1).optional().describe("Optional. Pass the `solution_id` from a prior `get_example` response (shown on the trailing line of the markdown result, or under the `solution_id` key in JSON mode) to anchor feedback to that specific result. Omit for generic feedback about any tool (code/package navigation, search, docs) or the overall experience."),
|
|
8632
|
+
accepted: z.boolean().describe("True for positive feedback (helpful/good), False for negative (unhelpful/bad). Always required."),
|
|
8633
|
+
feedback_text: z.string().optional().describe('Optional context (e.g., "This solved problem X" or "code_grep regex over npm:lodash missed Foo function"). Strongly recommended when `solution_id` is omitted, since there is no specific result to anchor to.')
|
|
7914
8634
|
};
|
|
7915
|
-
var DESCRIPTION = `Submit feedback on a GitHits
|
|
8635
|
+
var DESCRIPTION = `Submit feedback on a tool result or the GitHits experience.
|
|
7916
8636
|
|
|
7917
|
-
|
|
8637
|
+
Two modes:
|
|
8638
|
+
1. **Solution-tied** — pass the \`solution_id\` from a prior \`get_example\` response to rate that specific result.
|
|
8639
|
+
2. **Generic** — omit \`solution_id\` to send feedback about any tool (\`search\`, \`code_grep\`, \`code_read\`, \`code_files\`, \`docs_*\`, \`pkg_*\`) or the overall experience.
|
|
8640
|
+
|
|
8641
|
+
\`accepted\` is always required (true = positive, false = negative). Add \`feedback_text\` for context — strongly recommended in generic mode. Feeds ranking and product quality.`;
|
|
7918
8642
|
function createFeedbackTool(service) {
|
|
7919
8643
|
return {
|
|
7920
8644
|
name: "feedback",
|
|
@@ -7938,7 +8662,7 @@ var schema2 = {
|
|
|
7938
8662
|
query: z2.string().min(1).describe("Natural-language example-search query for canonical code examples."),
|
|
7939
8663
|
language: z2.string().min(1).optional().describe("Optional programming language. If omitted, GitHits tries to infer it automatically. Use search_language first only when you need to force a specific language and the exact name is uncertain."),
|
|
7940
8664
|
license_mode: z2.enum(["strict", "yolo", "custom"]).optional().describe("License filtering mode: strict (default), yolo, or custom."),
|
|
7941
|
-
format: z2.enum(["json", "text", "text-v1"]).optional().describe(
|
|
8665
|
+
format: z2.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` returns markdown directly with a trailing `solution_id` line when available. Pass `format: "json"` for `{result, solution_id?}`.')
|
|
7942
8666
|
};
|
|
7943
8667
|
var DESCRIPTION2 = `Get verified, canonical code examples from global open source.
|
|
7944
8668
|
|
|
@@ -8044,7 +8768,7 @@ function invalidTargetResult(message) {
|
|
|
8044
8768
|
// src/tools/grep-repo.ts
|
|
8045
8769
|
var schema3 = {
|
|
8046
8770
|
target: codeTargetSchema,
|
|
8047
|
-
pattern: z4.string().describe(GREP_REPO_PATTERN_NOTE),
|
|
8771
|
+
pattern: z4.string().optional().describe(GREP_REPO_PATTERN_NOTE),
|
|
8048
8772
|
path: z4.string().optional().describe("Exact file path to grep. Shares the same path vocabulary as `code_read`."),
|
|
8049
8773
|
path_prefix: z4.string().optional().describe("Literal directory prefix to scope grep, matching `code_files` / `search` naming."),
|
|
8050
8774
|
globs: z4.array(z4.string()).optional().describe("Repeatable glob scopes with real glob semantics (e.g. `src/**/*.ts`)."),
|
|
@@ -8063,7 +8787,7 @@ var schema3 = {
|
|
|
8063
8787
|
wait_timeout_ms: z4.number().optional(),
|
|
8064
8788
|
format: z4.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact line-oriented output (matches grouped by file with grep -A/-B notation for context). Pass `format: "json"` for the structured envelope. `text` is an alias for `text-v1`. Errors stay JSON-formatted in either mode for now.')
|
|
8065
8789
|
};
|
|
8066
|
-
var DESCRIPTION3 = "Deterministic text grep over indexed dependency and repository source files. " +
|
|
8790
|
+
var DESCRIPTION3 = "Deterministic text or regex grep over indexed dependency and repository source files. " + 'Use this when you know the pattern (literal by default; pass `pattern_type: "regex"` for RE2). ' + "Use `search` for discovery instead. " + "Whole-target grep is the default — narrow with `path`, `path_prefix`, `globs`, or `extensions` to keep responses small. " + "Each match's `filePath` (or text file heading) chains into `code_read.path`; pick a window around `match.line` for `code_read.start_line` / `end_line`.";
|
|
8067
8791
|
function createGrepRepoTool(service) {
|
|
8068
8792
|
return {
|
|
8069
8793
|
name: "code_grep",
|
|
@@ -8156,9 +8880,9 @@ var schema4 = {
|
|
|
8156
8880
|
include_hidden: z5.boolean().optional(),
|
|
8157
8881
|
limit: z5.number().optional().describe("Max entries to return (1–1000, default 200). Out-of-range values return an `INVALID_ARGUMENT` envelope."),
|
|
8158
8882
|
wait_timeout_ms: z5.number().optional().describe("Max milliseconds to wait for indexing (0–60000, default 20000). On an `INDEXING` error envelope, retry with a longer timeout or pass a version from `details.availableVersions`."),
|
|
8159
|
-
format: z5.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact paths-only listing
|
|
8883
|
+
format: z5.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact paths-only listing. Pass `format: "json"` for the structured envelope. `text` is an alias for `text-v1`. Errors stay JSON-formatted in either mode for now.')
|
|
8160
8884
|
};
|
|
8161
|
-
var DESCRIPTION4 = "List files in an indexed dependency.
|
|
8885
|
+
var DESCRIPTION4 = "List files in an indexed dependency. First choice for file/path " + "enumeration tasks such as files under a directory; use " + "`path_prefix` for directory prefixes (e.g. `lib/`) and optional " + "`extensions` for language filtering. Use this to discover paths " + "before `code_read` (when `code_read` returns `FILE_NOT_FOUND` or " + "you don't yet know the path) and to scope `code_grep`. Address " + "via `target.registry` + `target.package_name` (package scope) or " + "`target.repo_url` + `target.git_ref` (repo scope), mutually " + "exclusive. Narrow with `path`, `path_prefix`, `globs`, " + "`extensions`, `file_types`, `languages`, or file-intent filters. " + "JSON envelope shape: `{total, hasMore, files: [{path, name, " + "language, fileType, byteSize}], resolution, indexedVersion}`. " + "Returns an `INDEXING` error envelope when the dependency is being " + "indexed on-demand — retry with a longer `wait_timeout_ms` or use " + "a version from `details.availableVersions`.";
|
|
8162
8886
|
function createListFilesTool(service) {
|
|
8163
8887
|
return {
|
|
8164
8888
|
name: "code_files",
|
|
@@ -8237,7 +8961,7 @@ var schema5 = {
|
|
|
8237
8961
|
after: z6.string().optional().describe("Pagination cursor from a prior response."),
|
|
8238
8962
|
format: z6.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact page list with ready-to-call `docs_read` follow-ups. Pass `format: "json"` for the structured envelope.')
|
|
8239
8963
|
};
|
|
8240
|
-
var DESCRIPTION5 = "List mixed package documentation pages from hosted docs and repository-backed docs. " + "Every entry includes a stable `pageId`, `sourceKind` (`crawled` or `repo`), and source URL; repo-backed entries also expose `repoUrl` / `gitRef` / `filePath` for exact file reads. " + "Pass a returned `pageId` to `docs_read`. Use this to browse before reading a full page.";
|
|
8964
|
+
var DESCRIPTION5 = "List mixed package documentation pages from hosted docs and repository-backed docs. " + 'This browses available pages; for topic search, use `search` with `sources: ["docs"]` and pass the returned `pageId` to `docs_read`. ' + "Every entry includes a stable `pageId`, `sourceKind` (`crawled` or `repo`), and source URL; repo-backed entries also expose `repoUrl` / `gitRef` / `filePath` for exact file reads. " + "Pass a returned `pageId` to `docs_read`. Use this to browse before reading a full page.";
|
|
8241
8965
|
function createListPackageDocsTool(service) {
|
|
8242
8966
|
return {
|
|
8243
8967
|
name: "docs_list",
|
|
@@ -8451,11 +9175,11 @@ function formatPackageChangelogTerminal(envelope, options) {
|
|
|
8451
9175
|
const dateWidth = 10;
|
|
8452
9176
|
for (const entry of envelope.entries.items) {
|
|
8453
9177
|
const version2 = entry.version ?? "(unversioned)";
|
|
8454
|
-
const date = entry.publishedAt ? formatDate(entry.publishedAt) : dim("
|
|
9178
|
+
const date = entry.publishedAt ? formatDate(entry.publishedAt) : dim("-", options.useColors);
|
|
8455
9179
|
const url = entry.htmlUrl ? dim(entry.htmlUrl, options.useColors) : dim("(no link)", options.useColors);
|
|
8456
9180
|
const padded = padColumn(version2, versionWidth);
|
|
8457
9181
|
const datePadded = padColumn(date, dateWidth);
|
|
8458
|
-
lines.push(`${
|
|
9182
|
+
lines.push(`${highlight(`${padded} ${datePadded}`, options.useColors)} ${url}`);
|
|
8459
9183
|
if (entry.body != null) {
|
|
8460
9184
|
appendBodyLines(lines, entry.body, options);
|
|
8461
9185
|
}
|
|
@@ -8472,28 +9196,28 @@ function appendBodyLines(lines, body, options) {
|
|
|
8472
9196
|
}
|
|
8473
9197
|
const bodyLines = body.split(`
|
|
8474
9198
|
`);
|
|
8475
|
-
const cap = options.verbose ? bodyLines.length : DEFAULT_BODY_PREVIEW_LINES;
|
|
9199
|
+
const cap = options.verbose ? bodyLines.length : options.bodyPreviewLines ?? DEFAULT_BODY_PREVIEW_LINES;
|
|
8476
9200
|
const visible = bodyLines.slice(0, cap);
|
|
8477
9201
|
for (const bodyLine of visible) {
|
|
8478
|
-
lines.push(` ${
|
|
9202
|
+
lines.push(` ${bodyLine}`);
|
|
8479
9203
|
}
|
|
8480
9204
|
const hidden = bodyLines.length - visible.length;
|
|
8481
9205
|
if (hidden > 0) {
|
|
8482
|
-
lines.push(` ${dim(
|
|
9206
|
+
lines.push(` ${dim(`... (+${hidden} more line${hidden === 1 ? "" : "s"} - ${options.fullBodyHint ?? "use --verbose for the full body"})`, options.useColors)}`);
|
|
8483
9207
|
}
|
|
8484
9208
|
}
|
|
8485
9209
|
function buildSummaryLine(envelope, options) {
|
|
8486
|
-
const identity = envelope.registry && envelope.name ? `${envelope.name}
|
|
9210
|
+
const identity = envelope.registry && envelope.name ? `${envelope.name} | ${envelope.registry}` : envelope.repoUrl ?? "(unknown)";
|
|
8487
9211
|
const sourceLabel = envelope.source ? humanizeSource(envelope.source) : "package versions";
|
|
8488
9212
|
const modeLabel = envelope.mode === "range" ? rangeLabel(envelope) : latestLabel(envelope);
|
|
8489
9213
|
const countLabel = `${envelope.entries.count} ${plural2("entry", "entries", envelope.entries.count)}`;
|
|
8490
9214
|
const parts = [identity, `source: ${sourceLabel}`, modeLabel, countLabel];
|
|
8491
|
-
return colorize(parts.join("
|
|
9215
|
+
return colorize(parts.join(" | "), "bold", options.useColors);
|
|
8492
9216
|
}
|
|
8493
9217
|
function rangeLabel(envelope) {
|
|
8494
9218
|
const from = envelope.filter?.fromVersion ?? "earliest";
|
|
8495
9219
|
const to = envelope.filter?.toVersion ?? "latest";
|
|
8496
|
-
return `range ${from}
|
|
9220
|
+
return `range ${from} -> ${to}`;
|
|
8497
9221
|
}
|
|
8498
9222
|
function latestLabel(envelope) {
|
|
8499
9223
|
if (envelope.filter?.toVersion) {
|
|
@@ -8552,9 +9276,11 @@ var schema6 = {
|
|
|
8552
9276
|
limit: z7.number().optional().describe("Latest-mode cap on entry count (1–50, default 10). Rejected with `INVALID_ARGUMENT` when `from_version` is also set or when out of range."),
|
|
8553
9277
|
git_ref: z7.string().optional().describe("Git branch or tag for CHANGELOG.md source (no effect on GitHub Releases or HexDocs). Defaults to the repository's default branch."),
|
|
8554
9278
|
include_bodies: z7.boolean().optional().describe("When false, each entry in `entries.items[]` omits its `body` field. Default true. Set false when you only need the version / date / URL timeline — drops 10 KB+ per entry on large release notes."),
|
|
8555
|
-
|
|
9279
|
+
verbose: z7.boolean().optional().describe("Text output only. Show full body previews. Mutually exclusive with include_bodies:false and body_lines."),
|
|
9280
|
+
body_lines: z7.number().optional().describe("Text output only. Number of body lines to preview per entry (1-50, default 10). Ignored for format=json and include_bodies:false. Mutually exclusive with verbose:true."),
|
|
9281
|
+
format: z7.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact entry timeline with body previews. Pass `format: "json"` for the structured envelope with full markdown bodies.')
|
|
8556
9282
|
};
|
|
8557
|
-
var DESCRIPTION6 = "Release notes for a package or GitHub repo, newest-first. Default " + "latest mode returns the ten most recent entries (`limit` 1–50). " + "With `from_version`, returns every entry in the " + "`[from_version, to_version]` range (range mode, no count cap). " + "Address via `registry` + `package_name` or `repo_url` (mutually " + 'exclusive). Response includes optional `source` (`"releases"` / ' + '`"changelog_file"` / `"hexdocs"`) when a concrete changelog source ' + 'exists, `mode` (`"latest"` or `"range"`),
|
|
9283
|
+
var DESCRIPTION6 = "Release notes for a package or GitHub repo, newest-first. Default " + "latest mode returns the ten most recent entries (`limit` 1–50). " + "With `from_version`, returns every entry in the " + "`[from_version, to_version]` range (range mode, no count cap). " + "Address via `registry` + `package_name` or `repo_url` (mutually " + 'exclusive). Response includes optional `source` (`"releases"` / ' + '`"changelog_file"` / `"hexdocs"`) when a concrete changelog source ' + 'exists, `mode` (`"latest"` or `"range"`), and entries with ' + "markdown body previews. Example: " + '`{"registry":"npm","package_name":"express","limit":2}`. ' + "Text output previews 10 body lines by default; use `body_lines` " + "to tune the preview or `verbose:true` for full text bodies. Set " + "`include_bodies: false` for a version / date / URL timeline only; " + 'pass `format: "json"` for the complete structured envelope. ' + "Package-version entries without changelog " + "text succeed with `source` omitted; no-source plus no entries " + "returns `NOT_FOUND`. Supports npm, PyPI, Hex, Crates, NuGet, " + "Maven, Zig, vcpkg, Packagist, RubyGems, and Go.";
|
|
8558
9284
|
function createPackageChangelogTool(service) {
|
|
8559
9285
|
return {
|
|
8560
9286
|
name: "pkg_changelog",
|
|
@@ -8563,6 +9289,8 @@ function createPackageChangelogTool(service) {
|
|
|
8563
9289
|
annotations: { readOnlyHint: true },
|
|
8564
9290
|
handler: async (args) => {
|
|
8565
9291
|
try {
|
|
9292
|
+
const textFormat = isTextFormat5(args.format);
|
|
9293
|
+
const bodyPreviewLines = textFormat ? validateTextOptions(args) : undefined;
|
|
8566
9294
|
const { params, explicitFilterFields } = buildPackageChangelogParams({
|
|
8567
9295
|
registry: args.registry,
|
|
8568
9296
|
packageName: args.package_name,
|
|
@@ -8585,11 +9313,12 @@ function createPackageChangelogTool(service) {
|
|
|
8585
9313
|
limit: params.limit,
|
|
8586
9314
|
gitRef: params.gitRef
|
|
8587
9315
|
});
|
|
8588
|
-
if (
|
|
9316
|
+
if (textFormat) {
|
|
8589
9317
|
return textResult(formatPackageChangelogTerminal(payload, {
|
|
8590
9318
|
useColors: false,
|
|
8591
|
-
verbose: false,
|
|
8592
|
-
|
|
9319
|
+
verbose: args.verbose ?? false,
|
|
9320
|
+
bodyPreviewLines,
|
|
9321
|
+
fullBodyHint: 'pass verbose=true, body_lines=<n>, or format="json" for full bodies'
|
|
8593
9322
|
}).trimEnd());
|
|
8594
9323
|
}
|
|
8595
9324
|
return textResult(JSON.stringify(payload));
|
|
@@ -8613,22 +9342,36 @@ function createPackageChangelogTool(service) {
|
|
|
8613
9342
|
}
|
|
8614
9343
|
};
|
|
8615
9344
|
}
|
|
9345
|
+
function validateTextOptions(args) {
|
|
9346
|
+
if (args.include_bodies === false && args.verbose === true) {
|
|
9347
|
+
throw new InvalidPackageSpecError("verbose:true conflicts with include_bodies:false because bodies are omitted. Drop one of the two options.");
|
|
9348
|
+
}
|
|
9349
|
+
if (args.verbose === true && args.body_lines !== undefined) {
|
|
9350
|
+
throw new InvalidPackageSpecError("body_lines conflicts with verbose:true because verbose already shows full bodies. Drop one of the two options.");
|
|
9351
|
+
}
|
|
9352
|
+
if (args.body_lines === undefined)
|
|
9353
|
+
return;
|
|
9354
|
+
if (!Number.isInteger(args.body_lines) || args.body_lines < 1 || args.body_lines > 50) {
|
|
9355
|
+
throw new InvalidPackageSpecError(`body_lines must be an integer between 1 and 50. Got ${args.body_lines}.`);
|
|
9356
|
+
}
|
|
9357
|
+
return args.body_lines;
|
|
9358
|
+
}
|
|
8616
9359
|
function isTextFormat5(format) {
|
|
8617
9360
|
return format === undefined || format === "text" || format === "text-v1";
|
|
8618
9361
|
}
|
|
8619
9362
|
// src/tools/package-dependencies.ts
|
|
8620
9363
|
import { z as z8 } from "zod";
|
|
8621
9364
|
var schema7 = {
|
|
8622
|
-
registry: z8.string().describe(`Package registry. Dependency data is available on ${
|
|
9365
|
+
registry: z8.string().describe(`Package registry. Dependency data is available on ${SUPPORTED_DEPS_REGISTRIES_LIST}.`),
|
|
8623
9366
|
package_name: z8.string().describe("Package name (scoped names ok: @types/node)."),
|
|
8624
9367
|
version: z8.string().optional().describe("Specific version to inspect. Defaults to latest when omitted. Tag-style inputs with a leading `v` (for example `v4.18.0`) are rejected — pass the canonical version (`4.18.0`)."),
|
|
8625
9368
|
lifecycle: z8.union([z8.string(), z8.array(z8.string())]).optional().describe("Lifecycle breadth. Omit for runtime-only. Use `runtime` for explicit runtime-only, a concrete non-runtime lifecycle (`development`, `build`, `peer`, `optional`) for runtime plus matching groups, or `all` for runtime plus all available groups. Accepts a single value, a comma-separated string, or an array; `all` cannot be combined with other values. Uppercase is tolerated."),
|
|
8626
9369
|
include_transitive: z8.boolean().optional().describe("When true the response gains a `transitive` block with aggregate counts (`edges`, `uniquePackages`), the preprocessed `packages[]` list (each `{name, version}` — the complete install footprint), plus typed `conflicts[]` (`{name, requiredVersions}`) and `circularDependencies[]` (`{cycle: string[]}`) when the backend reported any. Off by default."),
|
|
8627
9370
|
include_importers: z8.boolean().optional().describe("Requires `include_transitive: true`. When true, each entry in `transitive.packages[]` also carries an `importers` array — every upstream package that pulls it in, with that importer's own resolved version and the constraint it declared. Off by default because adding provenance roughly quadruples the envelope size on heavy graphs. Turn on when you need to trace why a specific transitive dep is present."),
|
|
8628
9371
|
max_depth: z8.number().int().min(1).max(10).optional().describe("Cap the transitive traversal at this depth (1–10). Omit to get the backend's full graph. Requires `include_transitive: true` — passing `max_depth` without the transitive flag is rejected with `INVALID_ARGUMENT`."),
|
|
8629
|
-
format: z8.enum(["json", "text", "text-v1"]).optional().describe(
|
|
9372
|
+
format: z8.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact dependency listing. Pass `format: "json"` for the structured envelope.')
|
|
8630
9373
|
};
|
|
8631
|
-
var DESCRIPTION7 = "Analyze a package's dependency graph.
|
|
9374
|
+
var DESCRIPTION7 = "Analyze a package's dependency graph. Lists direct runtime " + "dependencies with resolved versions; non-runtime groups are " + "omitted by default. Use `lifecycle` with a concrete value for " + "matching dependency groups, or `all` for every available group. " + "Runtime group rows include resolved versions when available. " + "Set `include_transitive: true` to add a " + "`transitive` block with the full install footprint, conflict " + "detection, and circular-dependency flags; layer " + "`include_importers: true` on top when you also need per-package " + "provenance. Supports npm, PyPI, Hex, Crates, Zig, vcpkg, RubyGems, " + "and Go.";
|
|
8632
9375
|
function createPackageDependenciesTool(service) {
|
|
8633
9376
|
return {
|
|
8634
9377
|
name: "pkg_deps",
|
|
@@ -8701,9 +9444,10 @@ import { z as z9 } from "zod";
|
|
|
8701
9444
|
var schema8 = {
|
|
8702
9445
|
registry: z9.string().describe(`Package registry. One of: ${PKGSEER_REGISTRY_LIST}.`),
|
|
8703
9446
|
package_name: z9.string().describe("Package name (scoped names ok: @types/node)."),
|
|
8704
|
-
|
|
9447
|
+
verbose: z9.boolean().optional().describe("Text only. Adds GitHub language/topics/last-pushed, recent advisories, and recent changes. Ignored for format=json."),
|
|
9448
|
+
format: z9.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact package overview. Pass `format: "json"` for the structured envelope.')
|
|
8705
9449
|
};
|
|
8706
|
-
var DESCRIPTION8 = "
|
|
9450
|
+
var DESCRIPTION8 = "Latest-version package overview for dependency triage. Provide " + "`registry` and `package_name` (for example `npm` + `express`). " + "Default text returns license, description, repository popularity " + "(stars/forks/issues and [ARCHIVED] when applicable), downloads, " + "publish age, and vulnerability status. Set `verbose: true` for " + "GitHub language/topics/last-pushed, recent advisories, and recent " + 'changes. Pass `format: "json"` for structured fields. Use ' + "`pkg_vulns` for version-specific vulnerability details.";
|
|
8707
9451
|
function createPackageSummaryTool(service) {
|
|
8708
9452
|
return {
|
|
8709
9453
|
name: "pkg_info",
|
|
@@ -8720,6 +9464,7 @@ function createPackageSummaryTool(service) {
|
|
|
8720
9464
|
const payload = buildPackageSummarySuccessPayload(summary);
|
|
8721
9465
|
if (isTextFormat7(args.format)) {
|
|
8722
9466
|
return textResult(formatPackageSummaryTerminal(summary, {
|
|
9467
|
+
verbose: args.verbose,
|
|
8723
9468
|
useColors: false
|
|
8724
9469
|
}).trimEnd());
|
|
8725
9470
|
}
|
|
@@ -8750,14 +9495,16 @@ function isTextFormat7(format) {
|
|
|
8750
9495
|
// src/tools/package-vulnerabilities.ts
|
|
8751
9496
|
import { z as z10 } from "zod";
|
|
8752
9497
|
var schema9 = {
|
|
8753
|
-
registry: z10.string().describe("Package registry. Vulnerability data
|
|
9498
|
+
registry: z10.string().describe("Package registry. Vulnerability data is unavailable for vcpkg and zig."),
|
|
8754
9499
|
package_name: z10.string().describe("Package name (scoped names ok: @types/node)."),
|
|
8755
9500
|
version: z10.string().optional().describe("Specific version to check. Defaults to latest when omitted."),
|
|
8756
9501
|
min_severity: z10.string().optional().describe("Only return advisories at or above this severity (`low`, `medium`, `high`, `critical`; uppercase tolerated). Omit to see all, including null-severity advisories."),
|
|
8757
9502
|
include_withdrawn: z10.boolean().optional().describe("Include retracted advisories (default: false)."),
|
|
8758
|
-
|
|
9503
|
+
advisory_scope: z10.string().optional().describe("Advisory rows to return: `affected` (default), `non_affecting` for historical advisories that do not affect the inspected version, or `all` for both affected and historical advisories. Counts always include affected/non-affecting/all totals."),
|
|
9504
|
+
verbose: z10.boolean().optional().describe("Text output only. Show every advisory and full detail rows; format=json always returns the complete structured envelope."),
|
|
9505
|
+
format: z10.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact advisory summary. Pass `format: "json"` for the structured envelope.')
|
|
8759
9506
|
};
|
|
8760
|
-
var DESCRIPTION9 = "Check known vulnerabilities for a package on npm, PyPI, Hex,
|
|
9507
|
+
var DESCRIPTION9 = "Check known vulnerabilities for a package on npm, PyPI, Hex, " + "Crates, NuGet, Maven, Packagist, RubyGems, or Go (vcpkg and Zig " + "are not supported for vulnerability data). Returns a count summary, each advisory with OSV ID, " + "severity, affected ranges, and fix versions. Malicious-package " + "advisories surface in a separate bucket. Example: " + '`{"registry":"npm","package_name":"lodash","version":"4.17.20","min_severity":"high"}`. ' + "Pass `version` to inspect " + "a pinned release; omit it for latest. Default text is capped for " + "readability; use `verbose:true` for all selected advisory rows or " + '`format:"json"` for the complete envelope. Use ' + "`min_severity` to filter to a threshold (`low`, `medium`, `high`, " + "`critical`) and `include_withdrawn` to also see retracted " + 'advisories. Use `advisory_scope:"non_affecting"` to list ' + "historical advisories that do not affect the inspected version, or " + '`advisory_scope:"all"` to list affected and historical advisories together.';
|
|
8761
9508
|
function createPackageVulnerabilitiesTool(service) {
|
|
8762
9509
|
return {
|
|
8763
9510
|
name: "pkg_vulns",
|
|
@@ -8766,21 +9513,26 @@ function createPackageVulnerabilitiesTool(service) {
|
|
|
8766
9513
|
annotations: { readOnlyHint: true },
|
|
8767
9514
|
handler: async (args) => {
|
|
8768
9515
|
try {
|
|
8769
|
-
const { params } = buildPackageVulnerabilitiesParams({
|
|
9516
|
+
const { params, filter } = buildPackageVulnerabilitiesParams({
|
|
8770
9517
|
registry: args.registry,
|
|
8771
9518
|
packageName: args.package_name,
|
|
8772
9519
|
version: args.version,
|
|
8773
9520
|
minSeverity: args.min_severity,
|
|
8774
|
-
includeWithdrawn: args.include_withdrawn
|
|
9521
|
+
includeWithdrawn: args.include_withdrawn,
|
|
9522
|
+
advisoryScope: args.advisory_scope
|
|
8775
9523
|
});
|
|
8776
9524
|
const report = await service.packageVulnerabilities(params);
|
|
8777
9525
|
const payload = buildPackageVulnerabilitiesSuccessPayload(report, {
|
|
8778
|
-
requestedVersion: args.version
|
|
9526
|
+
requestedVersion: args.version,
|
|
9527
|
+
filter
|
|
8779
9528
|
});
|
|
8780
9529
|
if (isTextFormat8(args.format)) {
|
|
8781
9530
|
return textResult(formatPackageVulnerabilitiesTerminal(report, {
|
|
8782
9531
|
useColors: false,
|
|
8783
|
-
requestedVersion: args.version
|
|
9532
|
+
requestedVersion: args.version,
|
|
9533
|
+
filter,
|
|
9534
|
+
verbose: args.verbose,
|
|
9535
|
+
surface: "mcp"
|
|
8784
9536
|
}).trimEnd());
|
|
8785
9537
|
}
|
|
8786
9538
|
return textResult(JSON.stringify(payload));
|
|
@@ -8812,13 +9564,13 @@ import { z as z11 } from "zod";
|
|
|
8812
9564
|
var MCP_READ_MAX_SPAN = 150;
|
|
8813
9565
|
var schema10 = {
|
|
8814
9566
|
target: codeTargetSchema,
|
|
8815
|
-
path: z11.string().describe("
|
|
9567
|
+
path: z11.string().describe("Exact file path to read, not a directory. Package addressing: package-relative. Repo addressing: repo-relative. Use `code_files` with `path_prefix` to list directories, then pass an emitted `path` here."),
|
|
8816
9568
|
start_line: z11.number().optional().describe(`Starting line (1-indexed). Omit to start at line 1. The MCP surface caps any single read at ${MCP_READ_MAX_SPAN} lines — pick a focused window from your prior \`search\` / \`code_grep\` hit.`),
|
|
8817
9569
|
end_line: z11.number().optional().describe(`Ending line (inclusive). Must be ≥ \`start_line\` when both are set. Omitting it implies \`start_line + ${MCP_READ_MAX_SPAN - 1}\` because the MCP surface caps each read at ${MCP_READ_MAX_SPAN} lines.`),
|
|
8818
9570
|
wait_timeout_ms: z11.number().optional().describe("Max milliseconds to wait for indexing (0–60000, default 20000). On an `INDEXING` error envelope, retry with a longer timeout or pass a version from `details.availableVersions`."),
|
|
8819
9571
|
format: z11.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — line-numbered source content. Pass `format: "json"` for the structured envelope.')
|
|
8820
9572
|
};
|
|
8821
|
-
var DESCRIPTION10 = "Read
|
|
9573
|
+
var DESCRIPTION10 = "Read one exact file from an indexed dependency; it does not list " + "directories. Use `code_files` with `path_prefix` for file/path " + "enumeration. **MCP cap: " + `${MCP_READ_MAX_SPAN} lines per call** — broader requests (or no ` + `range) silently truncate to the first ${MCP_READ_MAX_SPAN} lines ` + "from your start, with a `hint` describing what was returned vs. " + "requested. Pick a focused window from a `search` / `code_grep` " + "match. Response: `{path, language, totalLines, startLine, endLine, " + "content, isBinary, hint?}`. Binary files set `isBinary: true` and " + "omit `content`. Pass the same `path` emitted by `code_files`. " + "Address via `target.registry` + `target.package_name` (package " + "scope) or `target.repo_url` + `target.git_ref` (repo scope), " + "mutually exclusive. On `INDEXING` retry with a longer " + "`wait_timeout_ms`. On `NOT_FOUND` / `FILE_NOT_FOUND` call " + "`code_files` to discover the actual path.";
|
|
8822
9574
|
function deriveBoundedRange(startLine, endLine) {
|
|
8823
9575
|
const start = startLine ?? 1;
|
|
8824
9576
|
if (endLine === undefined) {
|
|
@@ -8873,7 +9625,7 @@ function createReadFileTool(service) {
|
|
|
8873
9625
|
}
|
|
8874
9626
|
return textResult(JSON.stringify(payload));
|
|
8875
9627
|
} catch (error2) {
|
|
8876
|
-
const mapped = mapCodeNavigationError(error2);
|
|
9628
|
+
const mapped = withReadFileRecovery(mapCodeNavigationError(error2), args.path);
|
|
8877
9629
|
return errorResult(JSON.stringify({
|
|
8878
9630
|
error: mapped.message,
|
|
8879
9631
|
code: mapped.code,
|
|
@@ -8900,7 +9652,8 @@ function shouldEmitCappedHint(bounded, payload) {
|
|
|
8900
9652
|
}
|
|
8901
9653
|
function buildCappedHint(payload, originalStart, originalEnd) {
|
|
8902
9654
|
const requested = describeRequest(originalStart, originalEnd);
|
|
8903
|
-
|
|
9655
|
+
const continuation = payload.endLine !== undefined ? ` To continue, retry with start_line=${payload.endLine + 1}.` : "";
|
|
9656
|
+
return `Returned lines ${payload.startLine}-${payload.endLine}/${payload.totalLines} ` + `(MCP cap: ${MCP_READ_MAX_SPAN} lines per call; you requested ${requested}).` + `${continuation} ` + `Pick a focused start_line/end_line window — typical 80-150 lines around a search/code_grep match. ` + `Each retry also costs context, so aim for one well-sized read.`;
|
|
8904
9657
|
}
|
|
8905
9658
|
function describeRequest(originalStart, originalEnd) {
|
|
8906
9659
|
if (originalStart === undefined && originalEnd === undefined) {
|
|
@@ -9031,9 +9784,9 @@ var schema12 = {
|
|
|
9031
9784
|
limit: z13.coerce.number().int().min(1).max(100).optional().describe("Maximum results to return (default 10, max 100)."),
|
|
9032
9785
|
offset: z13.coerce.number().int().min(0).optional(),
|
|
9033
9786
|
wait_timeout_ms: z13.coerce.number().int().min(0).max(60000).optional(),
|
|
9034
|
-
format: z13.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact line-oriented output
|
|
9787
|
+
format: z13.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact line-oriented output. Pass `format: "json"` for the structured envelope. `text` is an alias for `text-v1`. The text format is a public, snapshot-tested contract.')
|
|
9035
9788
|
};
|
|
9036
|
-
var DESCRIPTION12 = "Search indexed dependency and repository code, docs, and explicit symbols. " + "Provide either `target` for one target or `targets` for many; omit `sources` to use backend AUTO. " + "
|
|
9789
|
+
var DESCRIPTION12 = "Search indexed dependency and repository code, docs, and explicit symbols. " + "Provide either `target` for one target or `targets` for many; omit `sources` to use backend AUTO. " + "Structured parameters combine with the `query` using AND semantics. " + "Complete by default — if indexing is still running, the response carries a `searchRef` and no hits; pass it to `search_status` to follow up. " + "Set `allow_partial_results: true` to opt into hits from sources that finished while others continue indexing. " + "Each hit's `type` tells you the follow-up tool: `documentation_page` and `repository_doc` → `docs_read` with `locator.pageId`; `repository_code` and `repository_symbol` → `code_read` with `locator.filePath` (and `locator.startLine`/`endLine` when present).";
|
|
9037
9790
|
function createSearchTool(service) {
|
|
9038
9791
|
return {
|
|
9039
9792
|
name: "search",
|
|
@@ -9137,7 +9890,7 @@ function isTextFormat11(format) {
|
|
|
9137
9890
|
import { z as z14 } from "zod";
|
|
9138
9891
|
var schema13 = {
|
|
9139
9892
|
query: z14.string().min(1).describe('Language name or partial name to search for (e.g., "python", "type", "java")'),
|
|
9140
|
-
format: z14.enum(["json", "text", "text-v1"]).optional().describe(
|
|
9893
|
+
format: z14.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` returns one language per line. Pass `format: "json"` for the structured array.')
|
|
9141
9894
|
};
|
|
9142
9895
|
var DESCRIPTION13 = `Find the correct language name for \`get_example\` when it is uncertain. Returns up to 5 matching languages by name, display name, or alias. Default output is one language per line; pass \`format: "json"\` for the structured array.`;
|
|
9143
9896
|
function createSearchLanguageTool(service) {
|
|
@@ -9173,10 +9926,10 @@ function renderLanguageMatches(matches) {
|
|
|
9173
9926
|
// src/tools/search-status.ts
|
|
9174
9927
|
import { z as z15 } from "zod";
|
|
9175
9928
|
var schema14 = {
|
|
9176
|
-
search_ref: z15.string().min(1).describe("
|
|
9929
|
+
search_ref: z15.string().min(1).describe("The `searchRef` field from a prior `search` response (camelCase in the response, snake_case as this parameter). Pass it through unchanged."),
|
|
9177
9930
|
format: z15.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — compact line-oriented output matching `search`. Pass `format: "json"` for the structured envelope.')
|
|
9178
9931
|
};
|
|
9179
|
-
var DESCRIPTION14 = "Check progress, fetch partial hits when the original request used allow_partial_results: true, or fetch final results for a prior
|
|
9932
|
+
var DESCRIPTION14 = "Check progress, fetch partial hits (when the original request used `allow_partial_results: true`), or fetch final results for a prior `search` that returned a `searchRef`. " + "Pass the `searchRef` from that response as `search_ref` here (response field is camelCase; this parameter is snake_case).";
|
|
9180
9933
|
function createSearchStatusTool(service) {
|
|
9181
9934
|
return {
|
|
9182
9935
|
name: "search_status",
|
|
@@ -9201,50 +9954,54 @@ function isTextFormat13(format) {
|
|
|
9201
9954
|
return format === undefined || format === "text" || format === "text-v1";
|
|
9202
9955
|
}
|
|
9203
9956
|
// src/commands/mcp-instructions.ts
|
|
9204
|
-
var CORE_BLOCK = `GitHits provides verified, canonical code examples from global open source
|
|
9957
|
+
var CORE_BLOCK = `GitHits provides verified, canonical code examples from global open source for AI agents. Pick a path:
|
|
9958
|
+
|
|
9959
|
+
- Need a canonical example or cross-project pattern with no specific package pinned (you're stuck, repeated attempts failed, the user wants up-to-date API usage, or the user mentioned GitHits) — call \`get_example\` for global solution synthesis.
|
|
9960
|
+
- Inspecting a specific known dependency or repository (stack trace points there, verifying how a particular library works, evaluating an upgrade) — call \`search\` plus the indexed code, docs, and package tools listed below.
|
|
9961
|
+
- Question is comparative across OSS projects (e.g. "how does X vs Y handle Z") or requires reading how a real codebase implements a feature — combine the two: \`search\` for known targets, \`get_example\` for cross-project synthesis.
|
|
9962
|
+
- Rate a result or share tool/UX feedback — call \`feedback\` with a \`solution_id\` from a prior \`get_example\` response (solution-tied) or omit it for generic feedback about any tool (\`search\`, \`code_*\`, \`docs_*\`, \`pkg_*\`) or the overall experience.
|
|
9205
9963
|
|
|
9206
|
-
|
|
9964
|
+
\`get_example\` workflow: pass \`language\` only when you know the exact name; otherwise call \`search_language\` first. Default output is markdown with a trailing \`solution_id\`. Reuse prior results before searching again. For dependency-specific grounding, prefer package-scoped \`search\` before global \`get_example\`.`;
|
|
9207
9965
|
var PACKAGE_TOOLS_PREAMBLE = `Indexed package/source tools inspect third-party dependency source, docs, and registry metadata. Use them when a stack trace points into a dependency, you need to verify how a library works, or you're evaluating whether to add or upgrade a package.
|
|
9208
9966
|
|
|
9209
9967
|
Package spec: \`registry:name[@version]\`. Default outputs are compact \`text-v1\` for agent context efficiency; pass \`format: "json"\` only when you need structured fields for programmatic parsing.`;
|
|
9210
|
-
var
|
|
9211
|
-
var DOCS_LIST_BULLET = "- `docs_list` — browse hosted and repository-backed package docs. Entries include stable pageIds, source URLs, and repo-file follow-up metadata when available.";
|
|
9212
|
-
var DOCS_READ_BULLET = "- `docs_read` — read a documentation page by pageId. Works for both hosted docs and repo-backed docs.";
|
|
9213
|
-
var PKG_VULNS_BULLET = '- `pkg_vulns` — compact known CVE / OSV advisory summary for npm, PyPI, Hex, or Crates packages, optionally pinned to `version`. Filter with `min_severity`; include retracted advisories with `include_withdrawn`. Pass `format: "json"` for per-advisory structured fields.';
|
|
9214
|
-
var PKG_DEPS_BULLET = '- `pkg_deps` — compact direct runtime deps by default. Use `lifecycle: "runtime"` for explicit runtime-only, a concrete lifecycle for runtime plus matching non-runtime deps, or `lifecycle: "all"` for all groups. Use `include_transitive` for the full graph and `include_importers` for provenance. Pass `format: "json"` for the structured envelope.';
|
|
9215
|
-
var PKG_CHANGELOG_BULLET = '- `pkg_changelog` — compact release notes for a package or GitHub repo, newest-first. Default latest mode returns recent entries with markdown body previews; `from_version` switches to range mode. Set `include_bodies: false` for a compact timeline or pass `format: "json"` for full bodies.';
|
|
9968
|
+
var MULTI_TURN_TIP = '**Delegate multi-call work to a sub-agent.** Code-navigation work (`search`, `code_grep`, `code_read`, `code_files`) often takes 3-10 calls. For cross-project comparisons, codebase mapping, pattern surveys, or "how does X actually work" investigations, spawn a sub-task/sub-agent and ask for a compact synthesis. Do it inline only when the raw snippet belongs in the main conversation.';
|
|
9216
9969
|
var SEARCH_BULLET = '- `search` — unified search across indexed dependency code, docs, and symbols. Omit `sources` for AUTO. Use `sources:["code"]` for implementation/examples/tests text, `sources:["symbol"]` for precise API/entity lookup, and `sources:["docs"]` for guides/reference/changelogs. Default output is compact `text-v1` with ready-to-call follow-up arguments; pass `format: "json"` for structured locators, highlights, and source status. Complete by default; opt into partial hits with `allow_partial_results: true`. Incomplete responses carry a `searchRef`.';
|
|
9217
9970
|
var SEARCH_STATUS_BULLET = "- `search_status` — follow up a prior `search` by `searchRef` to check progress, fetch partial hits, or fetch final results.";
|
|
9218
|
-
var
|
|
9219
|
-
var CODE_READ_BULLET = "- `code_read` — read
|
|
9220
|
-
var
|
|
9221
|
-
var
|
|
9222
|
-
var
|
|
9223
|
-
var
|
|
9971
|
+
var CODE_GREP_BULLET = "- `code_grep` — deterministic text or regex grep over indexed source. Use it when you know the pattern; use `search` for discovery. Narrow with `path`, `path_prefix`, `globs`, or `extensions`. Each match's `filePath`/file heading plus line number chains into `code_read`.";
|
|
9972
|
+
var CODE_READ_BULLET = "- `code_read` — read one exact dependency file by `path`; do not use it to probe/list directories like `lib` or `lib/`. **MCP cap: 150 lines per call**. When you already have an exact path (e.g. from a stack trace), call this directly; otherwise locate it first with `search`, `code_grep`, or `code_files` and pick a focused `start_line` / `end_line` window. Binary files set `isBinary: true` and omit `content`. On `FILE_NOT_FOUND` / `NOT_FOUND`, follow `details.action` or call `code_files` for the actual path.";
|
|
9973
|
+
var CODE_FILES_BULLET = '- `code_files` — list or discover file paths in an indexed dependency. First choice for file-listing/path-enumeration tasks such as "files under lib/" (`path_prefix: "lib/"`, optional `extensions: ["js"]`); do not use `code_read` to probe directories and do not use `code_grep` with empty or generic patterns to list files. `path`, `path_prefix`, and `globs` are OR-ed selectors; extensions, language, file type, and file-intent filters intersect on top. Returned paths feed into `code_read` and help scope `code_grep`; pass `format: "json"` for language/type/size metadata.';
|
|
9974
|
+
var DOCS_LIST_BULLET = '- `docs_list` — browse hosted and repository-backed package docs when you need the available pages. It is not topic search; for "find docs about X", call `search` with `sources:["docs"]`, then pass the returned `pageId` to `docs_read`. Entries include stable pageIds, source URLs, and repo-file follow-up metadata when available.';
|
|
9975
|
+
var DOCS_READ_BULLET = "- `docs_read` — read a documentation page by pageId. Works for both hosted docs and repo-backed docs. Prefer focused `start_line` / `end_line` windows from `search` hits or prior `docs_read` `totalLines` metadata instead of rereading large pages.";
|
|
9976
|
+
var PKG_INFO_BULLET = '- `pkg_info` — latest-version package triage by `registry` + `package_name` (e.g. `npm` + `express`): license, repository popularity, downloads, publish age, and vulnerability status. Set `verbose: true` for GitHub language/topics/last-pushed, recent advisories, and recent changes; pass `format: "json"` for structured fields.';
|
|
9977
|
+
var PKG_VULNS_BULLET = '- `pkg_vulns` — compact known CVE / OSV advisory summary for npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, RubyGems, or Go packages; vcpkg and Zig are not supported. Optionally pin `version` (e.g. `npm` + `lodash` + `4.17.20`). Filter with `min_severity`; include retracted advisories with `include_withdrawn`; set `advisory_scope: "non_affecting"` for historical advisories or `"all"` for affected + historical rows. Default text is capped; use `verbose: true` for all selected rows or `format: "json"` for the complete per-advisory envelope.';
|
|
9978
|
+
var PKG_DEPS_BULLET = '- `pkg_deps` — compact direct runtime deps by default. Use `lifecycle: "runtime"` for explicit runtime-only, a concrete lifecycle for runtime plus matching non-runtime deps, or `lifecycle: "all"` for all groups. Use `include_transitive` for the full graph and `include_importers` for provenance. Pass `format: "json"` for the structured envelope.';
|
|
9979
|
+
var PKG_CHANGELOG_BULLET = '- `pkg_changelog` — compact release notes for a package or GitHub repo, newest-first (e.g. `npm` + `express` + `limit: 2`). Default latest mode returns recent entries with 10-line markdown previews; `from_version` switches to range mode. Set `body_lines` to tune text previews, `verbose: true` for full text bodies, `include_bodies: false` for a compact timeline, or `format: "json"` for the complete envelope.';
|
|
9980
|
+
var STRATEGY_TIP = 'Strategy — reference-first. For file/path enumeration, call `code_files` directly; never test directory paths with `code_read`. For behavioral claims, locate symbols and lines with `search` or `code_grep` first, then read the needed window with `code_read` using explicit `start_line` / `end_line` (typical 80-150 lines around the match; the MCP `code_read` surface caps each call at 150 lines). Source, symbols, tests, and call sites beat docs prose; use `sources:["symbol"]` when you want symbol-shaped results, `code_grep` for deterministic text matching, and `get_example` for global canonical examples after package-scoped grounding.';
|
|
9224
9981
|
function buildMcpInstructions(_deps) {
|
|
9225
|
-
const
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
const
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
`
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9982
|
+
const bullets = [
|
|
9983
|
+
SEARCH_BULLET,
|
|
9984
|
+
SEARCH_STATUS_BULLET,
|
|
9985
|
+
CODE_FILES_BULLET,
|
|
9986
|
+
CODE_GREP_BULLET,
|
|
9987
|
+
CODE_READ_BULLET,
|
|
9988
|
+
DOCS_LIST_BULLET,
|
|
9989
|
+
DOCS_READ_BULLET,
|
|
9990
|
+
PKG_INFO_BULLET,
|
|
9991
|
+
PKG_VULNS_BULLET,
|
|
9992
|
+
PKG_DEPS_BULLET,
|
|
9993
|
+
PKG_CHANGELOG_BULLET
|
|
9994
|
+
];
|
|
9995
|
+
const packageSection = [
|
|
9996
|
+
PACKAGE_TOOLS_PREAMBLE,
|
|
9997
|
+
MULTI_TURN_TIP,
|
|
9998
|
+
bullets.join(`
|
|
9999
|
+
`),
|
|
10000
|
+
STRATEGY_TIP
|
|
10001
|
+
].join(`
|
|
9245
10002
|
|
|
9246
|
-
`)
|
|
9247
|
-
return
|
|
10003
|
+
`);
|
|
10004
|
+
return [CORE_BLOCK, packageSection].join(`
|
|
9248
10005
|
|
|
9249
10006
|
`);
|
|
9250
10007
|
}
|
|
@@ -9339,14 +10096,14 @@ Authenticated tool calls require a valid GitHits token.`).action(async () => {
|
|
|
9339
10096
|
showMcpSetupInstructions();
|
|
9340
10097
|
return;
|
|
9341
10098
|
}
|
|
9342
|
-
const deps = await createContainer();
|
|
10099
|
+
const deps = await createContainer({ resolveStoredToken: false });
|
|
9343
10100
|
await startMcpServer(deps);
|
|
9344
10101
|
});
|
|
9345
10102
|
mcpCommand.command("start").summary("Start MCP server (stdio mode)").description(`Start the MCP server using STDIO transport.
|
|
9346
10103
|
|
|
9347
10104
|
This command explicitly starts the server and is intended for use
|
|
9348
10105
|
in MCP configuration files. Use 'githits mcp' for interactive setup.`).action(async () => {
|
|
9349
|
-
const deps = await createContainer();
|
|
10106
|
+
const deps = await createContainer({ resolveStoredToken: false });
|
|
9350
10107
|
await startMcpServer(deps);
|
|
9351
10108
|
});
|
|
9352
10109
|
}
|
|
@@ -9443,7 +10200,7 @@ function formatChangelogTerminalError(mapped) {
|
|
|
9443
10200
|
if (available && available.length > 0) {
|
|
9444
10201
|
const sample = available.slice(0, 5).join(", ");
|
|
9445
10202
|
const more = available.length - 5;
|
|
9446
|
-
const suffix = more > 0 ? `,
|
|
10203
|
+
const suffix = more > 0 ? `, ... (+${more} more)` : "";
|
|
9447
10204
|
lines.push(` available: ${sample}${suffix}`);
|
|
9448
10205
|
}
|
|
9449
10206
|
return lines.join(`
|
|
@@ -9577,13 +10334,12 @@ function formatDepsTerminalError(mapped) {
|
|
|
9577
10334
|
var PKG_DEPS_DESCRIPTION = `Analyze package dependencies. By default shows the flat list of
|
|
9578
10335
|
direct runtime dependencies. Use --lifecycle all for the structured view
|
|
9579
10336
|
(dev / peer / build / optional, plus registry-specific feature / TFM
|
|
9580
|
-
groups).
|
|
10337
|
+
groups). Runtime group rows include resolved versions when available.
|
|
9581
10338
|
--transitive opts into aggregate edge / unique-package counts,
|
|
9582
10339
|
conflict detection, and circular-dependency flags.
|
|
9583
10340
|
|
|
9584
10341
|
Package spec: <registry>:<name>[@<version>]. Supported registries:
|
|
9585
|
-
|
|
9586
|
-
release.`;
|
|
10342
|
+
${SUPPORTED_DEPS_REGISTRIES_LIST}. Omit @<version> for the latest release.`;
|
|
9587
10343
|
function registerPkgDepsCommand(pkgCommand) {
|
|
9588
10344
|
return pkgCommand.command("deps").summary("Analyze dependencies for a package").description(PKG_DEPS_DESCRIPTION).argument("<spec>", "Package spec, e.g. npm:express or npm:express@4.18.0").option("-l, --lifecycle <phases>", "Dependency lifecycle breadth (runtime, development, build, peer, optional, all; comma-separated for multi-select except all).").option("-t, --transitive", "Include aggregate transitive counts, conflicts, and circular dependencies").option("--depth <n>", "Cap transitive traversal depth (1-10). Omit for the full graph.").option("-v, --verbose", "Show conditionType / selectionMode / environmentConstraints metadata in the groups view").option("--json", "Emit the lean JSON envelope").action(async (spec, options) => {
|
|
9589
10345
|
const deps = await createContainer();
|
|
@@ -9640,16 +10396,20 @@ function handlePkgInfoCommandError(error2, json) {
|
|
|
9640
10396
|
console.error(formatMappedErrorForTerminal(mapped));
|
|
9641
10397
|
process.exit(1);
|
|
9642
10398
|
}
|
|
9643
|
-
var PKG_INFO_DESCRIPTION = `
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
10399
|
+
var PKG_INFO_DESCRIPTION = `Latest-version package overview for dependency triage.
|
|
10400
|
+
|
|
10401
|
+
Default output shows license, description, repository popularity
|
|
10402
|
+
(stars/forks/issues and [ARCHIVED] when applicable), downloads,
|
|
10403
|
+
publish age, and vulnerability status. --verbose adds GitHub
|
|
10404
|
+
language/topics/last-pushed, recent advisories, and recent changes.
|
|
9647
10405
|
|
|
9648
10406
|
Package spec: <registry>:<name>. Supported registries: ${PKGSEER_REGISTRY_LIST}.
|
|
9649
10407
|
|
|
10408
|
+
Example: githits pkg info npm:express
|
|
10409
|
+
|
|
9650
10410
|
Always returns data for the latest published version.`;
|
|
9651
10411
|
function registerPkgInfoCommand(pkgCommand) {
|
|
9652
|
-
return pkgCommand.command("info").summary("Show a package overview").description(PKG_INFO_DESCRIPTION).argument("<spec>", "Package spec, e.g. npm:express or pypi:requests").option("-v, --verbose", "Show
|
|
10412
|
+
return pkgCommand.command("info").summary("Show a package overview").description(PKG_INFO_DESCRIPTION).argument("<spec>", "Package spec, e.g. npm:express or pypi:requests").option("-v, --verbose", "Show GitHub language/topics/last-pushed, recent advisories, and recent changes").option("--json", "Emit the lean JSON envelope").action(async (spec, options) => {
|
|
9653
10413
|
const deps = await createContainer();
|
|
9654
10414
|
await pkgInfoAction(spec, options, {
|
|
9655
10415
|
packageIntelligenceService: deps.packageIntelligenceService,
|
|
@@ -9668,17 +10428,19 @@ async function pkgVulnsAction(spec, options, deps) {
|
|
|
9668
10428
|
throw new InvalidPackageSpecError("Package intelligence is not configured for this environment.");
|
|
9669
10429
|
}
|
|
9670
10430
|
const parsed = parsePackageSpec(spec);
|
|
9671
|
-
const { params } = buildPackageVulnerabilitiesParams({
|
|
10431
|
+
const { params, filter } = buildPackageVulnerabilitiesParams({
|
|
9672
10432
|
registry: parsed.registry,
|
|
9673
10433
|
packageName: parsed.name,
|
|
9674
10434
|
version: parsed.version,
|
|
9675
10435
|
minSeverity: options.severity,
|
|
9676
|
-
includeWithdrawn: options.includeWithdrawn
|
|
10436
|
+
includeWithdrawn: options.includeWithdrawn,
|
|
10437
|
+
advisoryScope: options.scope
|
|
9677
10438
|
});
|
|
9678
10439
|
const report = await deps.packageIntelligenceService.packageVulnerabilities(params);
|
|
9679
10440
|
if (options.json) {
|
|
9680
10441
|
const payload = buildPackageVulnerabilitiesSuccessPayload(report, {
|
|
9681
|
-
requestedVersion: parsed.version
|
|
10442
|
+
requestedVersion: parsed.version,
|
|
10443
|
+
filter
|
|
9682
10444
|
});
|
|
9683
10445
|
console.log(JSON.stringify(payload));
|
|
9684
10446
|
return;
|
|
@@ -9687,6 +10449,8 @@ async function pkgVulnsAction(spec, options, deps) {
|
|
|
9687
10449
|
verbose: options.verbose,
|
|
9688
10450
|
useColors: shouldUseColors(),
|
|
9689
10451
|
requestedVersion: parsed.version,
|
|
10452
|
+
filter,
|
|
10453
|
+
surface: "cli",
|
|
9690
10454
|
terminalWidth: process.stdout.columns
|
|
9691
10455
|
});
|
|
9692
10456
|
process.stdout.write(output);
|
|
@@ -9729,24 +10493,30 @@ function formatVulnsTerminalError(mapped) {
|
|
|
9729
10493
|
if (available && available.length > 0) {
|
|
9730
10494
|
const sample = available.slice(0, 5).join(", ");
|
|
9731
10495
|
const more = available.length - 5;
|
|
9732
|
-
const suffix = more > 0 ? `,
|
|
10496
|
+
const suffix = more > 0 ? `, ... (+${more} more)` : "";
|
|
9733
10497
|
lines.push(` available: ${sample}${suffix}`);
|
|
9734
10498
|
}
|
|
9735
10499
|
return lines.join(`
|
|
9736
10500
|
`);
|
|
9737
10501
|
}
|
|
9738
10502
|
var PKG_VULNS_DESCRIPTION = `Show known vulnerabilities for a package. Lists CVE / OSV advisories
|
|
9739
|
-
with severity, affected version ranges, and fix versions.
|
|
9740
|
-
|
|
10503
|
+
with severity, affected version ranges, and fix versions. Default text is
|
|
10504
|
+
capped for readability; use --verbose for all selected advisory rows or --json
|
|
10505
|
+
for the complete structured envelope.
|
|
9741
10506
|
|
|
9742
10507
|
Package spec: <registry>:<name>[@<version>]. Supported registries:
|
|
9743
|
-
npm, pypi, hex, crates.
|
|
10508
|
+
npm, pypi, hex, crates, nuget, maven, packagist, rubygems, go. vcpkg and zig are not supported.
|
|
10509
|
+
Omit @<version> to check the latest release.
|
|
10510
|
+
Example: githits pkg vulns npm:lodash@4.17.20 --severity high
|
|
9744
10511
|
|
|
9745
10512
|
Severity filter (--severity) and withdrawn-advisory visibility
|
|
9746
10513
|
(--include-withdrawn) are passed through to the backend; the
|
|
9747
|
-
returned count reflects whatever survived the filter
|
|
10514
|
+
returned count reflects whatever survived the filter and active filters
|
|
10515
|
+
are echoed in text and JSON output. Use --scope non_affecting to list
|
|
10516
|
+
historical advisories that do not affect the inspected version, or --scope all
|
|
10517
|
+
to list affected and historical package advisories together.`;
|
|
9748
10518
|
function registerPkgVulnsCommand(pkgCommand) {
|
|
9749
|
-
return pkgCommand.command("vulns").summary("List known vulnerabilities for a package").description(PKG_VULNS_DESCRIPTION).argument("<spec>", "Package spec, e.g. npm:express or npm:express@4.18.0").option("-s, --severity <level>", "Only show advisories at or above this severity (low, medium, high, critical). Omit to see all.").option("--include-withdrawn", "Include retracted advisories (default: off)").option("-v, --verbose", "Show aliases, modified/withdrawn dates, and malicious-advisory markers").option("--json", "Emit the lean JSON envelope").action(async (spec, options) => {
|
|
10519
|
+
return pkgCommand.command("vulns").summary("List known vulnerabilities for a package").description(PKG_VULNS_DESCRIPTION).argument("<spec>", "Package spec, e.g. npm:express or npm:express@4.18.0").option("-s, --severity <level>", "Only show advisories at or above this severity (low, medium, high, critical). Omit to see all.").option("--scope <scope>", "Advisory rows to return: affected, non_affecting, all (default: affected)").option("--include-withdrawn", "Include retracted advisories (default: off)").option("-v, --verbose", "Show aliases, modified/withdrawn dates, and malicious-advisory markers").option("--json", "Emit the lean JSON envelope").action(async (spec, options) => {
|
|
9750
10520
|
const deps = await createContainer();
|
|
9751
10521
|
await pkgVulnsAction(spec, options, {
|
|
9752
10522
|
packageIntelligenceService: deps.packageIntelligenceService,
|
|
@@ -9763,7 +10533,7 @@ async function registerPkgCommandGroup(program, options = {}) {
|
|
|
9763
10533
|
if (!registration.shouldRegister) {
|
|
9764
10534
|
return;
|
|
9765
10535
|
}
|
|
9766
|
-
const pkgCommand = program.command("pkg").summary("Package metadata: info, vulnerabilities, dependencies, changelog").description("Inspect package metadata from npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, RubyGems, Go, vcpkg, and Zig: overviews, advisories, dependency graphs, and changelogs. For source-level operations inside a dependency, use `githits code`.");
|
|
10536
|
+
const pkgCommand = program.command("pkg").summary("Package metadata: info, vulnerabilities, dependencies, changelog").description("Inspect package metadata from npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, RubyGems, Go, vcpkg, and Zig: overviews, advisories, dependency graphs, and changelogs. Advisory data is unavailable for vcpkg and Zig. For source-level operations inside a dependency, use `githits code`.");
|
|
9767
10537
|
registerPkgInfoCommand(pkgCommand);
|
|
9768
10538
|
registerPkgVulnsCommand(pkgCommand);
|
|
9769
10539
|
registerPkgDepsCommand(pkgCommand);
|
|
@@ -9887,7 +10657,7 @@ function requireSearchService(deps) {
|
|
|
9887
10657
|
return deps.codeNavigationService;
|
|
9888
10658
|
}
|
|
9889
10659
|
async function loadContainer2() {
|
|
9890
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
10660
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-k35egwwf.js");
|
|
9891
10661
|
return createContainer2();
|
|
9892
10662
|
}
|
|
9893
10663
|
function parseTargetSpecs(specs) {
|
|
@@ -9997,13 +10767,13 @@ function formatUnifiedSearchTerminal(payload) {
|
|
|
9997
10767
|
`).trimEnd();
|
|
9998
10768
|
}
|
|
9999
10769
|
const { display, duplicatesFolded } = dedupeSearchResultsForDisplay(payload.results);
|
|
10000
|
-
const baseCount = `${display.length} result
|
|
10770
|
+
const baseCount = `${display.length} result${display.length === 1 ? "" : "s"}`;
|
|
10001
10771
|
const countSuffix = [
|
|
10002
10772
|
payload.hasMore ? " (more available)" : "",
|
|
10003
10773
|
duplicatesFolded > 0 ? ` (+${duplicatesFolded} near-duplicate folded)` : ""
|
|
10004
10774
|
].join("");
|
|
10005
|
-
|
|
10006
|
-
lines.push(dim(
|
|
10775
|
+
const typeSummary = formatUnifiedSearchTypeSummary(display);
|
|
10776
|
+
lines.push(`${highlight(baseCount, useColors)}${dim(countSuffix, useColors)}${typeSummary ? dim(` | ${typeSummary}`, useColors) : ""}`);
|
|
10007
10777
|
lines.push("");
|
|
10008
10778
|
for (const entry of display) {
|
|
10009
10779
|
const location = formatUnifiedSearchLocation(entry.locator);
|
|
@@ -10153,7 +10923,7 @@ function formatUnifiedSearchTypeSummary(results) {
|
|
|
10153
10923
|
for (const result of results) {
|
|
10154
10924
|
counts.set(result.type, (counts.get(result.type) ?? 0) + 1);
|
|
10155
10925
|
}
|
|
10156
|
-
return Array.from(counts.entries()).map(([type, count]) => formatUnifiedSearchCountLabel(type, count)).join("
|
|
10926
|
+
return Array.from(counts.entries()).map(([type, count]) => formatUnifiedSearchCountLabel(type, count)).join(", ");
|
|
10157
10927
|
}
|
|
10158
10928
|
function formatUnifiedSearchResultLabel(type) {
|
|
10159
10929
|
switch (type) {
|
|
@@ -10206,11 +10976,33 @@ function formatUnifiedSearchLocation(locator) {
|
|
|
10206
10976
|
return `${locator.filePath}:${locator.startLine}${locator.endLine && locator.endLine !== locator.startLine ? `-${locator.endLine}` : ""}`;
|
|
10207
10977
|
}
|
|
10208
10978
|
function formatUnifiedSearchHeader(entry, useColors, location, rawQuery) {
|
|
10979
|
+
if (entry.type === "documentation_page") {
|
|
10980
|
+
return formatDocumentationPageHeader(entry, useColors);
|
|
10981
|
+
}
|
|
10209
10982
|
const primary = formatUnifiedSearchPrimary(entry.type, entry.target, location, rawQuery, useColors);
|
|
10210
10983
|
const badge = `[${formatUnifiedSearchResultLabel(entry.type)}]`;
|
|
10211
10984
|
const title = entry.title ? highlightRanges(entry.title, entry.highlights?.title, useColors) : undefined;
|
|
10212
10985
|
return `${primary} ${dim(badge, useColors)}${title ? ` - ${title}` : ""}`;
|
|
10213
10986
|
}
|
|
10987
|
+
function formatDocumentationPageHeader(entry, useColors) {
|
|
10988
|
+
const pageId = entry.locator.pageId ?? "unknown";
|
|
10989
|
+
const title = entry.title ? highlightRanges(entry.title, entry.highlights?.title, useColors) : "Untitled documentation page";
|
|
10990
|
+
const source = entry.locator.sourceUrl ? ` - ${formatDisplayUrl(entry.locator.sourceUrl)}` : "";
|
|
10991
|
+
const target = formatDocsPageTarget2(entry.locator, entry.target);
|
|
10992
|
+
return `${highlight(pageId, useColors)} ${dim("[docs page]", useColors)}${target ? ` ${dim(target, useColors)}` : ""} - ${title}${dim(source, useColors)}`;
|
|
10993
|
+
}
|
|
10994
|
+
function formatDisplayUrl(value) {
|
|
10995
|
+
return value.replace(/^https?:\/\//, "");
|
|
10996
|
+
}
|
|
10997
|
+
function formatDocsPageTarget2(locator, fallbackTarget) {
|
|
10998
|
+
return locator.registry && locator.packageName ? `${locator.registry}:${locator.packageName}` : stripVersionFromTarget2(fallbackTarget);
|
|
10999
|
+
}
|
|
11000
|
+
function stripVersionFromTarget2(value) {
|
|
11001
|
+
if (!value)
|
|
11002
|
+
return "";
|
|
11003
|
+
const atIndex = value.lastIndexOf("@");
|
|
11004
|
+
return atIndex > 0 ? value.slice(0, atIndex) : value;
|
|
11005
|
+
}
|
|
10214
11006
|
function formatUnifiedSearchPrimary(type, target, location, rawQuery, useColors) {
|
|
10215
11007
|
const formattedTarget = highlight(target, useColors);
|
|
10216
11008
|
if (type === "documentation_page" || !location) {
|
|
@@ -10318,14 +11110,8 @@ function formatUnifiedSearchMetadata(entry, useColors) {
|
|
|
10318
11110
|
return [];
|
|
10319
11111
|
}
|
|
10320
11112
|
const lines = [];
|
|
10321
|
-
if (entry.
|
|
10322
|
-
|
|
10323
|
-
lines.push(` ${dim("pageId:", useColors)} ${entry.locator.pageId}`);
|
|
10324
|
-
}
|
|
10325
|
-
}
|
|
10326
|
-
const sourceBadge = entry.locator.sourceKind?.toLowerCase() === "repository" ? "[repo]" : entry.locator.sourceKind?.toLowerCase() === "crawled" ? "[crawled]" : undefined;
|
|
10327
|
-
if (entry.locator.sourceUrl && entry.type === "documentation_page") {
|
|
10328
|
-
lines.push(` ${dim("source:", useColors)} ${sourceBadge ? `${sourceBadge} ` : ""}${entry.locator.sourceUrl}`);
|
|
11113
|
+
if (entry.type === "documentation_page") {
|
|
11114
|
+
return lines;
|
|
10329
11115
|
}
|
|
10330
11116
|
return lines;
|
|
10331
11117
|
}
|
|
@@ -10364,6 +11150,8 @@ if (isTelemetryEnabled()) {
|
|
|
10364
11150
|
}
|
|
10365
11151
|
var rootCliPreAction = createRootCliPreAction({
|
|
10366
11152
|
createContainer,
|
|
11153
|
+
loadAuthSessionMetadata: loadAutoLoginAuthSessionMetadata,
|
|
11154
|
+
clearAuthSessionMetadata: clearAutoLoginAuthSessionMetadata,
|
|
10367
11155
|
loginFlow: (options, deps) => loginFlow(options, deps, stderrLoginOutput)
|
|
10368
11156
|
});
|
|
10369
11157
|
program.name("githits").description("Code examples from global open source for your AI assistant").version(version).option("--no-color", "Disable colored output").hook("preAction", async (thisCommand, actionCommand) => {
|