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/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-v8sths32.js";
54
+ } from "./shared/chunk-tfqxat16.js";
53
55
  import {
54
56
  __require,
55
57
  version
56
- } from "./shared/chunk-jdygt0ra.js";
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(`↳ in: ${line.symbolHint}`, useColors)}`;
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
- return Math.max(0, Math.min(text.length, offset));
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 = buildFilterEcho2(envelope);
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 buildFilterEcho2(envelope) {
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} · ${envelope.pages.length} page${envelope.pages.length === 1 ? "" : "s"}`;
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 SUPPORTED_DEPS_REGISTRIES_HUMAN = "npm, pypi, hex, crates, vcpkg, zig, rubygems, and go";
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 ${SUPPORTED_DEPS_REGISTRIES_HUMAN}. Got: ${normalisedRegistryArg}.`);
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} · ${payload.registry}`
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(", ")} ${options.hiddenGroupsHint ?? "use --lifecycle all."}`, useColors);
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
- return sorted.map((item) => ` ${formatDepLabel(item)}`).join(`
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
- return sorted.map((item) => {
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
- }).join(`
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
- return sorted.map((pkg) => ` ${formatPackageLabel(pkg)}`).join(`
2521
+ lines.push(...sorted.map((pkg) => ` ${formatPackageLabel(pkg)}`));
2522
+ return lines.join(`
2521
2523
  `);
2522
2524
  }
2523
- return sorted.map((pkg) => {
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
- }).join(`
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
- const name = dep.name.padEnd(nameWidth);
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
- const total = security.vulnerabilityCount ?? 0;
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, 120).trimEnd()}…` : firstLine;
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} · ${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: dim(lean.repository, useColors)
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.github) {
3128
- fields.push({ label: "GitHub", value: formatGithubLine(lean.github) });
3129
- }
3130
- if (lean.install) {
3131
- fields.push({ label: "Install", value: lean.install });
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 formatGithubLine(github) {
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(`★ ${formatCompactNumber(github.stars)}`);
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)} open issues`);
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 formatVulnFooter(vulns, useColors) {
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
- const base = `${vulns.total} known ${noun}`;
3155
- const full = vulns.affectsLatest ? `${base} · latest affected` : base;
3156
- return colorize(full, "yellow", useColors);
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.usage) {
3161
- blocks.push([
3162
- highlight("Usage", useColors),
3163
- lean.usage.split(`
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 crates";
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 minSeverity = resolveMinSeverity(input.minSeverity);
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 resolveMinSeverity(raw) {
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 SEVERITY_LABEL_TO_CVSS[lower];
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 (total > 0) {
3297
- const sortedAdvisories = sortAdvisories(dedupedAdvisories.map(buildAdvisory));
3298
- if (sortedAdvisories.length > 0) {
3299
- payload.advisories = sortedAdvisories;
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 (total === 0)
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 breakdown = formatBreakdownLine(payload.summary, useColors);
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} · ${payload.registry}`;
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 "No known vulnerabilities affect this version.";
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 labelWidth = Math.max(...advisories.map((a) => severityColumnLabel(a).length));
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 advisories) {
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 · ${advisory.severityLabel}`;
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(`… (+${backendHidden} ranges omitted by service)`, useColors);
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 hintText = backendHidden > 0 ? `… (+${localHidden} more with -v; +${backendHidden} omitted by service)` : `… (+${localHidden} more; use -v)`;
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(buildHeader4(envelope));
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 buildHeader4(envelope) {
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.split(`
4014
- `);
4015
- if (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === "") {
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(buildHeader5(envelope, options.useColors));
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 buildHeader5(envelope, useColors) {
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(buildHeader6(envelope));
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 buildHeader6(envelope) {
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("At least one target is required.");
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(buildHeader7(payload));
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 buildHeader7(payload) {
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.target, shortType(hit.type)];
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(buildHeader8(payload));
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 buildHeader8(payload) {
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-q2mre780.js");
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-q2mre780.js");
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 search result.
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
- Rate whether a code example was helpful. Use --accept for positive
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 abc123 --accept --message "Solved my problem" --json`;
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 search result").description(FEEDBACK_DESCRIPTION).argument("<solution_id>", "Solution ID from search result").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) => {
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: ["plugin", "marketplace", "add", "githits-com/githits-cli"]
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", "githits@githits-plugins"]
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: /githits/i
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("The solution ID from a previous search result (shown in the result)"),
7912
- accepted: z.boolean().describe("True if the example was helpful/good, False if unhelpful/bad"),
7913
- feedback_text: z.string().optional().describe('Optional text explaining why (e.g., "This solved problem X" or "Example was outdated")')
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 example result.
8635
+ var DESCRIPTION = `Submit feedback on a tool result or the GitHits experience.
7916
8636
 
7917
- Call after \`get_example\` to record whether the returned example was used. \`accepted=true\` when it solved the problem or was useful; \`accepted=false\` when it was irrelevant or wrong. Use \`feedback_text\` to add a short reason. Feeds back into ranking quality.`;
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("Response format. Default `text-v1` returns markdown directly with a trailing `solution_id` line when available. Pass `json` for `{result, solution_id?}`.")
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. " + "Use this when you know the text pattern you want; use `search` for discovery. " + "Whole-target grep is the default — narrow with `path`, `path_prefix`, `globs`, or `extensions` to keep responses small. " + 'Default response is a compact line-oriented listing (`format: "text-v1"`); pass `format: "json"` for the structured envelope. ' + "Matches chain directly into `code_read` (the `path` and `line` from each match feed straight into `start_line` / `end_line`).";
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 tuned for agent context efficiency. Pass `format: "json"` for the structured envelope. `text` is an alias for `text-v1`. Errors stay JSON-formatted in either mode for now.')
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. Default response is a compact " + 'paths-only listing (`format: "text-v1"`); pass `format: "json"` ' + "for the structured envelope `{total, hasMore, files: [{path, name, " + "language, fileType, byteSize}], resolution, indexedVersion}`. " + "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. " + "The returned paths feed directly into `code_read` and help scope " + "`code_grep`. 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`.";
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("", options.useColors);
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(`${colorize(padded, "bold", options.useColors)} ${datePadded} ${url}`);
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(` ${dim(bodyLine, options.useColors)}`);
9202
+ lines.push(` ${bodyLine}`);
8479
9203
  }
8480
9204
  const hidden = bodyLines.length - visible.length;
8481
9205
  if (hidden > 0) {
8482
- lines.push(` ${dim(`… (+${hidden} more line${hidden === 1 ? "" : "s"} ${options.fullBodyHint ?? "use --verbose for the full body"})`, options.useColors)}`);
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} · ${envelope.registry}` : envelope.repoUrl ?? "(unknown)";
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(" · "), "bold", options.useColors);
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} ${to}`;
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
- format: z7.enum(["json", "text", "text-v1"]).optional().describe("Response format. Default `text-v1` is compact for agents. Pass `json` for the structured envelope.")
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"`), ' + 'and entries with markdown body previews. Pass `format: "json"` ' + "for the structured envelope with full markdown bodies. Set " + "`include_bodies: false` for a version / date / URL timeline only. " + "Package-version entries without changelog text succeed with `source` " + "omitted; no-source plus no entries returns `NOT_FOUND`. Supports npm, " + "PyPI, Hex, Crates, vcpkg, Zig, NuGet, Maven, Packagist, RubyGems, Go.";
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 (isTextFormat5(args.format)) {
9316
+ if (textFormat) {
8589
9317
  return textResult(formatPackageChangelogTerminal(payload, {
8590
9318
  useColors: false,
8591
- verbose: false,
8592
- fullBodyHint: 'pass format="json" for full bodies'
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 ${SUPPORTED_DEPS_REGISTRIES_HUMAN}.`),
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("Response format. Default `text-v1` is compact for agents. Pass `json` for the structured envelope.")
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. Default output is compact " + "text listing direct runtime dependencies with resolved versions; " + 'pass `format: "json"` for the structured envelope. Non-runtime ' + "groups are omitted by default for token efficiency. Use `lifecycle` " + "with a concrete value for runtime plus matching groups, or `all` " + "for runtime plus all available groups. 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, " + "RubyGems, Go, vcpkg, and Zig.";
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
- format: z9.enum(["json", "text", "text-v1"]).optional().describe("Response format. Default `text-v1` is compact for agents. Pass `json` for the structured envelope.")
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 = "Get a package overview latest version, license, description, " + "repository, downloads, GitHub stars, install command, recent " + "changes, and a count of known vulnerabilities. Use before " + "recommending a package or to orient on what a dependency is. " + 'Default output is compact text; pass `format: "json"` for the ' + "structured envelope. " + "Works across npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, " + "RubyGems, Go, vcpkg, and Zig. Always returns data for the latest published " + "version.";
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 available on npm, pypi, hex, and crates only."),
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
- format: z10.enum(["json", "text", "text-v1"]).optional().describe("Response format. Default `text-v1` is compact for agents. Pass `json` for the structured envelope.")
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, or " + "Crates (other registries are not yet 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. Pass `version` to inspect a specific release; otherwise " + "the latest is checked. Use `min_severity` to filter to a threshold " + "(`low`, `medium`, `high`, `critical`) and `include_withdrawn` to " + "also see retracted advisories. Default output is compact text; " + 'pass `format: "json"` for the structured envelope.';
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("Path to the file. Package addressing: package-relative. Repo addressing: repo-relative. This is the same `path` key that `code_files` emits for each entry, so chaining needs no renaming."),
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 a file from an indexed dependency. **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.";
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
- return `Returned lines ${payload.startLine}-${payload.endLine}/${payload.totalLines} ` + `(MCP cap: ${MCP_READ_MAX_SPAN} lines per call; you requested ${requested}). ` + `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.`;
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 tuned for agent context efficiency. Pass `format: "json"` for the structured envelope (programmatic consumers, parity testing). `text` is an alias for `text-v1`. The text format is a public, snapshot-tested contract.')
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. " + "The query field uses GitHits discovery syntax (AND/OR/parens/qualifiers; see the parameter description). " + "Structured parameters combine with that query using AND semantics. " + "Results are 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).";
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("Response format. Default `text-v1` returns one language per line. Pass `json` for the structured array.")
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("Search reference returned by search."),
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 unified search. " + "Pass the search_ref returned by `search` when the original request did not complete within the wait window.";
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. Use it for global solution synthesis and canonical examples when you're stuck, repeated attempts failed, you need up-to-date API usage, the question is comparative across OSS projects (e.g. "how does X vs Y handle Z"), the answer requires reading how a real codebase implements a feature, or the user mentions GitHits.
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
- Example workflow: call \`get_example\` with one focused question; pass \`language\` only when you know the exact language name, otherwise call \`search_language\` first. Default output is markdown with a trailing \`solution_id\`; send \`feedback\` on that solution_id. Reuse prior results before searching again. For dependency-specific grounding, use package-scoped \`search\` before global \`get_example\`.`;
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 PKG_INFO_BULLET = '- `pkg_info` compact package overview: latest version, license, downloads, quickstart, and active advisory count. Pass `format: "json"` for the structured envelope.';
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 CODE_FILES_BULLET = '- `code_files` — list files in an indexed dependency. `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.';
9219
- var CODE_READ_BULLET = "- `code_read` — read a dependency file by `path`. **MCP cap: 150 lines per call**; choose focused `start_line` / `end_line` windows from `search` or `code_grep`. Binary files set `isBinary: true` and omit `content`. On `FILE_NOT_FOUND` / `NOT_FOUND`, call `code_files` for the actual path.";
9220
- 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 `path:line` chains into `code_read`.";
9221
- var SEARCH_VS_SYMBOLS_TIP = 'Use code-first grounding for behavioral claims: source, symbols, tests, examples, and call sites beat docs prose. Prefer unified `search` for indexed dependency and repository discovery; use `sources:["symbol"]` when you want symbol-shaped results. Prefer `get_example` for global canonical example retrieval after package-scoped grounding. Use `code_grep` for deterministic text matching and `code_read` for focused-window inspection of a known file.';
9222
- var REFERENCE_FIRST_TIP = "Strategy — reference-first, content-second. Locate symbols and lines with `search` / `code_grep` first, then read only the needed window with `code_read` using explicit `start_line` / `end_line` values, typically 80-150 lines around the match. The MCP `code_read` surface caps each call at 150 lines.";
9223
- 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.';
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 sections = [CORE_BLOCK];
9226
- const bullets = [];
9227
- bullets.push(DOCS_LIST_BULLET);
9228
- bullets.push(DOCS_READ_BULLET);
9229
- bullets.push(PKG_INFO_BULLET);
9230
- bullets.push(PKG_VULNS_BULLET);
9231
- bullets.push(PKG_DEPS_BULLET);
9232
- bullets.push(PKG_CHANGELOG_BULLET);
9233
- bullets.push(SEARCH_BULLET);
9234
- bullets.push(SEARCH_STATUS_BULLET);
9235
- bullets.push(CODE_FILES_BULLET);
9236
- bullets.push(CODE_READ_BULLET);
9237
- bullets.push(CODE_GREP_BULLET);
9238
- const parts = [PACKAGE_TOOLS_PREAMBLE];
9239
- parts.push(MULTI_TURN_TIP);
9240
- parts.push(bullets.join(`
9241
- `));
9242
- parts.push(REFERENCE_FIRST_TIP);
9243
- parts.push(SEARCH_VS_SYMBOLS_TIP);
9244
- sections.push(parts.join(`
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 sections.join(`
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 ? `, (+${more} more)` : "";
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). Concrete --lifecycle values include runtime plus matching 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
- npm, pypi, hex, crates, vcpkg, zig, rubygems, go. Omit @<version> for the latest
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 = `Get a package overview latest version, license, description,
9644
- repository, downloads, GitHub stars, install command, and known
9645
- vulnerabilities. Use before picking a dependency or to orient on what
9646
- a package is.
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 advisories, install usage, topics, and recent changes").option("--json", "Emit the lean JSON envelope").action(async (spec, options) => {
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 ? `, (+${more} more)` : "";
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
- Malicious-package advisories are flagged prominently.
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. Omit @<version> to check the latest release.
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-q2mre780.js");
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(s)`;
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
- lines.push(`${highlight(baseCount, useColors)}${dim(countSuffix, useColors)}`);
10006
- lines.push(dim(formatUnifiedSearchTypeSummary(display), useColors));
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.locator.pageId) {
10322
- if (entry.type === "documentation_page") {
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) => {